@ -0,0 +1,62 @@ |
|||
# ABP Framework 3.1 Final Has Been Released |
|||
|
|||
It is exciting for us to announce that we've released the ABP Framework & ABP Commercial 3.1 today. |
|||
|
|||
Since all the new features are already explained in details with the [3.1 RC Announcement Post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released), I will not repeat all the details here. Please read [the RC post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) for **new feature and changes** you may need to do for your solution while upgrading to the version 3.1. |
|||
|
|||
## Creating New Solutions |
|||
|
|||
You can create a new solution with the ABP Framework version 3.1 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). |
|||
|
|||
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. |
|||
|
|||
## How to Upgrade an Existing Solution |
|||
|
|||
### Install/Update the ABP CLI |
|||
|
|||
First of all, install the ABP CLI or upgrade to the latest version. |
|||
|
|||
If you haven't installed yet: |
|||
|
|||
````bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
```` |
|||
|
|||
To update an existing installation: |
|||
|
|||
```bash |
|||
dotnet tool update -g Volo.Abp.Cli |
|||
``` |
|||
|
|||
### ABP UPDATE Command |
|||
|
|||
[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: |
|||
|
|||
````bash |
|||
abp update |
|||
```` |
|||
|
|||
After the update command, check [the RC blog post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) to learn if you need to make any changes in your solution. |
|||
|
|||
> You may want to see the new [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading). |
|||
|
|||
## About the version 3.2 |
|||
|
|||
The planned schedule for the version 3.2 is like that; |
|||
|
|||
* **September 17, 2020**: 3.2.0-rc.1 (release candidate) |
|||
* **October 1, 2020**: 3.2.0 final (stable) |
|||
|
|||
You can check [the GitHub milestone](https://github.com/abpframework/abp/milestone/39) to see the features/issues we are working on. |
|||
|
|||
## ABP Community & Articles |
|||
|
|||
We had lunched the [ABP Community web site](https://community.abp.io/) a few weeks before. The core ABP team and the ABP community have started to create content for the community. |
|||
|
|||
Here, the last three articles from the ABP Community: |
|||
|
|||
* [ABP Suite: How to Add the User Entity as a Navigation Property of Another Entity](https://community.abp.io/articles/abp-suite-how-to-add-the-user-entity-as-a-navigation-property-of-another-entity-furp75ex) by [@ebicoglu](https://github.com/ebicoglu) |
|||
* [Reuse ABP vNext Modules to Quickly Implement Application Features](https://community.abp.io/articles/reuse-abp-vnext-modules-to-quickly-implement-application-features-tdtmwd9w) by [@gdlcf88](https://github.com/gdlcf88) |
|||
* [Using DevExtreme Components With the ABP Framework](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) by [@cotur](https://github.com/cotur). |
|||
|
|||
We are looking for your contributions; You can [submit your article](https://community.abp.io/articles/submit)! We will promote your article to the community. |
|||
@ -0,0 +1,431 @@ |
|||
# Introducing the Angular Service Proxy Generation |
|||
|
|||
ABP 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 a solution that was created before v3.1, follow the steps below to configure the angular application: |
|||
|
|||
* 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' |
|||
}, |
|||
} |
|||
``` |
|||
|
|||
`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. This value is `AngularProxyDemo` for the example solution explained below. |
|||
|
|||
* Finally, add the following paths to the `tsconfig.base.json` to have a shortcut while importing proxies: |
|||
|
|||
```json |
|||
"paths": { |
|||
"@proxy": ["src/app/proxy/index.ts"], |
|||
"@proxy/*": ["src/app/proxy/*"] |
|||
} |
|||
``` |
|||
|
|||
## Basic Usage |
|||
|
|||
### Project Creation |
|||
|
|||
> If you already have a solution, you can skip this section. |
|||
|
|||
You need to [create](https://abp.io/get-started) your solution with the Angular UI. You can use the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to create a new solution: |
|||
|
|||
````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 details of creating and running the solution. |
|||
|
|||
### 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 [auto API controllers](https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers) system of the ABP Framework, we don't have to 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/proxy/books/book.service.ts (446 bytes) |
|||
CREATE src/app/proxy/books/models.ts (148 bytes) |
|||
CREATE src/app/proxy/books/index.ts (57 bytes) |
|||
CREATE src/app/proxy/index.ts (33 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). |
|||
|
|||
#### The Generated Code |
|||
|
|||
`src/app/proxy/books/book.service.ts`: This is the service that can be injected and used to get the list of books; |
|||
|
|||
````js |
|||
import type { BookDto } from './models'; |
|||
import { RestService } from '@abp/ng.core'; |
|||
import { Injectable } from '@angular/core'; |
|||
|
|||
@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/proxy/books/models.ts`: This file contains the modal classes corresponding to the DTOs defined in the server side; |
|||
|
|||
````js |
|||
import type { EntityDto } from '@abp/ng.core'; |
|||
|
|||
export interface BookDto extends EntityDto<string> { |
|||
name: string; |
|||
publishDate: string; |
|||
} |
|||
```` |
|||
|
|||
> There are a few more files have been generated to help you import the types easier. |
|||
|
|||
#### How to Import |
|||
|
|||
You can now import the `BookService` into any Angular component and use the `getList()` method to get the list of books. |
|||
|
|||
````js |
|||
import { BookService, BookDto } from '../proxy/books'; |
|||
```` |
|||
|
|||
You can also use the `@proxy` as a shortcut of the proxy folder: |
|||
|
|||
````js |
|||
import { BookService, BookDto } from '@proxy/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: |
|||
|
|||
````bash |
|||
abp generate-proxy |
|||
```` |
|||
|
|||
This command will re-generate the proxies by updating some files. Let's see some of the changes; |
|||
|
|||
**book.service.ts** |
|||
|
|||
````js |
|||
import type { BookDto, BookUpdateDto } from './models'; |
|||
import { RestService } from '@abp/ng.core'; |
|||
import { Injectable } from '@angular/core'; |
|||
|
|||
@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. |
|||
|
|||
**models.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 there are some created and updated files. Let's see some important ones; |
|||
|
|||
`src/app/proxy/orders/order.service.ts` |
|||
|
|||
````js |
|||
import type { OrderCreateDto } from './models'; |
|||
import { RestService } from '@abp/ng.core'; |
|||
import { Injectable } from '@angular/core'; |
|||
|
|||
@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/proxy/orders/models.ts` |
|||
|
|||
````typescript |
|||
export interface GenericDetailDto<TCount> { |
|||
productId: string; |
|||
count: TCount; |
|||
} |
|||
|
|||
export interface OrderCreateDto { |
|||
customerId: string; |
|||
creationTime: string; |
|||
details: OrderDetailDto[]; |
|||
extraProperties: Record<string, object>; |
|||
} |
|||
|
|||
export interface OrderDetailDto extends GenericDetailDto<number> { |
|||
note: string; |
|||
} |
|||
```` |
|||
|
|||
## Conclusion |
|||
|
|||
`abp generate-proxy` is a very handy command that creates all the necessary code to consume your ABP based backend HTTP APIs. It generates a clean code that is well aligned to the backend services and benefits from the power of TypeScript (by using generics, inheritance...). |
|||
|
|||
## The Documentation |
|||
|
|||
See [the documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies) for details of the Angular Service Proxy Generation. |
|||
|
After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,81 @@ |
|||
# ABP Suite: How to Add the User Entity as a Navigation Property of Another Entity |
|||
|
|||
## Introduction |
|||
|
|||
[ABP Suite](https://commercial.abp.io/tools/suite), a part of the [ABP Commercial](https://commercial.abp.io/), is a productivity tool developed by the team behind the ABP Framework. The main functionality of the ABP Suite is to generate code for you. |
|||
|
|||
In this post, I'll show you how to add the user entity as a navigation property in your new entity, by the help of the ABP Suite. |
|||
|
|||
> In the sample project MVC UI is used, but the same steps are applicable to the Angular UI as well. |
|||
|
|||
## Code Generation |
|||
|
|||
### Create a New Entity |
|||
|
|||
Open the ABP Suite ([see how](https://docs.abp.io/en/commercial/latest/abp-suite/index)). Create a new entity called `Note`, as an example entity. |
|||
|
|||
 |
|||
|
|||
Then add a string property called `Title`, as an example property. |
|||
|
|||
 |
|||
|
|||
### Create AppUserDto |
|||
|
|||
ABP Suite needs a DTO for the target entity (user, in this case) in order to define a navigation property. |
|||
|
|||
To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`. |
|||
|
|||
 |
|||
|
|||
We should define the [object mapping](https://docs.abp.io/en/abp/latest/Object-To-Object-Mapping) to be able to convert the `AppUser` objects to `AppUserDto` objects. To do this, open `YourProjectApplicationAutoMapperProfile.cs` and add the below line: |
|||
|
|||
```csharp |
|||
CreateMap<AppUser, AppUserDto>().Ignore(x => x.ExtraProperties); |
|||
``` |
|||
|
|||
 |
|||
|
|||
> Creating such a DTO class may not be needed for another entity than the `AppUser`, since it will probably be already available, especially if you had created the other entity using the ABP Suite. |
|||
|
|||
### Define the Navigation Property |
|||
|
|||
Get back to ABP Suite, open the **Navigation Properties** tab of the ABP Suite, click the **Add Navigation Property** button. Browse `AppUser.cs` in `*.Domain\Users` folder. Then choose the `Name` item as display property. Browse `AppUserDto.cs` in `*.Contracts\Users` folder. Choose `Users` from Collection Names dropdown. |
|||
|
|||
 |
|||
|
|||
### Generate the Code! |
|||
|
|||
That's it! Click **Save and generate** button to create your page. You'll see the following page if everything goes well. |
|||
|
|||
 |
|||
|
|||
This is the new page that has been created by the ABP Suite. It can perform the fundamental CRUD operations. Also, it has the "App user" column that shows the related user name (you can easily change the automatically created "App user" title from the **Entity Name** field of the navigation property creation screen). |
|||
|
|||
**Picking Users from Look Up Table** |
|||
|
|||
We used dropdown element to select a user from the user list. If you have a lot of users, then it's good to pick a user from a look up table. A look up table is a modal window that lets you filter data and pick one. To do this, get back to Suite and click **Edit** button of user navigation which is set as `AppUserId` name. Choose "Modal" from the "UI Pick Type" field. Then click **Save and generate** button to recreate your page. |
|||
|
|||
 |
|||
|
|||
After successful code generation, you'll see the the user can be picked from user table. |
|||
|
|||
 |
|||
|
|||
## About the ABP Commercial RC |
|||
|
|||
This example has been implemented with **ABP Commercial 3.1.0**. If you have not installed the ABP CLI and ABP Suite, follow the next steps: |
|||
|
|||
1- Uninstall the current version of the CLI and install: |
|||
|
|||
```bash |
|||
dotnet tool install --global Volo.Abp.Cli --version 3.1.0 |
|||
``` |
|||
|
|||
2- Uninstall the current version of the Suite and install: |
|||
|
|||
```bash |
|||
dotnet tool uninstall --global Volo.Abp.Suite && dotnet tool install -g Volo.Abp.Suite --version 3.1.0 --add-source https://nuget.abp.io/<YOUR-API-KEY>/v3/index.json |
|||
``` |
|||
|
|||
Don't forget to replace the `<YOUR-API-KEY>` with your own key! |
|||
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 539 KiB |
|
After Width: | Height: | Size: 175 KiB |
@ -0,0 +1,167 @@ |
|||
# Distributed Event Bus Kafka Integration |
|||
|
|||
> This document explains **how to configure the [Kafka](https://kafka.apache.org/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system |
|||
|
|||
## Installation |
|||
|
|||
Use the ABP CLI to add [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet package to your project: |
|||
|
|||
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. |
|||
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Kafka` package. |
|||
* Run `abp add-package Volo.Abp.EventBus.Kafka` command. |
|||
|
|||
If you want to do it manually, install the [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusKafkaModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. |
|||
|
|||
## Configuration |
|||
|
|||
You can configure using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes. |
|||
|
|||
### `appsettings.json` file configuration |
|||
|
|||
This is the simplest way to configure the Kafka settings. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/). |
|||
|
|||
**Example: The minimal configuration to connect to a local kafka server with default configurations** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "localhost:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `MyGroupId` is the name of this application, which is used as the **GroupId** on the Kakfa. |
|||
* `MyTopicName` is the **topic name**. |
|||
|
|||
See [the Kafka document](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.html) to understand these options better. |
|||
|
|||
#### Connections |
|||
|
|||
If you need to connect to another server than the localhost, you need to configure the connection properties. |
|||
|
|||
**Example: Specify the host name (as an IP address)** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Defining multiple connections is allowed. In this case, you can specify the connection that is used for the event bus. |
|||
|
|||
**Example: Declare two connections and use one of them for the event bus** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
}, |
|||
"SecondConnection": { |
|||
"BootstrapServers": "321.321.321.321:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName", |
|||
"ConnectionName": "SecondConnection" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus. |
|||
|
|||
You can use any of the [ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html) properties as the connection properties. |
|||
|
|||
**Example: Specify the socket timeout** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092", |
|||
"SocketTimeoutMs": 60000 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### The Options Classes |
|||
|
|||
`AbpRabbitMqOptions` and `AbpRabbitMqEventBusOptions` classes can be used to configure the connection strings and event bus options for the RabbitMQ. |
|||
|
|||
You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md). |
|||
|
|||
**Example: Configure the connection** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.Connections.Default.BootstrapServers = "123.123.123.123:9092"; |
|||
options.Connections.Default.SaslUsername = "user"; |
|||
options.Connections.Default.SaslPassword = "pwd"; |
|||
}); |
|||
```` |
|||
|
|||
**Example: Configure the consumer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureConsumer = config => |
|||
{ |
|||
config.GroupId = "MyGroupId"; |
|||
config.EnableAutoCommit = false; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**Example: Configure the producer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureProducer = config => |
|||
{ |
|||
config.MessageTimeoutMs = 6000; |
|||
config.Acks = Acks.All; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**Example: Configure the topic specification** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureTopic = specification => |
|||
{ |
|||
specification.ReplicationFactor = 3; |
|||
specification.NumPartitions = 3; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. |
|||
@ -1,3 +1,448 @@ |
|||
# Features |
|||
|
|||
TODO |
|||
ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. |
|||
|
|||
The runtime value for a feature is generally a `boolean` value, like `true` (enabled) or `false` (disabled). However, you can get/set **any kind** of value for feature. |
|||
|
|||
Feature system was originally designed to control the tenant features in a **[multi-tenant](Multi-Tenancy.md)** application. However, it is **extensible** and capable of determining the features by any condition. |
|||
|
|||
> The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). |
|||
|
|||
## Checking for the Features |
|||
|
|||
Before explaining to define features, let's see how to check a feature value in your application code. |
|||
|
|||
### RequiresFeature Attribute |
|||
|
|||
`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is `true` (enabled) or not. It is a useful shortcut for the `boolean` features. |
|||
|
|||
**Example: Check if the "PDF Reporting" feature enabled** |
|||
|
|||
```csharp |
|||
public class ReportingAppService : ApplicationService, IReportingAppService |
|||
{ |
|||
[RequiresFeature("MyApp.PdfReporting")] |
|||
public async Task<PdfReportResultDto> GetPdfReportAsync() |
|||
{ |
|||
//TODO... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* `RequiresFeature(...)` simply gets a feature name to check if it is enabled or not. If not enabled, an authorization [exception](Exception-Handling.md) is thrown and a proper response is returned to the client side. |
|||
* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the methods of that class require the given feature. |
|||
* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if any of the features enabled. Use `RequiresAll` option, like `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to check all of the features to be enabled. |
|||
* Multiple usage of `[RequiresFeature]` attribute is supported for a method or class. ABP check checks all of them in that case. |
|||
|
|||
> Feature name can be any arbitrary string. It should be unique for a feature. |
|||
|
|||
#### About the Interception |
|||
|
|||
ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class (application services, controllers...) that is injected from the [dependency injection](Dependency-Injection.md). |
|||
|
|||
However, there are **some rules should be followed** in order to make it working; |
|||
|
|||
* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work. |
|||
* Only `async` methods (methods returning a `Task` or `Task<T>`) are intercepted. |
|||
|
|||
> There is an exception for the **controller and razor page methods**. They **don't require** the following the rules above, since ABP Framework uses the action/page filters to implement the feature checking in this case. |
|||
|
|||
### IFeatureChecker Service |
|||
|
|||
`IFeatureChecker` allows to check a feature in your application code. |
|||
|
|||
#### IsEnabledAsync |
|||
|
|||
Returns `true` if the given feature is enabled. So, you can conditionally execute your business flow. |
|||
|
|||
**Example: Check if the "PDF Reporting" feature enabled** |
|||
|
|||
```csharp |
|||
public class ReportingAppService : ApplicationService, IReportingAppService |
|||
{ |
|||
private readonly IFeatureChecker _featureChecker; |
|||
|
|||
public ReportingAppService(IFeatureChecker featureChecker) |
|||
{ |
|||
_featureChecker = featureChecker; |
|||
} |
|||
|
|||
public async Task<PdfReportResultDto> GetPdfReportAsync() |
|||
{ |
|||
if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting")) |
|||
{ |
|||
//TODO... |
|||
} |
|||
else |
|||
{ |
|||
//TODO... |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`IsEnabledAsync` has overloads to check multiple features in one method call. |
|||
|
|||
#### GetOrNullAsync |
|||
|
|||
Gets the current value for a feature. This method returns a `string`, so you store any kind of value inside it, by converting to or from `string`. |
|||
|
|||
**Example: Check the maximum product count allowed** |
|||
|
|||
```csharp |
|||
public class ProductController : AbpController |
|||
{ |
|||
private readonly IFeatureChecker _featureChecker; |
|||
|
|||
public ProductController(IFeatureChecker featureChecker) |
|||
{ |
|||
_featureChecker = featureChecker; |
|||
} |
|||
|
|||
public async Task<IActionResult> Create(CreateProductModel model) |
|||
{ |
|||
var currentProductCount = await GetCurrentProductCountFromDatabase(); |
|||
|
|||
//GET THE FEATURE VALUE |
|||
var maxProductCountLimit = |
|||
await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount"); |
|||
|
|||
if (currentProductCount >= Convert.ToInt32(maxProductCountLimit)) |
|||
{ |
|||
throw new BusinessException( |
|||
"MyApp:ReachToMaxProductCountLimit", |
|||
$"You can not create more than {maxProductCountLimit} products!" |
|||
); |
|||
} |
|||
|
|||
//TODO: Create the product in the database... |
|||
} |
|||
|
|||
private async Task<int> GetCurrentProductCountFromDatabase() |
|||
{ |
|||
throw new System.NotImplementedException(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
This example uses a numeric value as a feature limit product counts for a user/tenant in a SaaS application. |
|||
|
|||
Instead of manually converting the value to `int`, you can use the generic overload of the `GetAsync` method: |
|||
|
|||
```csharp |
|||
var maxProductCountLimit = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount"); |
|||
``` |
|||
|
|||
#### Extension Methods |
|||
|
|||
There are some useful extension methods for the `IFeatureChecker` interface; |
|||
|
|||
* `Task<T> GetAsync<T>(string name, T defaultValue = default)`: Used to get a value of a feature with the given type `T`. Allows to specify a `defaultValue` that is returned when the feature value is `null`. |
|||
* `CheckEnabledAsync(string name)`: Checks if given feature is enabled. Throws an `AbpAuthorizationException` if the feature was not `true` (enabled). |
|||
|
|||
## Defining the Features |
|||
|
|||
A feature should be defined to be able to check it. |
|||
|
|||
### FeatureDefinitionProvider |
|||
|
|||
Create a class inheriting the `FeatureDefinitionProvider` to define permissions. |
|||
|
|||
**Example: Defining permissions** |
|||
|
|||
```csharp |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false"); |
|||
myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10"); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> ABP automatically discovers this class and registers the features. No additional configuration required. |
|||
|
|||
> This class is generally created in the `Application.Contracts` project of your solution. |
|||
|
|||
* In the `Define` method, you first need to add a **feature group** for your application/module or get an existing group then add **features** to this group. |
|||
* First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value. |
|||
* Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value. |
|||
|
|||
Default value is used if there is no other value set for the current user/tenant. |
|||
|
|||
### Other Feature Properties |
|||
|
|||
While these minimal definitions are enough to make the feature system working, you can specify the **optional properties** for the features; |
|||
|
|||
* `DisplayName`: A localizable string that will be used to show the feature name on the user interface. |
|||
* `Description`: A longer localizable text to describe the feature. |
|||
* `ValueType`: Type of the feature value. Can be a class implementing the `IStringValueType`. Built-in types: |
|||
* `ToggleStringValueType`: Used to define `true`/`false`, `on`/`off`, `enabled`/`disabled` style features. A checkbox is shown on the UI. |
|||
* `FreeTextStringValueType`: Used to define free text values. A textbox is shown on the UI. |
|||
* `SelectionStringValueType`: Used to force the value to be selected from a list. A dropdown list is shown on the UI. |
|||
* `IsVisibleToClients` (default: `true`): Set false to hide the value of this feature from clients (browsers). Sharing the value with the clients helps them to conditionally show/hide/change the UI parts based on the feature value. |
|||
* `Properties`: A dictionary to set/get arbitrary key-value pairs related to this feature. This can be a point for customization. |
|||
|
|||
So, based on these descriptions, it would be better to define these features as shown below: |
|||
|
|||
```csharp |
|||
using FeaturesDemo.Localization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
myGroup.AddFeature( |
|||
"MyApp.PdfReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("PdfReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
myGroup.AddFeature( |
|||
"MyApp.MaxProductCount", |
|||
defaultValue: "10", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("MaxProductCount"), |
|||
valueType: new FreeTextStringValueType( |
|||
new NumericValueValidator(0, 1000000)) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system. |
|||
* First feature is set to `ToggleStringValueType`, while the second one is set to `FreeTextStringValueType` with a numeric validator that allows to the values from `0` to `1,000,000`. |
|||
|
|||
Remember to define the localization the keys in your localization file: |
|||
|
|||
````json |
|||
"PdfReporting": "PDF Reporting", |
|||
"MaxProductCount": "Maximum number of products" |
|||
```` |
|||
|
|||
See the [localization document](Localization.md) for details about the localization system. |
|||
|
|||
### Feature Management Modal |
|||
|
|||
The [application startup template](Startup-Templates/Application.md) comes with the [tenant management](Modules/Tenant-Management.md) and the [feature management](Modules/Feature-Management.md) modules pre-installed. |
|||
|
|||
Whenever you define a new feature, it will be available on the **feature management modal**. To open this modal, navigate to the **tenant management page** and select the `Features` action for a tenant (create a new tenant if there is no tenant yet): |
|||
|
|||
 |
|||
|
|||
This action opens a modal to manage the feature values for the selected tenant: |
|||
|
|||
 |
|||
|
|||
So, you can enable, disable and set values for a tenant. These values will be used whenever a user of this tenant uses the application. |
|||
|
|||
See the *Feature Management* section below to learn more about managing the features. |
|||
|
|||
### Child Features |
|||
|
|||
A feature may have child features. This is especially useful if you want to create a feature that is selectable only if another feature was enabled. |
|||
|
|||
**Example: Defining child features** |
|||
|
|||
```csharp |
|||
using FeaturesDemo.Localization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
var reportingFeature = myGroup.AddFeature( |
|||
"MyApp.Reporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("Reporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
reportingFeature.CreateChild( |
|||
"MyApp.PdfReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("PdfReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
reportingFeature.CreateChild( |
|||
"MyApp.ExcelReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("ExcelReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The example above defines a *Reporting* feature with two children: *PDF Reporting* and *Excel Reporting*. |
|||
|
|||
### Changing Features Definitions of a Depended Module |
|||
|
|||
A class deriving from the `FeatureDefinitionProvider` (just like the example above) can also get the existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. |
|||
|
|||
**Example: Manipulate an existing feature definition** |
|||
|
|||
```csharp |
|||
var someGroup = context.GetGroupOrNull("SomeModule"); |
|||
var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature"); |
|||
if (feature != null) |
|||
{ |
|||
feature.Description = ... |
|||
feature.CreateChild(...); |
|||
} |
|||
``` |
|||
|
|||
## Check a Feature in the Client Side |
|||
|
|||
A feature value is available at the client side too, unless you set `IsVisibleToClients` to `false` on the feature definition. The feature values are exposed from the [Application Configuration API](API/Application-Configuration.md) and usable via some services on the UI. |
|||
|
|||
### ASP.NET Core MVC / Razor Pages UI |
|||
|
|||
Use `abp.features` API to get the feature values. |
|||
|
|||
**Example: Get feature values in the JavaScript code** |
|||
|
|||
````js |
|||
var isEnabled = abp.features.values["MyApp.ExcelReporting"] === "true"; |
|||
var count = abp.features.values["MyApp.MaxProductCount"]; |
|||
```` |
|||
|
|||
### Angular UI |
|||
|
|||
See the [features](Features.md) document for the Angular UI. |
|||
|
|||
## Feature Management |
|||
|
|||
Feature management is normally done by an admin user using the feature management modal: |
|||
|
|||
 |
|||
|
|||
This modal is available on the related entities, like tenants in a multi-tenant application. To open it, navigate to the **Tenant Management** page (for a multi-tenant application), click to the **Actions** button left to the Tenant and select the **Features** action. |
|||
|
|||
If you need to manage features by code, inject the `IFeatureManager` service. |
|||
|
|||
**Example: Enable PDF reporting for a tenant** |
|||
|
|||
```csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IFeatureManager _featureManager; |
|||
|
|||
public MyService(IFeatureManager featureManager) |
|||
{ |
|||
_featureManager = featureManager; |
|||
} |
|||
|
|||
public async Task EnablePdfReporting(Guid tenantId) |
|||
{ |
|||
await _featureManager.SetForTenantAsync( |
|||
tenantId, |
|||
"MyApp.PdfReporting", |
|||
true.ToString() |
|||
); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`IFeatureManager` is defined by the Feature Management module. It comes pre-installed with the application startup template. See the [feature management module documentation](Modules/Feature-Management.md) for more information. |
|||
|
|||
## Advanced Topics |
|||
|
|||
### Feature Value Providers |
|||
|
|||
Feature system is extensible. Any class derived from `FeatureValueProvider` (or implements `IFeatureValueProvider`) can contribute to the feature system. A value provider is responsible to **obtain the current value** of a given feature. |
|||
|
|||
Feature value providers are **executed one by one**. If one of them return a non-null value, then this feature value is used and the other providers are not executed. |
|||
|
|||
There are three pre-defined value providers, executed by the given order: |
|||
|
|||
* `TenantFeatureValueProvider` tries to get if the feature value is explicitly set for the **current tenant**. |
|||
* `EditionFeatureValueProvider` tries to get the feature value for the current edition. Edition Id is obtained from the current principal identity (`ICurrentPrincipalAccessor`) with the claim name `editionid` (a constant defined as`AbpClaimTypes.EditionId`). Editions are not implemented for the [tenant management](Modules/Tenant-Management.md) module. You can implement it yourself or consider to use the [SaaS module](https://commercial.abp.io/modules/Volo.Saas) of the ABP Commercial. |
|||
* `DefaultValueFeatureValueProvider` gets the default value of the feature. |
|||
|
|||
You can write your own provider by inheriting the `FeatureValueProvider`. |
|||
|
|||
**Example: Enable all features for a user with "SystemAdmin" as a "User_Type" claim value** |
|||
|
|||
```csharp |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Security.Claims; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class SystemAdminFeatureValueProvider : FeatureValueProvider |
|||
{ |
|||
public override string Name => "SA"; |
|||
|
|||
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; |
|||
|
|||
public SystemAdminFeatureValueProvider( |
|||
IFeatureStore featureStore, |
|||
ICurrentPrincipalAccessor currentPrincipalAccessor) |
|||
: base(featureStore) |
|||
{ |
|||
_currentPrincipalAccessor = currentPrincipalAccessor; |
|||
} |
|||
|
|||
public override Task<string> GetOrNullAsync(FeatureDefinition feature) |
|||
{ |
|||
if (feature.ValueType is ToggleStringValueType && |
|||
_currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin") |
|||
{ |
|||
return Task.FromResult("true"); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
If a provider returns `null`, then the next provider is executed. |
|||
|
|||
Once a provider is defined, it should be added to the `AbpFeatureOptions` as shown below: |
|||
|
|||
```csharp |
|||
Configure<AbpFeatureOptions>(options => |
|||
{ |
|||
options.ValueProviders.Add<SystemAdminFeatureValueProvider>(); |
|||
}); |
|||
``` |
|||
|
|||
Use this code inside the `ConfigureServices` of your [module](Module-Development-Basics.md) class. |
|||
|
|||
### Feature Store |
|||
|
|||
`IFeatureStore` is the only interface that needs to be implemented to read the value of features from a persistence source, generally a database system. The Feature Management module implements it and pre-installed in the application startup template. See the [feature management module documentation](https://docs.abp.io/en/abp/latest/Modules/Feature-Management) for more information |
|||
@ -1,3 +1,5 @@ |
|||
# Feature Management Module |
|||
|
|||
> This module implements the `IFeatureStore` to store and manage feature values in a database. See the [Features System document](../Features.md) to understand the features first. |
|||
|
|||
TODO |
|||
@ -1,3 +1,5 @@ |
|||
# Permission Management Module |
|||
|
|||
This module implements the `IPermissionStore` to store and manage feature values in a database. See the [Authorization document](../Authorization.md) to understand the authorization and permission systems first. |
|||
|
|||
TODO |
|||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,109 @@ |
|||
# Environment |
|||
|
|||
Every application needs some **environment** variables. In Angular world, this is usually managed by `environment.ts`, `environment.prod.ts` and so on. It is the same for ABP as well. |
|||
|
|||
Current `Environment` configuration holds sub config classes as follows: |
|||
|
|||
```typescript |
|||
export interface Environment { |
|||
apis: Apis; |
|||
application: Application; |
|||
oAuthConfig: AuthConfig; |
|||
production: boolean; |
|||
remoteEnv?: RemoteEnv; |
|||
} |
|||
``` |
|||
|
|||
## Apis |
|||
|
|||
```typescript |
|||
export interface Apis { |
|||
[key: string]: ApiConfig; |
|||
default: ApiConfig; |
|||
} |
|||
|
|||
export interface ApiConfig { |
|||
[key: string]: string; |
|||
rootNamespace?: string; |
|||
url: string; |
|||
} |
|||
``` |
|||
|
|||
Api config has to have a default config and it may have some additional ones for different modules. |
|||
I.e. you may want to connect to different Apis for different modules. |
|||
|
|||
Take a look at following example |
|||
|
|||
```json |
|||
{ |
|||
// ... |
|||
"apis": { |
|||
"default": { |
|||
"url": "https://localhost:8080", |
|||
}, |
|||
"AbpIdentity": { |
|||
"url": "https://localhost:9090", |
|||
} |
|||
}, |
|||
// ... |
|||
} |
|||
``` |
|||
|
|||
When an api from `AbpIdentity` is called, the request will be sent to `"https://localhost:9090"`. |
|||
Everything else will be sent to `"https://localhost:8080"` |
|||
|
|||
* `rootNamespace` **(new)** : Root namespace of the related API. e.g. Acme.BookStore |
|||
|
|||
## Application |
|||
|
|||
```typescript |
|||
export interface Application { |
|||
name: string; |
|||
baseUrl?: string; |
|||
logoUrl?: string; |
|||
} |
|||
``` |
|||
|
|||
* `name`: Name of the backend Application. It is also used by `logo.component` if `logoUrl` is not provided. |
|||
* `logoUrl`: Url of the application logo. It is used by `logo.component` |
|||
* `baseUrl`: [For detailed information](./Multi-Tenancy.md#domain-tenant-resolver) |
|||
|
|||
|
|||
## AuthConfig |
|||
|
|||
For authentication, we use angular-oauth2-oidc. Please check their [docs](https://github.com/manfredsteyer/angular-oauth2-oidc) out |
|||
|
|||
## RemoteEnvironment |
|||
|
|||
Some applications need to integrate an existing config into the `environment` used throughout the application. |
|||
Abp Framework supports this out of box. |
|||
|
|||
To integrate an existing config json into the `environment`, you need to set `remoteEnv` |
|||
|
|||
```typescript |
|||
export type customMergeFn = ( |
|||
localEnv: Partial<Config.Environment>, |
|||
remoteEnv: any, |
|||
) => Config.Environment; |
|||
|
|||
export interface RemoteEnv { |
|||
url: string; |
|||
mergeStrategy: 'deepmerge' | 'overwrite' | customMergeFn; |
|||
method?: string; |
|||
headers?: ABP.Dictionary<string>; |
|||
} |
|||
``` |
|||
|
|||
* `url` *: Required. The url to be used to retrieve environment config |
|||
* `mergeStrategy` *: Required. Defines how the local and the remote `environment` json will be merged |
|||
* `deepmerge`: Both local and remote `environment` json will be merged recursively. If both configs have same nested path, the remote `environment` will be prioritized. |
|||
* `overwrite`: Remote `environment` will be used and local environment will be ignored. |
|||
* `customMergeFn`: You can also provide your own merge function as shown in the example. It will take two parameters, `localEnv: Partial<Config.Environment>` and `remoteEnv` and it needs to return a `Config.Environment` object. |
|||
* `method`: HTTP method to be used when retrieving environment config. Default: `GET` |
|||
* `headers`: If extra headers are needed for the request, it can be set through this field. |
|||
|
|||
|
|||
## What's Next? |
|||
|
|||
* [Service Proxies](./Service-Proxies.md) |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
# Angular UI: Features |
|||
|
|||
> This document explains how to get feature values in an Angular application. See the [Features document](../../Features.md) to learn the feature system. |
|||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 83 KiB |
@ -0,0 +1,41 @@ |
|||
# Upgrading the ABP Framework |
|||
|
|||
This document explains how to upgrade your existing solution when a new ABP Framework version is published. |
|||
|
|||
## ABP UPDATE Command |
|||
|
|||
ABP Framework & module ecosystem consist of hundreds of NuGet and NPM packages. It would be tedious to manually update all these packages to upgrade your application. |
|||
|
|||
[ABP CLI](CLI.md) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: |
|||
|
|||
````bash |
|||
abp update |
|||
```` |
|||
|
|||
Run this command in the terminal while you are in the root folder of your solution. |
|||
|
|||
> If your solution has the Angular UI, you probably have `aspnet-core` and `angular` folders in the solution. Run this command in the parent folder of these two folders. |
|||
|
|||
## The Blog Posts |
|||
|
|||
Sometimes we introduce new features/changes that requires to make changes in the startup template. We already implement the changes in the startup template for new applications. However, in some cases you need to manually make some minor changes in your solution. |
|||
|
|||
Whenever you upgrade your solution, it is strongly suggested to check the [ABP BLOG](https://blog.abp.io/?_ga=2.177248992.411298747.1597771169-1910388957.1594128976) to learn the new features and changes coming with the new version. We regularly publish posts and write these kind of changes. If the changes are not trivial, we also provide migration guides. |
|||
|
|||
## Semantic Versioning & Breaking Changes |
|||
|
|||
We are working hard to keep the semantic versioning rules, so you don't get breaking changes for minor (feature) versions like 3.1, 3.2, 3.3... |
|||
|
|||
However, there are some cases we may introduce breaking changes in feature versions too; |
|||
|
|||
* ABP has many integration packages and sometimes the integrated libraries/frameworks releases major versions and makes breaking changes. In such cases, we carefully check these changes and decide to upgrade the integration package or not. If the impact of the change is relatively small, we update the integration package and explain the change in the release blog post. In such a case, if you've used this integration package, you should follow the instructions explained in the blog post. If the change may break many applications and not easy to fix, we decide to wait this upgrade until the next major ABP Framework release. |
|||
* Sometimes we have to make breaking change to fix a major bug or usage problem. In this case, we think that developer already can't properly use that feature, so no problem to fix it with a breaking change. In such cases, the feature will generally be a rarely used feature. Again, we try to keep the impact minimum. |
|||
|
|||
## Preview Releases & Nightly Builds |
|||
|
|||
Preview releases and nightly builds can help you to try new features and adapt your solution earlier than a new stable release. |
|||
|
|||
* [Preview releases](Previews.md) are typically published ~2 weeks before a minor (feature) version (our minor version development cycle is about ~4 weeks). |
|||
* [Nightly builds](Nightly-Builds.md) are published in every night (except weekends) from the development branch. That means you can try the previous day's development. |
|||
|
|||
Refer to the their documents to learn details about these kind of releases. |
|||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,163 @@ |
|||
# 分布式事件总线Kafka集成 |
|||
|
|||
> 本文解释了**如何配置[Kafka](https://kafka.apache.org/)**做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统. |
|||
|
|||
## 安装 |
|||
|
|||
使用ABP CLI添加[Volo.Abp.EventBus.Kafka[Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka)NuGet包到你的项目: |
|||
|
|||
* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI),如果你还没有安装. |
|||
* 在你想要安装 `Volo.Abp.EventBus.Kafka` 包的 `.csproj` 文件目录打开命令行(终端). |
|||
* 运行 `abp add-package Volo.Abp.EventBus.Kafka` 命令. |
|||
|
|||
如果你想要手动安装,安装[Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet 包到你的项目然后添加 `[DependsOn(typeof(AbpEventBusKafkaModule))]` 到你的项目[模块](Module-Development-Basics.md)类. |
|||
|
|||
## 配置 |
|||
|
|||
可以使用配置使用标准的[配置系统](Configuration.md),如 `appsettings.json` 文件,或[选项](Options.md)类. |
|||
|
|||
### `appsettings.json` 文件配置 |
|||
|
|||
这是配置Kafka设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持的](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量). |
|||
|
|||
**示例:最小化配置与默认配置连接到本地的Kafka服务器** |
|||
|
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `MyGroupId` 是应用程序的名称,用于Kafka的**GroupId**. |
|||
* `MyTopicName` 是**topic名称**. |
|||
|
|||
参阅[Kafka文档](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.html)更好的了解这些选项. |
|||
|
|||
#### 连接 |
|||
|
|||
如果需要连接到本地主机以外的另一台服务器,需要配置连接属性. |
|||
|
|||
**示例: 指定主机名 (如IP地址)** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
允许定义多个连接. 在这种情况下,你可以指定用于事件总线的连接. |
|||
|
|||
**示例: 声明两个连接并将其中一个用于事件总线** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092" |
|||
}, |
|||
"SecondConnection": { |
|||
"BootstrapServers": "321.321.321.321:9092" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"GroupId": "MyGroupId", |
|||
"TopicName": "MyTopicName", |
|||
"ConnectionName": "SecondConnection" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
这允许你可以在你的应用程序使用多个Kafka服务器,但将其中一个做为事件总线. |
|||
|
|||
你可以使用任何[ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html)属性作为连接属性. |
|||
|
|||
**示例: 指定socket超时时间** |
|||
|
|||
````json |
|||
{ |
|||
"Kafka": { |
|||
"Connections": { |
|||
"Default": { |
|||
"BootstrapServers": "123.123.123.123:9092", |
|||
"SocketTimeoutMs": 60000 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### 选项类 |
|||
|
|||
`AbpKafkaOptions` 和 `AbpKafkaEventBusOptions` 类用于配置Kafka的连接字符串和事件总线选项. |
|||
|
|||
你可以在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法配置选项. |
|||
|
|||
**示例: 配置连接** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.Connections.Default.BootstrapServers = "123.123.123.123:9092"; |
|||
options.Connections.Default.SaslUsername = "user"; |
|||
options.Connections.Default.SaslPassword = "pwd"; |
|||
}); |
|||
```` |
|||
|
|||
**示例: 配置 consumer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureConsumer = config => |
|||
{ |
|||
config.GroupId = "MyGroupId"; |
|||
config.EnableAutoCommit = false; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**示例: 配置 producer config** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureProducer = config => |
|||
{ |
|||
config.MessageTimeoutMs = 6000; |
|||
config.Acks = Acks.All; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
**示例: 配置 topic specification** |
|||
|
|||
````csharp |
|||
Configure<AbpKafkaOptions>(options => |
|||
{ |
|||
options.ConfigureTopic = specification => |
|||
{ |
|||
specification.ReplicationFactor = 3; |
|||
specification.NumPartitions = 3; |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值. |
|||
@ -1,3 +0,0 @@ |
|||
## 在控制台应用中使用ABP |
|||
|
|||
ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息. |
|||
@ -0,0 +1,3 @@ |
|||
# Global Features |
|||
|
|||
TODO... |
|||
@ -1,9 +0,0 @@ |
|||
# "如何" 指南 |
|||
|
|||
本部分包含一些常见问题的 "如何" 指南. 尽管其中是一些常见的开发任务和ABP并不直接相关,但我们认为有一些具体的示例可以直接与基于ABP的应用程序一起使用. |
|||
|
|||
## Authentication |
|||
|
|||
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md) |
|||
* [如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证](Azure-Active-Directory-Authentication-MVC.md) |
|||
* [如何为ABP应用程序定制SignIn Manager](Customize-SignIn-Manager.md) |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -1,5 +1,28 @@ |
|||
# 身份管理模块 |
|||
|
|||
身份模块基于Microsoft Identity 库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. |
|||
身份模块基于Microsoft Identity库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. |
|||
|
|||
参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. |
|||
参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. |
|||
|
|||
## Identity安全日志 |
|||
|
|||
安全日志可以记录账户的一些重要的操作或者改动, 你可以在在一些功能中保存安全日志. |
|||
|
|||
你可以注入和使用 `IdentitySecurityLogManager` 或 `ISecurityLogManager` 来保存安全日志. 默认它会创建一个安全日志对象并填充常用的值. 如 `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`等等. 当然你可以自定义这些值. |
|||
|
|||
```cs |
|||
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() |
|||
{ |
|||
Identity = "IdentityServer"; |
|||
Action = "ChangePassword"; |
|||
}); |
|||
``` |
|||
|
|||
通过配置 `AbpSecurityLogOptions` 来提供应用程序的名称或者禁用安全日志功能. 默认是**启用**状态. |
|||
|
|||
```cs |
|||
Configure<AbpSecurityLogOptions>(options => |
|||
{ |
|||
options.ApplicationName = "AbpSecurityTest"; |
|||
}); |
|||
``` |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
# Module Entity Extensions |
|||
|
|||
参阅 https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions (文档会在近期完成). |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||