Browse Source

Merge branch 'dev' into liangshiwei/mongodb-transaction

pull/5174/head
liangshiwei 5 years ago
parent
commit
52bbd04b19
  1. 39
      .github/workflows/angular.yml
  2. 2
      .github/workflows/build-and-test.yml
  3. 0
      LICENSE.md
  4. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  5. 1
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  6. 25
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  7. 9
      common.props
  8. 2
      common.test.props
  9. 18
      docs/en/Authorization.md
  10. 62
      docs/en/Blog-Posts/2020-09-03 v3_1_Release_Stable/POST.md
  11. 431
      docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md
  12. BIN
      docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/swagger-book-list.png
  13. 76
      docs/en/CLI.md
  14. 31
      docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md
  15. 18
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/Using-DevExtreme-Components-With-The-ABP-Framework.md
  16. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/both-example-result.png
  17. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-app-contract.png
  18. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-application.png
  19. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-final.png
  20. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-web.png
  21. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devexp-result.gif
  22. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devextreme-js.png
  23. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/gulp.png
  24. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/initial-project.png
  25. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-app-contract.png
  26. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-application.png
  27. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-final.png
  28. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-web.png
  29. 0
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/wwwroot-lib.png
  30. 81
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md
  31. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-simple-property.jpg
  32. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-user-navigation.jpg
  33. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-appuserdto.jpg
  34. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-mapping.jpg
  35. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-note-entity.jpg
  36. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/final-page.jpg
  37. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal.jpg
  38. BIN
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal2.jpg
  39. 2
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  40. 14
      docs/en/Data-Filtering.md
  41. 167
      docs/en/Distributed-Event-Bus-Kafka-Integration.md
  42. 1
      docs/en/Distributed-Event-Bus.md
  43. 447
      docs/en/Features.md
  44. 16
      docs/en/Index.md
  45. 2
      docs/en/Modules/Feature-Management.md
  46. 23
      docs/en/Modules/Identity.md
  47. 2
      docs/en/Modules/Permission-Management.md
  48. 3
      docs/en/Multi-Tenancy.md
  49. 153
      docs/en/Tutorials/Part-10.md
  50. 13
      docs/en/Tutorials/Part-2.md
  51. 58
      docs/en/Tutorials/Part-3.md
  52. 19
      docs/en/Tutorials/Part-9.md
  53. BIN
      docs/en/Tutorials/images/bookstore-angular-author-selection.png
  54. BIN
      docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png
  55. BIN
      docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png
  56. BIN
      docs/en/Tutorials/images/generated-proxies-3.png
  57. 115
      docs/en/UI/Angular/Component-Replacement.md
  58. 109
      docs/en/UI/Angular/Environment.md
  59. 3
      docs/en/UI/Angular/Features.md
  60. 2
      docs/en/UI/Angular/Migration-Guide-v3.md
  61. 12
      docs/en/UI/Angular/Permission-Management-Component-Replacement.md
  62. 123
      docs/en/UI/Angular/Service-Proxies.md
  63. BIN
      docs/en/UI/Angular/images/generated-files-via-generate-proxy.png
  64. 41
      docs/en/Upgrading.md
  65. 20
      docs/en/docs-nav.json
  66. BIN
      docs/en/images/features-action.png
  67. BIN
      docs/en/images/features-modal.png
  68. 9
      docs/zh-Hans/Authentication/Social-External-Logins.md
  69. 191
      docs/zh-Hans/CLI.md
  70. 28
      docs/zh-Hans/Contribution/Index.md
  71. 5
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  72. 2
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  73. 14
      docs/zh-Hans/Data-Filtering.md
  74. 163
      docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md
  75. 1
      docs/zh-Hans/Distributed-Event-Bus.md
  76. 6
      docs/zh-Hans/Entities.md
  77. 5
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  78. 5
      docs/zh-Hans/Entity-Framework-Core.md
  79. 3
      docs/zh-Hans/Getting-Started-Console-Application.md
  80. 3
      docs/zh-Hans/Global-Features.md
  81. 9
      docs/zh-Hans/How-To/Index.md
  82. 2
      docs/zh-Hans/Local-Event-Bus.md
  83. 1
      docs/zh-Hans/MailKit.md
  84. 2
      docs/zh-Hans/Module-Development-Basics.md
  85. 27
      docs/zh-Hans/Modules/Identity.md
  86. 3
      docs/zh-Hans/Moodule-Entity-Extensions.md
  87. 32
      docs/zh-Hans/Nightly-Builds.md
  88. 1
      docs/zh-Hans/Previews.md
  89. 6
      docs/zh-Hans/Repositories.md
  90. 27
      docs/zh-Hans/Samples/Index.md
  91. 14
      docs/zh-Hans/Startup-Templates/Application.md
  92. 1
      docs/zh-Hans/UI/Angular/Environment.md
  93. 2
      docs/zh-Hans/UI/Angular/Migration-Guide-v3.md
  94. 1
      docs/zh-Hans/UI/Angular/Multi-Tenancy.md
  95. 4
      docs/zh-Hans/UI/Angular/Permission-Management.md
  96. 8
      docs/zh-Hans/UI/Angular/Toaster-Service.md
  97. 2
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  98. 1
      docs/zh-Hans/Upgrading.md
  99. 116
      docs/zh-Hans/Virtual-File-System.md
  100. 56
      docs/zh-Hans/docs-nav.json

39
.github/workflows/angular.yml

@ -9,8 +9,41 @@ jobs:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
- uses: actions/cache@v2
with:
node-version: '12.x'
- run: yarn && yarn ci
path: 'npm/ng-packs/node_modules'
key: ${{ runner.os }}-${{ hashFiles('npm/ng-packs/yarn.lock') }}
- uses: actions/cache@v2
with:
path: 'templates/app/angular/node_modules'
key: ${{ runner.os }}-${{ hashFiles('templates/app/angular/yarn.lock') }}
- name: Install packages
run: yarn install
working-directory: npm/ng-packs
- name: Run lint
run: yarn ng lint
working-directory: npm/ng-packs
- name: Run prepare workspace
run: yarn prepare:workspace
working-directory: npm/ng-packs
- name: Run test
run: yarn ci:test
working-directory: npm/ng-packs
- name: Build dev-app
run: yarn build --prod
working-directory: npm/ng-packs
- name: Install packages of app template
run: yarn install
working-directory: templates/app/angular
- name: Build app template
run: yarn build --prod
working-directory: templates/app/angular

2
.github/workflows/build-and-test.yml

@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
with:
dotnet-version: 3.1.100
dotnet-version: 3.1.102
- name: Build All
run: .\build-all.ps1

0
LICENSE → LICENSE.md

5
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -191,6 +191,9 @@
"Enum:Status:2": "Approved",
"Summary": "Summary",
"AuthorName": "Author name",
"CoverImage": "Cover Image"
"CoverImage": "Cover Image",
"RemoveCacheConfirmationMessage": "Are you sure you remove the cache for \"{0}\" article?",
"SuccessfullyRemoved": "Successfully cleared",
"RemoveCache": "Remove Cache"
}
}

1
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -26,6 +26,7 @@
"ContributionGuide": "Contribution Guide",
"Blog": "Blog",
"Commercial": "Commercial",
"MyAccount": "My account",
"SeeDocuments": "See Documents"
}
}

25
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -37,7 +37,7 @@
"CreateArticleTitleInfo": "Title of the article to be shown on the article list.",
"CreateArticleUrlInfo": "Original GitHub/External URL of the article.",
"CreateArticleSummaryInfo": "A short summary of the article to be shown on the article list.",
"CreateArticleCoverInfo": "For creating an effective article, add a cover photo. Only 16:9 aspect ratio pictures will be accepted!",
"CreateArticleCoverInfo": "For creating an effective article, add a cover photo. Upload 16:9 aspect ratio pictures for the best view.",
"ThisExtensionIsNotAllowed": "This extension is not allowed.",
"TheFileIsTooLarge": "The file is too large.",
"GoToTheArticle": "Go to the Article",
@ -61,10 +61,27 @@
"Oops": "Oops!",
"CreateArticleSuccessMessage": "The Article has been successfully submitted. It will be published after a review from the site admin.",
"ChooseCoverImage": "Choose a cover image...",
"PictureUploadedIsNotInExpectedAspectRatio": "The picture you uploaded is not in 16:9 aspect ratio!",
"HeightAndWidthMustNotExceed": "Height and Width must not exceed 1920*1080.",
"CoverImage": "Cover Image",
"ShareYourExperiencesWithTheABPFramework": "Share your experiences with the ABP Framework!",
"Optional": "Optional"
"Optional": "Optional",
"UpdateUserWebSiteInfo": "Example: https://johndoe.com",
"UpdateUserTwitterInfo": "Example: johndoe",
"UpdateUserGithubInfo": "Example: johndoe",
"UpdateUserLinkedinInfo": "Example: https://www.linkedin.com/...",
"UpdateUserCompanyInfo": "Example: Volosoft",
"UpdateUserJobTitleInfo": "Example: Software Developer",
"UserName": "UserName",
"Company": "Company",
"PersonalWebsite": "Personal Website",
"RegistrationDate": "Registration Date",
"Social": "Social",
"Biography": "Biography",
"HasNoPublishedArticlesYet": "has no published articles yet",
"Author": "Author",
"LatestGithubAnnouncements": "Latest Github Announcements",
"SeeAllAnnouncements": "See All Announcements",
"LatestBlogPost": "Latest Blog Post",
"Edit": "Edit",
"ProfileImageChange": "Change the profile image"
}
}

9
common.props

@ -1,13 +1,14 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>3.1.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<Version>3.2.0</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/abpframework/abp/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/abpframework/abp/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" />

2
common.test.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>

18
docs/en/Authorization.md

@ -80,6 +80,8 @@ namespace Acme.BookStore.Permissions
> ABP automatically discovers this class. No additional configuration required!
> You typically define this class inside the `Application.Contracts` project of your [application](Startup-Templates/Application.md). The startup template already comes with an empty class named *YourProjectNamePermissionDefinitionProvider* that you can start with.
In the `Define` method, you first need to add a **permission group** or get an existing group then add **permissions** to this group.
When you define a permission, it becomes usable in the ASP.NET Core authorization system as a **policy** name. It also becomes visible in the UI. See permissions dialog for a role:
@ -276,14 +278,24 @@ public async Task CreateAsync(CreateAuthorDto input)
> Tip: Prefer to use the `Authorize` attribute wherever possible, since it is declarative & simple. Use `IAuthorizationService` if you need to conditionally check a permission and run a business code based on the permission check.
### Check a Permission in JavaScript
## Check a Permission in JavaScript
You may need to check a policy/permission on the client side.
### MVC UI
You may need to check a policy/permission on the client side. For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API. Example:
For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API.
**Example: Check if a given permission has been granted for the current user**
```js
abp.auth.isGranted('MyPermissionName');
```
### Angular UI
See the [permission management document](UI/Angular/Permission-Management.md) for the Angular UI.
## Permission Management
Permission management is normally done by an admin user using the permission management modal:
@ -374,7 +386,7 @@ Configure<AbpPermissionOptions>(options =>
### Permission Store
`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. Permission management module implements it. See the [permission management module documentation](Modules/Permission-Management.md) for more information
`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. The Permission Management module implements it and pre-installed in the application startup template. See the [permission management module documentation](Modules/Permission-Management.md) for more information
### AlwaysAllowAuthorizationService

62
docs/en/Blog-Posts/2020-09-03 v3_1_Release_Stable/POST.md

@ -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.

431
docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md

@ -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:
![swagger-book-list](swagger-book-list.png)
### 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.

BIN
docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/swagger-book-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

76
docs/en/CLI.md

@ -16,6 +16,12 @@ To update an existing installation:
dotnet tool update -g Volo.Abp.Cli
````
## Global Options
While each command may have a set of options, there are some global options those can be used with any command;
* `--skip-cli-version-check`: Skips to check the latest version of the ABP CLI. If you don't specify, it will check the latest version and shows a warning message if there is a newer version of the ABP CLI.
## Commands
Here, the list of all available commands before explaining their details:
@ -25,7 +31,9 @@ Here, the list of all available commands before explaining their details:
* **`update`**: Automatically updates all ABP related NuGet and NPM packages in a solution.
* **`add-package`**: Adds an ABP package to a project.
* **`add-module`**: Adds a [multi-package application module](https://docs.abp.io/en/abp/latest/Modules/Index) to a solution.
* **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints on the server.
* **`get-source`**: Downloads the source code of a module.
* **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints.
* **`remove-proxy`**: Removes previously generated client side proxies.
* **`switch-to-preview`**: Switches to the latest preview version of the ABP Framework.
* **`switch-to-nightly`**: Switches to the latest [nightly builds](Nightly-Builds.md) of the ABP related packages on a solution.
* **`switch-to-stable`**: Switches to the latest stable versions of the ABP related packages on a solution.
@ -78,6 +86,8 @@ abp new Acme.BookStore
* `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios.
* `angular`: Angular. There are some additional options for this template:
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `blazor`: Blazor. There are some additional options for this template:
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `none`: Without UI. There are some additional options for this template:
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `--mobile` or `-m`: Specifies the mobile application framework. If not specified, no mobile application will be created. Available options:
@ -171,28 +181,72 @@ abp add-module Volo.Blogging
* `-sp` or `--startup-project`: Relative path to the project folder of the startup project. Default value is the current folder.
* `--with-source-code`: Add source code of the module instead of NuGet/NPM packages.
### get-source
Downloads the source code of a module to your computer.
Usage
````bash
abp get-source <module-name> [options]
````
Example:
```bash
abp get-source Volo.Blogging
abp get-source Volo.Blogging --local-framework-ref --abp-path D:\GitHub\abp
```
#### Options
* `--output-folder` or `-o`: Specifies the directory that source code will be downloaded in. If not specified, current directory is used.
* `--version` or `-v`: Specifies the version of the source code that will be downloaded. If not specified, latest version is used.
* `--preview`: If no version option is specified, this option specifies if latest [preview version](Previews.md) will be used instead of latest stable version.
* `-- local-framework-ref --abp-path`: Path of [ABP Framework GitHub repository](https://github.com/abpframework/abp) in your computer. This will be used for converting project references to your local system. If this is not specified, project references will be converted to NuGet references.
### generate-proxy
Generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running `generate-proxy` command, your host must be up and running.
Generates Angular service proxies for your HTTP APIs to make easy to consume your services from the client side. Your host (server) application must be up and running before running this command.
Usage:
````bash
abp generate-proxy [options]
abp generate-proxy
````
#### Options
* `--apiUrl` or `-a`: Specifies the root URL of the HTTP API. The default value is being retrieved from the `environment.ts` file for the Angular application. Make sure your host is up and running before running `abp generate-proxy`.
* `--ui` or `-u`: Specifies the UI framework. Default value is `angular` and it is the only UI framework supported for now. Creates TypeScript code.
* `--module` or `-m`: Specifies the module name. Default module name is `app`, which indicates your own application (you typically want this since every module is responsible to maintain its own client proxies). Set `all` for to generate proxies for all the modules.
* `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`.
* `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`.
* `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`.
* `--target` or `-t`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`.
* `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options).
> See the [Angular Service Proxies document](UI/Angular/Service-Proxies.md) for more.
Example usage with the options:
### remove-proxy
Removes previously generated proxy code from the Angular application. Your host (server) application must be up and running before running this command.
This can be especially useful when you generate proxies for multiple modules before and need to remove one of them later.
Usage:
````bash
abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
abp remove-proxy
````
#### Options
* `--module` or `-m`: Specifies the name of the backend module you wish to remove proxies for. Default value: `app`.
* `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`.
* `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`.
* `--target` or `-t`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`.
* `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options).
> See the [Angular Service Proxies document](UI/Angular/Service-Proxies.md) for more.
### switch-to-preview
@ -206,7 +260,7 @@ abp switch-to-preview [options]
#### Options
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
* `--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### switch-to-nightly
@ -221,7 +275,7 @@ abp switch-to-nightly [options]
#### Options
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
* `--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### switch-to-stable
@ -234,7 +288,7 @@ abp switch-to-stable [options]
````
#### Options
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
* `--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### translate

31
docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md

@ -2,15 +2,17 @@
## Introduction
To allow a user login with a magic URL, you need to implement a custom token provider. In this tutorial, we will show you how to add a custom token provider to authenticate a user with a link, instead of entering a password.
In this tutorial, we will show you how to add a custom token provider to authenticate a user with a link, instead of entering the password.
This can be useful especially if you want to make someone login to the application with your user, without sharing your secret password. The generated link will be for a single use.
### Source Code
The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication).
The completed sample is available on [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication).
## Creating the Solution
Before starting to the development, create a new solution named `PasswordlessAuthentication` and run it by following the [getting started tutorial](https://docs.abp.io/en/abp/latest/Getting-Started?UI=MVC&DB=EF&Tiered=No).
Before starting the development, create a new solution named `PasswordlessAuthentication` and run it by following the [getting started tutorial](https://docs.abp.io/en/abp/latest/Getting-Started?UI=MVC&DB=EF&Tiered=No).
## Step-1
@ -155,36 +157,33 @@ Open your **Index.cshtml** and set the content as below. We added a form that po
```html
@page
@inject IHtmlLocalizer<PasswordlessAuthenticationResource> L
@using Microsoft.AspNetCore.Mvc.Localization
@using PasswordlessAuthentication.Localization
@using PasswordlessAuthentication.Web.Menus
@using MyBookStore.Web.Menus
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@model PasswordlessAuthentication.Web.Pages.IndexModel
@model MyBookStore.Web.Pages.IndexModel
@using Microsoft.AspNetCore.Mvc.Localization
@using MyBookStore.Localization
@inject IHtmlLocalizer<MyBookStoreResource> L
@{
ViewBag.PageTitle = "Home";
}
@inject IPageLayout PageLayout;
@inject IPageLayout PageLayout
@{
PageLayout.Content.Title = L["Home"].Value;
PageLayout.Content.BreadCrumb.Add(L["Menu:Home"].Value);
PageLayout.Content.MenuItemName = PasswordlessAuthenticationMenus.Home;
PageLayout.Content.MenuItemName = MyBookStoreMenus.Home;
}
<abp-card>
<abp-card-body>
<form asp-page-handler="GeneratePasswordlessToken" method="post">
<abp-button button-type="Dark" type="submit">Generate passwordless token link</abp-button>
@if (Model.PasswordlessLoginUrl != null)
{
<abp-card class="mt-3 p-3">
[@Model.PasswordlessLoginUrl](/en/commercial/latest/how-to/@Model.PasswordlessLoginUrl)
<a href="@Model.PasswordlessLoginUrl">@Model.PasswordlessLoginUrl</a>
</abp-card>
}
</form>
</abp-card-body>
</abp-card>
@ -273,4 +272,4 @@ That's all! We created a passwordless login with 7 steps.
## Source Code
The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication).
The completed sample is available on [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication).

18
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/Using-DevExtreme-In-ABP-Based-Application.md → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/Using-DevExtreme-Components-With-The-ABP-Framework.md

@ -1,9 +1,11 @@
## Using DevExtreme with ABP Based Applications
## Using DevExtreme Components With the ABP Framework
Hi, in this step by step article, I will show you how to integrate DevExtreme components into ABP Framework based applications.
Hi, in this step by step article, I will show you how to integrate [DevExtreme](https://js.devexpress.com/) components into ABP Framework-based applications.
![both-example-result](both-example-result.png)
*(A screenshot from the example application developed in this article)*
## Create the Project
ABP Framework offers startup templates to get into the business faster. We can download a new startup template using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
@ -151,7 +153,7 @@ namespace DevExtremeSample.Web.Bundling
}
```
As you see, the `DevExtremeScriptContributor` is depends on `JQueryScriptContributor` which adds JQuery related files before the DevExpress packages (see the [bundling system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification) for details).
As you see, the `DevExtremeScriptContributor` depends on `JQueryScriptContributor` which adds JQuery related files before the DevExpress packages (see the [bundling system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification) for details).
#### Create DevExtremeJsViewComponent
@ -184,7 +186,7 @@ namespace DevExtremeSample.Web.Components.DevExtremeJs
<abp-script type="typeof(DevExtremeScriptContributor)" />
```
Your final Web project should be like as the following:
Your final Web project should be like the following:
![devextreme-js](devextreme-js.png)
@ -204,7 +206,7 @@ Configure<AbpLayoutHookOptions>(options =>
#### Known Issue: Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object.
> This issue does exists in the ABP Framework v3.0 and earlier versions. If you are using ABP Framework v3.1 or a latter version, you can skip this section.
> This issue does exist in the ABP Framework v3.0 and earlier versions. If you are using ABP Framework v3.1 or a later version, you can skip this section.
When you run your `*.Web` project, you will see an exception (`Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object.`) at your console.
@ -265,7 +267,7 @@ I've used an in-memory list to store data for this example, instead of a real da
#### JSON Serialization
You can see some `JsonProperty` attributes on the DTO properties. I uses these attributes because DevExtreme example expects `PascalCase` property names in the serialized JSON that is sent to the client. But ABP Framework & ASP.NET Core conventionally uses `camelCase` property names on JSON serialization. Adding these `JsonProperty` attributes ensures that the related properties are serialized as `PascalCase`.
You can see some `JsonProperty` attributes on the DTO properties. I use these attributes because DevExtreme example expects `PascalCase` property names in the serialized JSON that is sent to the client. But ABP Framework & ASP.NET Core conventionally uses `camelCase` property names on JSON serialization. Adding these `JsonProperty` attributes ensures that the related properties are serialized as `PascalCase`.
#### DevExtreme Components vs Application Service Methods
@ -300,3 +302,7 @@ Html.DevExtreme().DataGrid<Order>()
.Key("OrderID")
)
```
## Conclusion
In this article, I've explained how to use [DevExtreme](https://js.devexpress.com/) components in your application. ABP Framework is designed so that it can work with any UI library/framework.

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/both-example-result.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/both-example-result.png

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/data-grid-app-contract.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-app-contract.png

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/data-grid-application.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-application.png

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/data-grid-final.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-final.png

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/data-grid-web.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-web.png

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/devexp-result.gif → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devexp-result.gif

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/devextreme-js.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devextreme-js.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/gulp.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/gulp.png

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/initial-project.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/initial-project.png

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/tree-list-app-contract.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-app-contract.png

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/tree-list-application.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-application.png

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/tree-list-final.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-final.png

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/tree-list-web.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-web.png

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

0
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/wwwroot-lib.png → docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/wwwroot-lib.png

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

81
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md

@ -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.
![create-note-entity](create-note-entity.jpg)
Then add a string property called `Title`, as an example property.
![add-simple-property](add-simple-property.jpg)
### 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`.
![create-appuserdto](create-appuserdto.jpg)
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);
```
![create-mapping](create-mapping.jpg)
> 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.
![add-user-navigation](add-user-navigation.jpg)
### 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.
![final-page](final-page.jpg)
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.
![ui-pick-type-modal](ui-pick-type-modal.jpg)
After successful code generation, you'll see the the user can be picked from user table.
![ui-pick-type-modal2](ui-pick-type-modal2.jpg)
## 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!

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-simple-property.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-user-navigation.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-appuserdto.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-mapping.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-note-entity.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/final-page.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

BIN
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

2
docs/en/Customizing-Application-Modules-Overriding-Services.md

@ -61,7 +61,7 @@ In most cases, you will want to change one or a few methods of the current imple
````csharp
//[RemoteService(IsEnabled = false)] // If you use dynamic controller feature you can disable remote service. Prevent creating duplicate controller for the application service.
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))]
public class MyIdentityUserAppService : IdentityUserAppService
{
//...

14
docs/en/Data-Filtering.md

@ -151,11 +151,21 @@ public class Book : AggregateRoot<Guid>, IIsActive
ABP uses [EF Core's Global Query Filters](https://docs.microsoft.com/en-us/ef/core/querying/filters) system for the [EF Core Integration](Entity-Framework-Core.md). So, it is well integrated to EF Core and works as expected even if you directly work with `DbContext`.
Best way to implement a custom filter is to override `CreateFilterExpression` method for your `DbContext`. Example:
Best way to implement a custom filter is to override `ShouldFilterEntity` and `CreateFilterExpression` method for your `DbContext`. Example:
````csharp
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
{
var expression = base.CreateFilterExpression<TEntity>();
@ -174,7 +184,7 @@ protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntit
````
* Added a `IsActiveFilterEnabled` property to check if `IIsActive` is enabled or not. It internally uses the `IDataFilter` service introduced before.
* Overrided the `CreateFilterExpression` method, checked if given entity implements the `IIsActive` interface and combines the expressions if necessary.
* Overrided the `ShouldFilterEntity` and `CreateFilterExpression` methods, checked if given entity implements the `IIsActive` interface and combines the expressions if necessary.
### MongoDB

167
docs/en/Distributed-Event-Bus-Kafka-Integration.md

@ -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
docs/en/Distributed-Event-Bus.md

@ -8,6 +8,7 @@ Distributed event bus system provides an **abstraction** that can be implemented
* `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider.
* `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it.
* `KafkaDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://kafka.apache.org/). See the [Kafka integration document](Distributed-Event-Bus-Kafka-Integration.md) to learn how to configure it.
Using a local event bus as default has a few important advantages. The most important one is that: It allows you to write your code compatible to distributed architecture. You can write a monolithic application now that can be split into microservices later. It is a good practice to communicate between bounded contexts (or between application modules) via distributed events instead of local events.

447
docs/en/Features.md

@ -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):
![features-action](images/features-action.png)
This action opens a modal to manage the feature values for the selected tenant:
![features-modal](images/features-modal.png)
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:
![features-modal](images/features-modal.png)
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

16
docs/en/Index.md

@ -6,9 +6,21 @@ Explore the navigation menu to deep dive in the documentation.
## Getting Started
The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) tutorial.
The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) guide.
Then you can continue with the [web application development tutorial](Tutorials/Part-1.md).
## Tutorials / Articles
### Web Application Development
[Web application development tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack application using the ABP Framework.
### ABP Community Articles
See also the [ABP Community](https://community.abp.io/) articles.
## Samples
See the [sample projects](Samples/Index.md) built with the ABP Framework.
## Source Code

2
docs/en/Modules/Feature-Management.md

@ -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

23
docs/en/Modules/Identity.md

@ -4,3 +4,26 @@ Identity module is used to manage [organization units](Organization-Units.md), r
See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...
## Identity Security Log
The security log can record some important operations or changes about your account. You can save the security log if needed.
You can inject and use `IdentitySecurityLogManager` or `ISecurityLogManager` to write security logs. It will create a log object by default and fill in some common values, such as `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`, etc. Of course, you can override them.
```cs
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = "IdentityServer";
Action = "ChangePassword";
});
```
Configure `AbpSecurityLogOptions` to provide the application name for the log or disable this feature. **Enabled** by default.
```cs
Configure<AbpSecurityLogOptions>(options =>
{
options.ApplicationName = "AbpSecurityTest";
});
```

2
docs/en/Modules/Permission-Management.md

@ -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

3
docs/en/Multi-Tenancy.md

@ -369,3 +369,6 @@ options.AddDomainTenantResolver("{0}.mydomain.com");
options.AddDomainTenantResolver("{0}.com");
````
## See Also
* [Features](Features.md)

153
docs/en/Tutorials/Part-10.md

@ -376,6 +376,8 @@ namespace Acme.BookStore.Books
public override async Task<BookDto> GetAsync(Guid id)
{
await CheckGetPolicyAsync();
//Prepare a query to join books and authors
var query = from book in Repository
join author in _authorRepository on book.AuthorId equals author.Id
@ -397,6 +399,8 @@ namespace Acme.BookStore.Books
public override async Task<PagedResultDto<BookDto>>
GetListAsync(PagedAndSortedResultRequestDto input)
{
await CheckGetListPolicyAsync();
//Prepare a query to join books and authors
var query = from book in Repository
join author in _authorRepository on book.AuthorId equals author.Id
@ -495,6 +499,8 @@ namespace Acme.BookStore.Books
public override async Task<BookDto> GetAsync(Guid id)
{
await CheckGetPolicyAsync();
var book = await Repository.GetAsync(id);
var bookDto = ObjectMapper.Map<Book, BookDto>(book);
@ -507,6 +513,8 @@ namespace Acme.BookStore.Books
public override async Task<PagedResultDto<BookDto>>
GetListAsync(PagedAndSortedResultRequestDto input)
{
await CheckGetListPolicyAsync();
//Set a default sorting, if not provided
if (input.Sorting.IsNullOrWhiteSpace())
{
@ -915,6 +923,149 @@ You can run the application and try to create a new book or update an existing b
{{else if UI=="NG"}}
***Angular UI is being prepared...***
### The Book List
Book list page change is trivial. Open the `/src/app/book/book.component.html` and add the following column definition between the `Name` and `Type` columns:
````js
<ngx-datatable-column
[name]="'::Author' | abpLocalization"
prop="authorName"
></ngx-datatable-column>
````
When you run the application, you can see the *Author* column on the table:
![bookstore-books-with-authorname-angular](images/bookstore-books-with-authorname-angular.png)
### Create/Edit Forms
The next step is to add an Author selection (dropdown) to the create/edit forms. The final UI will look like the one shown below:
![bookstore-angular-author-selection](images/bookstore-angular-author-selection.png)
Added the Author dropdown as the first element in the form.
Open the `/src/app/book/book.component.ts` and and change the content as shown below:
````js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookService, BookDto, bookTypeOptions, AuthorLookupDto } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.scss'],
providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
form: FormGroup;
selectedBook = {} as BookDto;
authors$: Observable<AuthorLookupDto[]>;
bookTypes = bookTypeOptions;
isModalOpen = false;
constructor(
public readonly list: ListService,
private bookService: BookService,
private fb: FormBuilder,
private confirmation: ConfirmationService
) {
this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items));
}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
createBook() {
this.selectedBook = {} as BookDto;
this.buildForm();
this.isModalOpen = true;
}
editBook(id: string) {
this.bookService.get(id).subscribe((book) => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
});
}
buildForm() {
this.form = this.fb.group({
authorId: [this.selectedBook.authorId || null, Validators.required],
name: [this.selectedBook.name || null, Validators.required],
type: [this.selectedBook.type || null, Validators.required],
publishDate: [
this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
Validators.required,
],
price: [this.selectedBook.price || null, Validators.required],
});
}
save() {
if (this.form.invalid) {
return;
}
const request = this.selectedBook.id
? this.bookService.update(this.selectedBook.id, this.form.value)
: this.bookService.create(this.form.value);
request.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.bookService.delete(id).subscribe(() => this.list.get());
}
});
}
}
````
* Added imports for the `AuthorLookupDto`, `Observable` and `map`.
* Added `authors$: Observable<AuthorLookupDto[]>;` field after the `selectedBook`.
* Added `this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items));` into the constructor.
* Added ` authorId: [this.selectedBook.authorId || null, Validators.required],` into the `buildForm()` function.
Open the `/src/app/book/book.component.html` and add the following form group just before the book name form group:
````html
<div class="form-group">
<label for="author-id">Author</label><span> * </span>
<select class="form-control" id="author-id" formControlName="authorId">
<option [ngValue]="null">Select author</option>
<option [ngValue]="author.id" *ngFor="let author of authors$ | async">
{%{{{ author.name }}}%}
</option>
</select>
</div>
````
That's all. Just run the application and try to create or edit an author.
{{end}}

13
docs/en/Tutorials/Part-2.md

@ -347,9 +347,9 @@ This is a fully working, server side paged, sorted and localized table of books.
## Install NPM packages
> Notice: This tutorial is based on the ABP Framework v3.0.3+ If your project version is older, then please upgrade your solution. See the [migration guide](../UI/Angular/Migration-Guide-v3.md) if you are upgrading an existing project with v2.x.
> Notice: This tutorial is based on the ABP Framework v3.1.0+ If your project version is older, then please upgrade your solution. See the [migration guide](../UI/Angular/Migration-Guide-v3.md) if you are upgrading an existing project with v2.x.
If you haven't done it before, open a new command line interface (terminal window) and go to your `angular` folder and then run `yarn` command to install NPM packages:
If you haven't done it before, open a new command line interface (terminal window) and go to your `angular` folder and then run `yarn` command to install the NPM packages:
```bash
yarn
@ -473,9 +473,9 @@ Run the following command in the `angular` folder:
abp generate-proxy
```
The generated files looks like below:
This command will create the following files under the `/src/app/proxy/books` folder:
![Generated files](./images/generated-proxies-2.png)
![Generated files](./images/generated-proxies-3.png)
### BookComponent
@ -484,8 +484,7 @@ Open the `/src/app/book/book.component.ts` file and replace the content as below
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto } from './models';
import { BookService } from './services';
import { BookService, BookDto } from '@proxy/books';
@Component({
selector: 'app-book',
@ -499,7 +498,7 @@ export class BookComponent implements OnInit {
constructor(public readonly list: ListService, private bookService: BookService) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;

58
docs/en/Tutorials/Part-3.md

@ -656,8 +656,7 @@ Open `/src/app/book/book.component.ts` and replace the content as below:
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto } from './models';
import { BookService } from './services';
import { BookService, BookDto } from '@proxy/books';
@Component({
selector: 'app-book',
@ -673,7 +672,7 @@ export class BookComponent implements OnInit {
constructor(public readonly list: ListService, private bookService: BookService) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -749,8 +748,8 @@ Open `/src/app/book/book.component.ts` and replace the content as below:
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType } from './models'; // add BookType
import { BookService } from './services';
// import bookTypeOptions from @proxy/books
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this
@Component({
@ -764,12 +763,8 @@ export class BookComponent implements OnInit {
form: FormGroup; // add this line
bookType = BookType; // add this line
// add bookTypes as a list of BookType enum members
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
bookTypes = bookTypeOptions;
isModalOpen = false;
@ -780,7 +775,7 @@ export class BookComponent implements OnInit {
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -808,7 +803,7 @@ export class BookComponent implements OnInit {
return;
}
this.bookService.createByInput(this.form.value).subscribe(() => {
this.bookService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
@ -819,7 +814,6 @@ export class BookComponent implements OnInit {
* Imported `FormGroup`, `FormBuilder` and `Validators` from `@angular/forms`.
* Added `form: FormGroup` property.
* Added `bookType` property so that you can reach `BookType` enum members from template.
* Added `bookTypes` property as a list of `BookType` enum members. That will be used in form options.
* Injected `FormBuilder` into the constructor. [FormBuilder](https://angular.io/api/forms/FormBuilder) provides convenient methods for generating form controls. It reduces the amount of boilerplate needed to build complex forms.
* Added `buildForm` method to the end of the file and executed the `buildForm()` in the `createBook` method.
@ -844,7 +838,7 @@ Open `/src/app/book/book.component.html` and replace `<ng-template #abpBody> </n
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="bookType[type]" *ngFor="let type of bookTypes"> {%{{{ type }}}%}</option>
<option [ngValue]="type.value" *ngFor="let type of bookTypes"> {%{{{ type.key }}}%}</option>
</select>
</div>
@ -910,8 +904,7 @@ Open `/src/app/book/book.component.ts` and replace the content as below:
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType } from './models';
import { BookService } from './services';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
// added this line
@ -931,11 +924,7 @@ export class BookComponent implements OnInit {
form: FormGroup;
bookType = BookType;
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
bookTypes = bookTypeOptions;
isModalOpen = false;
@ -946,7 +935,7 @@ export class BookComponent implements OnInit {
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -972,7 +961,7 @@ export class BookComponent implements OnInit {
return;
}
this.bookService.createByInput(this.form.value).subscribe(() => {
this.bookService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
@ -995,8 +984,7 @@ Open `/src/app/book/book.component.ts` and replace the content as shown below:
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType, CreateUpdateBookDto } from './models';
import { BookService } from './services';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@ -1009,15 +997,11 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
selectedBook = new BookDto(); // declare selectedBook
selectedBook = {} as BookDto; // declare selectedBook
form: FormGroup;
bookType = BookType;
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
bookTypes = bookTypeOptions;
isModalOpen = false;
@ -1028,7 +1012,7 @@ export class BookComponent implements OnInit {
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -1036,14 +1020,14 @@ export class BookComponent implements OnInit {
}
createBook() {
this.selectedBook = new BookDto(); // reset the selected book
this.selectedBook = {} as BookDto; // reset the selected book
this.buildForm();
this.isModalOpen = true;
}
// Add editBook method
editBook(id: string) {
this.bookService.getById(id).subscribe((book) => {
this.bookService.get(id).subscribe((book) => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
@ -1069,8 +1053,8 @@ export class BookComponent implements OnInit {
}
const request = this.selectedBook.id
? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id)
: this.bookService.createByInput(this.form.value);
? this.bookService.update(this.selectedBook.id, this.form.value)
: this.bookService.create(this.form.value);
request.subscribe(() => {
this.isModalOpen = false;
@ -1155,7 +1139,7 @@ constructor(
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.bookService.deleteById(id).subscribe(() => this.list.get());
this.bookService.delete(id).subscribe(() => this.list.get());
}
});
}

19
docs/en/Tutorials/Part-9.md

@ -621,7 +621,7 @@ abp generate-proxy
This command generates the service proxy for the author service and the related model (DTO) classes:
![bookstore-angular-service-proxy-author](images/bookstore-angular-service-proxy-author.png)
![bookstore-angular-service-proxy-author](images/bookstore-angular-service-proxy-author-2.png)
### AuthorComponent
@ -630,8 +630,7 @@ Open the `/src/app/author/author.component.ts` file and replace the content as b
```js
import { Component, OnInit } from '@angular/core';
import { ListService, PagedResultDto } from '@abp/ng.core';
import { AuthorDto } from './models';
import { AuthorService } from './services';
import { AuthorService, AuthorDto } from '@proxy/authors';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
@ -649,7 +648,7 @@ export class AuthorComponent implements OnInit {
form: FormGroup;
selectedAuthor = new AuthorDto();
selectedAuthor = {} as AuthorDto;
constructor(
public readonly list: ListService,
@ -659,7 +658,7 @@ export class AuthorComponent implements OnInit {
) {}
ngOnInit(): void {
const authorStreamCreator = (query) => this.authorService.getListByInput(query);
const authorStreamCreator = (query) => this.authorService.getList(query);
this.list.hookToQuery(authorStreamCreator).subscribe((response) => {
this.author = response;
@ -667,13 +666,13 @@ export class AuthorComponent implements OnInit {
}
createAuthor() {
this.selectedAuthor = new AuthorDto();
this.selectedAuthor = {} as AuthorDto;
this.buildForm();
this.isModalOpen = true;
}
editAuthor(id: string) {
this.authorService.getById(id).subscribe((author) => {
this.authorService.get(id).subscribe((author) => {
this.selectedAuthor = author;
this.buildForm();
this.isModalOpen = true;
@ -697,14 +696,14 @@ export class AuthorComponent implements OnInit {
if (this.selectedAuthor.id) {
this.authorService
.updateByIdAndInput(this.form.value, this.selectedAuthor.id)
.update(this.selectedAuthor.id, this.form.value)
.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
} else {
this.authorService.createByInput(this.form.value).subscribe(() => {
this.authorService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
@ -716,7 +715,7 @@ export class AuthorComponent implements OnInit {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure')
.subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.authorService.deleteById(id).subscribe(() => this.list.get());
this.authorService.delete(id).subscribe(() => this.list.get());
}
});
}

BIN
docs/en/Tutorials/images/bookstore-angular-author-selection.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/Tutorials/images/generated-proxies-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

115
docs/en/UI/Angular/Component-Replacement.md

@ -8,27 +8,22 @@ The reason that you **can replace** but **cannot customize** default ABP compone
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below:
Then, open the `app.component.ts` and execute the `add` method of `ReplaceableComponentsService` to replace your component with an ABP component as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
@Component(/* component metadata */)
export class AppComponent {
constructor(
private store: Store // injected Store
)
{
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
private replaceableComponents: ReplaceableComponentsService, // injected the service
) {
this.replaceableComponents.add({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
});
}
}
```
@ -59,28 +54,24 @@ Add the following code in your layout template (`my-layout.component.html`) wher
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
import { Store } from '@ngxs/store'; // imported Store
import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
@Component(/* component metadata */)
export class AppComponent {
constructor(
private store: Store, // injected Store
private replaceableComponents: ReplaceableComponentsService, // injected the service
) {
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
}),
);
this.replaceableComponents.add({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
});
}
}
```
> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the AddReplaceableComponent action as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of AddReplaceableComponent is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.
> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the `add` method of `ReplaceableComponentsService` as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of the `add` method is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.
### Layout Components
@ -123,26 +114,22 @@ export class LogoComponent {}
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: LogoComponent,
key: eThemeBasicComponents.Logo,
}),
);
});
}
}
```
@ -158,15 +145,13 @@ The final UI looks like below:
Run the following command in `angular` folder to create a new component called `RoutesComponent`.
```bash
yarn ng generate component routes --entryComponent
# You don't need the --entryComponent option in Angular 9
yarn ng generate component routes
```
Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following:
```js
import { ABP, ReplaceableComponents } from '@abp/ng.core';
import { ABP } from '@abp/ng.core';
import {
Component,
HostBinding,
@ -200,6 +185,21 @@ export class RoutesComponent implements AfterViewInit {
}
```
Import the `SharedModule` to the `imports` array of `AppModule`:
```js
// app.module.ts
import { SharedModule } from './shared/shared.module';
@NgModule({
imports: [
//...
SharedModule
]
)}
```
Open the generated `routes.component.html` in `src/app/routes` folder and replace its content with the following:
```html
@ -309,26 +309,22 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: RoutesComponent,
key: eThemeBasicComponents.Routes,
}),
);
});
}
}
```
@ -344,9 +340,7 @@ The final UI looks like below:
Run the following command in `angular` folder to create a new component called `NavItemsComponent`.
```bash
yarn ng generate component nav-items --entryComponent
# You don't need the --entryComponent option in Angular 9
yarn ng generate component nav-items
```
Open the generated `nav-items.component.ts` in `src/app/nav-items` folder and replace the content with the following:
@ -431,6 +425,21 @@ export class NavItemsComponent implements AfterViewInit {
}
```
Import the `SharedModule` to the `imports` array of `AppModule`:
```js
// app.module.ts
import { SharedModule } from './shared/shared.module';
@NgModule({
imports: [
//...
SharedModule
]
)}
```
Open the generated `nav-items.component.html` in `src/app/nav-items` folder and replace the content with the following:
```html
@ -511,26 +520,22 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: NavItemsComponent,
key: eThemeBasicComponents.NavItems,
}),
);
});
}
}
```

109
docs/en/UI/Angular/Environment.md

@ -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)

3
docs/en/UI/Angular/Features.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.

2
docs/en/UI/Angular/Migration-Guide-v3.md

@ -473,5 +473,5 @@ Some interfaces have long been marked as deprecated and now they are removed.
## What's Next?
* [Service Proxies](./Service-Proxies.md)
* [Environment](./Environment.md)

12
docs/en/UI/Angular/Permission-Management-Component-Replacement.md

@ -473,24 +473,20 @@ Open the generated `permission-management.component.html` in `src/app/permission
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core';
import { ReplaceableComponentsService } from '@abp/ng.core';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngxs/store';
import { PermissionManagementComponent } from './permission-management/permission-management.component';
//...
export class AppComponent implements OnInit {
constructor(private store: Store) {} // injected store
constructor(private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
// added dispatching the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: PermissionManagementComponent,
key: ePermissionManagementComponents.PermissionManagement,
})
);
});
}
}
```

123
docs/en/UI/Angular/Service-Proxies.md

@ -4,42 +4,91 @@ It is common to call a REST endpoint in the server from our Angular applications
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced:
* It generates a **big, single** .ts file which has some problems;
* It get **too large** when your application grows.
* It doesn't fit into the **[modular](../../Module-Development-Basics) approach** of the ABP framework.
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually).
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies.
- It generates a **big, single** .ts file which has some problems;
- It get **too large** when your application grows.
- It doesn't fit into the **[modular](../../Module-Development-Basics) approach** of the ABP framework.
- It creates a bit **ugly code**. We want to have a clean code (just like if we write manually).
- It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies.
ABP CLI `generate-proxies` command automatically generates the typescript client proxies by creating folders which separated by module names in the `src/app` folder.
ABP CLI changes that via the `generate-proxy` command. It automatically generates the client proxies in TypeScript. by creating folders which separated by module names in the `src/app` folder.
Run the following command in the **root folder** of the angular application:
```bash
abp generate-proxy
```
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](../../CLI).
The command without any parameters creates proxies only for your own application's services and places them in your default Angular application. There are several parameters you may use to modify this behavior. See the [CLI documentation](../../CLI) for details.
The files generated with the `--module all` option like below:
The generated files will be placed in a folder called `proxy` at the root of target project.
![generated-files-via-generate-proxy](./images/generated-files-via-generate-proxy.png)
Each folder will have models, enums, and services defined at related namespace accompanied by a barrel export, i.e. an `index.ts` file for easier imports.
> The cammand is able to find application/library roots by reading `angular.json` file. Make sure you have either defined your target project as the `defaultProject` or pass the `--target` parameter to the command. This also means that you may have a monorepo workspace.
### Angular 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 your Angular application:
1. 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 -D
```
2. Add `rootNamespace` property to the `/src/environments/environment.ts` in your application project as shown below. `MyCompanyName.MyProjectName` should be replaced by the root namespace of your .NET project.
```js
export const environment: Config.Environment = {
// other environment variables...
apis: {
default: {
rootNamespace: "MyCompanyName.MyProjectName",
// other environment variables...
},
},
};
```
3. [OPTIONAL] Add the following paths to `tsconfig.base.json` in order to have a shortcut for importing proxies:
```json
{
// other TS configuration...
"compilerOptions": {
// other TS configuration...
"paths": {
"@proxy": ["src/app/proxy/index.ts"],
"@proxy/*": ["src/app/proxy/*"]
}
}
}
```
> The destination the `proxy` folder is created and the paths above may change based on your project structure.
### Services
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./Http-Requests#restservice).
The `generate-proxy` command generates one service per back-end controller and a method (property with a function value actually) for each action in the controller. These methods call backend APIs via [RestService](./Http-Requests#restservice).
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config)
The `providedIn` property of the services is defined as `'root'`. Therefore no need to add a service as a provider to a module. You can use a service by injecting it into a constructor as shown below:
The `providedIn` property of the services is defined as `'root'`. Therefore there is no need to provide them in a module. You can use them directly by injecting them into constructor as shown below:
```js
import { AbpApplicationConfigurationService } from '../abp/applicationconfiguration/services';
import { BookService } from '@proxy/books';
//...
export class HomeComponent{
constructor(private appConfigService: AbpApplicationConfigurationService) {}
@Component(/* component metadata here */)
export class BookComponent implements OnInit {
constructor(private service: BookService) {}
ngOnInit() {
this.appConfigService.get().subscribe()
this.service.get().subscribe(
// do something with the response
);
}
}
```
@ -48,20 +97,48 @@ The Angular compiler removes the services that have not been injected anywhere f
### Models
The generated models match the DTOs in the back-end. Each model is generated as a class under the `src/app/*/models` folder.
The `generate-proxy` command generates interfaces matching DTOs in the back-end. There are also a few [core DTOs](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts) in the `@abp/ng.core` package. In combination, these models can be used to reflect the APIs.
There are a few [base classes](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts) in the `@abp/ng.core` package. Some models extend these classes.
```js
import { PagedResultDto } from "@abp/ng.core";
import { BookDto } from "@proxy/books";
@Component(/* component metadata here */)
export class BookComponent implements OnInit {
data: PagedResultDto<BookDto> = {
items: [],
totalCount: 0,
};
}
```
A class instance can be created as shown below:
### Enums
Enums have always been difficult to populate in the frontend. The `generate-proxy` command genarates enums in a separate file and exports a ready-to-use options constant from the same file. So you can import them as follows:
```js
import { IdentityRoleCreateDto } from '../identity/role/models'
//...
const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true})
import { bookGenreOptions } from "@proxy/books";
@Component(/* component metadata here */)
export class BookComponent implements OnInit {
genres = bookGenreOptions;
}
```
...and consume the options in the template as follows:
```html
<!-- simplified for sake of clarity -->
<select formControlName="genre">
<option [ngValue]="null">Select a genre</option>
<option *ngFor="let genre of genres" [ngValue]="genre.value">
{%{{{ genre.key }}}%}
</option>
</select>
```
Initial values ​​can optionally be passed to each class constructor.
> Please [see this article](https://github.com/abpframework/abp/blob/dev/docs/en/Blog-Posts/2020-09-07%20Angular-Service-Proxies/POST.md) to learn more about service proxies.
## What's Next?
* [HTTP Requests](./Http-Requests)
- [HTTP Requests](./Http-Requests)

BIN
docs/en/UI/Angular/images/generated-files-via-generate-proxy.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 83 KiB

41
docs/en/Upgrading.md

@ -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.

20
docs/en/docs-nav.json

@ -170,6 +170,10 @@
"text": "Settings",
"path": "Settings.md"
},
{
"text": "Features",
"path": "Features.md"
},
{
"text": "Data Filtering",
"path": "Data-Filtering.md"
@ -198,6 +202,10 @@
{
"text": "RabbitMQ Integration",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
},
{
"text": "Kafka Integration",
"path": "Distributed-Event-Bus-Kafka-Integration.md"
}
]
}
@ -219,11 +227,11 @@
"items": [
{
"text": "Email Sending System",
"path": "Emailing.md",
"path": "Emailing.md"
},
{
"text": "MailKit Integration",
"path": "MailKit.md",
"path": "MailKit.md"
}
]
},
@ -423,6 +431,10 @@
"text": "Migration Guide v2.x to v3",
"path": "UI/Angular/Migration-Guide-v3.md"
},
{
"text": "Environment",
"path": "UI/Angular/Environment.md"
},
{
"text": "Service Proxies",
"path": "UI/Angular/Service-Proxies.md"
@ -676,6 +688,10 @@
"text": "Road Map",
"path": "Road-Map.md"
},
{
"text": "Upgrading",
"path": "Upgrading.md"
},
{
"text": "Contribution Guide",
"path": "Contribution/Index.md"

BIN
docs/en/images/features-action.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/images/features-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

9
docs/zh-Hans/Authentication/Social-External-Logins.md

@ -1,10 +1,9 @@
# 社交/外部登录
## ASP.NET Core MVC / Razor Pages UI
[帐户模块](../Modules/Account.md)已配置为开箱即用的处理社交或外部登录. 你可以按照ASP.NET Core文档向你的应用程序添加社交/外部登录提供程序.
### 示例: Facebook 认证
## 示例: Facebook 认证
按照[ASP.NET Core Facebook集成文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/facebook-logins)向你应用程序添加Facebook登录.
@ -27,4 +26,8 @@ context.Services.AddAuthentication()
});
````
> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密.
> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密.
## Angular UI
从v3.1开始,Angular UI使用授权码流程(作为最佳实践)通过重定向到MVC UI登录页面来对用户进行身份验证. 因此,即使你使用的是Angular UI,社交/外部登录集成也与上面说明的相同.并且可以开箱即用.

191
docs/zh-Hans/CLI.md

@ -16,6 +16,12 @@ dotnet tool install -g Volo.Abp.Cli
dotnet tool update -g Volo.Abp.Cli
````
## 全局选项
虽然每个命令可能都有一组选项,但有些全局选项可以与任何命令一起使用:
* `--skip-cli-version-check`: 跳过检查最新版本的ABP CLI. 如果没有指定,它会检查最新版本,如果检查到ABP CLI的新版本,会显示一条警告消息.
## Commands
这里是所有可用的命令列表:
@ -25,7 +31,9 @@ dotnet tool update -g Volo.Abp.Cli
* **`update`**:自动更新的ABP解决方案ABP相关的NuGet和NPM包.
* **`add-package`**: 添加ABP包到项目.
* **`add-module`**: 添加[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index)到解决方案.
* **`generate-proxy`**: 生成客户端代理以使用服务器上的HTTP API端点.
* **`generate-proxy`**: 生成客户端代理以使用HTTP API端点.
* **`remove-proxy`**: 移除以前生成的客户端代理.
* **`switch-to-preview`**: 切换到ABP框架的最新预览版本。
* **`switch-to-preview`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本.
* **`switch-to-stable`**: 切换解决方案所有ABP相关包为最新的稳定版本.
* **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程.
@ -77,6 +85,8 @@ abp new Acme.BookStore
* `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的.如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景.
* `angular`: Angular. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `blazor`: Blazor. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `none`: 无UI. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `--mobile` 或者 `-m`: 指定移动应用程序框架. 如果未指定,则不会创建任何移动应用程序,其他选项:
@ -90,12 +100,33 @@ abp new Acme.BookStore
* **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本.
* `--preview`: 使用最新的预发行版本 (仅在未指定 `--version` 且最新稳定版本之后至少有一个预发行版时).
* `--preview`: 使用最新的预览版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D:\local-templat``https://.../my-template-file.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 默认的数据库提供程序是 `SQL Server`. 如果你使用EF Core但需要更改DBMS,可以按[这里所述](Entity-Framework-Core-Other-DBMS.md)进行更改(创建解决方案之后).
* `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用.
### update
更新所有ABP相关的包可能会很繁琐,框架和模块都有很多包. 此命令自动将解决方案或项目中所有ABP相关的包更新到最新版本.
用法:
````bash
abp update [options]
````
* 如果你的文件夹中有.sln文件,运行命令会将解决方案中所有项目ABP相关的包更新到最新版本.
* 如果你的文件夹中有.csproj文件,运行命令会将项目中所有ABP相关的包更新到最新版本.
#### Options
* `--npm`: 仅更新NPM包
* `--nuget`: 仅更新的NuGet包
* `--solution-path``-sp`: 指定解决方案路径/目录. 默认使用当前目录
* `--solution-name``-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件.
*`--check-all`: 分别检查每个包的新版本. 默认是 `false`.
### add-package
通过以下方式将ABP包添加到项目中
@ -150,105 +181,157 @@ abp add-module Volo.Blogging
* `-sp``--startup-project`: 启动项目的项目文件夹的相对路径. 默认值是当前文件夹.
* `--with-source-code`: 添加模块的源代码,而不是NuGet/NPM软件包.
### update
### generate-proxy
更新所有ABP相关的包可能会很繁琐,框架和模块都有很多包. 此命令自动将解决方案或项目中所有ABP相关的包更新到最新版本.
为您的HTTP API生成Angular服务代理,简化从客户端使用服务的成本. 在运行此命令之前,你的host必须启动正在运行.
用法:
````bash
abp update [options]
abp generate-proxy
````
* 如果你的文件夹中有.sln文件,运行命令会将解决方案中所有项目ABP相关的包更新到最新版本.
* 如果你的文件夹中有.csproj文件,运行命令会将项目中所有ABP相关的包更新到最新版本.
#### Options
* `--include-previews``-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本.
* `--npm`: 仅更新NPM包
* `--nuget`: 仅更新的NuGet包
* `--solution-path``-sp`: 指定解决方案路径/目录. 默认使用当前目录
* `--solution-name``-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件.
*`--check-all`: 分别检查每个包的新版本. 默认是 `false`.
### 切换到每晚构建(预览)包
* `--module``-m`: 指定要为其生成代理的后端模块的名称. 默认值: `app`.
* `--api-name``-a`: 在 `/src/environments/environment.ts` 中定义的API端点名称。. 默认值: `default`.
* `--source``-s`: 指定解析根名称空间和API定义URL的Angular项目名称. 默认值: `defaultProject`
* `--target``-t`: 指定放置生成的代码的Angular项目名称. 默认值: `defaultProject`.
* `--prompt``-p`: 在命令行提示符下询问选项(未指定的选项).
想要切换到ABP框架的最新**每晚构建**预览版可以使用此命令.
> 参阅 [Angular服务代理文档](UI/Angular/Service-Proxies.md) 了解更多.
用法:
### remove-proxy
````bash
abp switch-to-nightly [options]
````
从Angular应用程序中删除以前生成的代理代码. 在运行此命令之前,你的host必须启动正在运行.
This can be especially useful when you generate proxies for multiple modules before and need to remove one of them later.
你也可以使用切换回最新稳定版本:
Usage:
````bash
abp switch-to-stable [options]
abp remove-proxy
````
#### Options
`--solution-directory``-sd`: 指定解决方案文件夹. 解决方案应该在指定文件夹或子文件夹中. 如果未指定,默认为当前目录.
* `--module``-m`: 指定要为其生成代理的后端模块的名称. 默认值: `app`.
* `--api-name``-a`: 在 `/src/environments/environment.ts` 中定义的API端点名称。. 默认值: `default`.
* `--source``-s`: 指定解析根名称空间和API定义URL的Angular项目名称. 默认值: `defaultProject`
* `--target``-t`: 指定放置生成的代码的Angular项目名称. 默认值: `defaultProject`.
* `--prompt``-p`: 在命令行提示符下询问选项(未指定的选项).
### login
> 参阅 [Angular服务代理文档](UI/Angular/Service-Proxies.md) 了解更多.
CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录
### switch-to-preview
```bash
abp login <username>
```
你可以使用此命令将项目切换到ABP框架的最新预览版本.
```bash
abp login <username> -p <password>
```
请注意,新的登录将终止先前的会话并创建一个新的会话.
### logout
用法:
通过从计算机中删除会话令牌来注销.
````bash
abp switch-to-preview [options]
````
```
abp logout
```
#### Options
### generate-proxy
* `--solution-directory``-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录.
### switch-to-nightly
为你的HTTP API生成客户端代码,简化客户端使用服务的成本. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
想要切换到ABP框架的最新[每晚构建](Nightly-Builds.md)预览版可以使用此命令.
用法:
````bash
abp generate-proxy [options]
abp switch-to-nightly [options]
````
#### Options
* `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注!
* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
`--solution-directory``-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录.
示例:
### switch-to-stable
如果你使用的是ABP框架预览包(包括每晚构建),可以使用此命令切换回最新的稳定版本.
用法:
````bash
abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
abp switch-to-stable [options]
````
#### Options
### help
`--solution-directory``-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录.
CLI的基本用法信息.
### translate
用法:
源代码控制存储库中有多个JSON[本地化](Localization.md)文件时,用于简化翻译[本地化](Localization.md)文件的过程.
* 该命令将基于参考文化创建一个统一的json文件
* 它搜索当前目录和所有子目录中的所有本地化"JSON"文件(递归). 然后创建一个包含所有需要翻译的条目的文件(默认情况下名为 "abp-translation.json").
* 翻译了此文件中的条目后,你就可以使用 `--apply` 命令将更改应用于原始本地化文件.
> 该命令的主要目的是翻译ABP框架本地化文件(因为[abp仓库](https://github.com/abpframework/abp)包括数十个要在不同目录中转换的本地化文件).
#### 创建翻译文件
第一步是创建统一的翻译文件:
````bash
abp help [命令名]
abp translate -c <culture> [options]
````
示例:
````bash
abp help # 显示常规帮助.
abp help new # 显示有关 "New" 命令的帮助.
abp translate -c de-DE
````
该命令为 `de-DE` (德语)文化创建了统一的翻译文件.
##### 附加选项
* `--reference-culture``-r`: 默认值 `en`. 指定参考文化.
* `--output``-o`: 输出文件名. 默认值 `abp-translation.json`.
* `--all-values``-all`: 包括所有要翻译的键. 默认情况下,统一翻译文件仅包含目标文化的缺失文本. 如果你可能需要修改之前已经翻译的值,请指定此参数.
#### 应用更改
翻译完统一翻译文件中的条目后,你可以使用 `--apply` 参数将更改应用于原始本地化文件:
````bash
abp translate --apply # apply all changes
abp translate -a # shortcut for --apply
````
然后,检查源代码控制系统上的更改,以确保它已更改了正确的文件. 如果你翻译了ABP框架资源, 请发送 "Pull Request". 提前感谢你的贡献.
##### 附加选项
* `--file``-f`: 默认值: `abp-translation.json`. 翻译文件(仅在之前使用过 `--output` 选项时使用).
### login
CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录
```bash
abp login <username> # Allows you to enter your password hidden
abp login <username> -p <password> # Specify the password as a parameter (password is visible)
abp login <username> --organization <organization> # If you have multiple organizations, you need set your active organization
abp login <username> -p <password> -o <organization> # You can enter both your password and organization in the same command
```
> 当使用-p参数,请注意,因为你的密码是可见的. 它对于CI / CD自动化管道很有用.
请注意,新的登录将终止先前的会话并创建一个新的会话.
### logout
通过从计算机中删除会话令牌来注销.
```
abp logout
```

28
docs/zh-Hans/Contribution/Index.md

@ -1,8 +1,12 @@
## 贡献指南
# 贡献指南
ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南旨在帮助任何想要为项目做出贡献的人.
### 贡献代码
## community.abp.io
如果你可编写文章或关于ASP框架和ASP.NET Core的 "如何" 指南,请提交你的文章到[community.abp.io](https://community.abp.io/)网站.
## 贡献代码
你可以将Pull request(拉取请求)发送到Github存储库.
@ -12,15 +16,15 @@ ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南
在进行任何更改之前,请在[Github问题](https://github.com/abpframework/abp/issues)上进行讨论. 通过这种方式, 其他开发人员将不会处理同一个问题, 你的PR将有更好的机会被接受.
#### Bug修复 & 增强功能
### Bug修复 & 增强功能
你可能希望修复已知Bug或处理计划的增强功能. 请参阅Github上的[问题列表](https://github.com/abpframework/abp/issues).
#### 功能请求
### 功能请求
如果你对框架或模块有功能的想法, 请在Github上[创建一个问题](https://github.com/abpframework/abp/issues/new)或参加现有的讨论. 如果它被社区所接受你就可以实现它.
### 文档翻译
## 文档翻译
你可能希望将完整的[文档](https://abp.io/documents/)(包括本文)翻译成你的母语. 请按照下列步骤操作:
@ -37,13 +41,13 @@ ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南
完成了这些基本的翻译后,将添加一种新的语言
### 资源本地化
## 资源本地化
ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自己的应用程序创建本地化用户界面.
除此之外,框架和预构建模块已经本地化了文本.请参阅[Volo.Abp.UI包的本地化文本](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
#### 使用 "abp translate" 命令
### 使用 "abp translate" 命令
这是推荐的方法,因为它会自动查找所有缺少的文本的特定文化,让你在一个地方翻译.
@ -54,14 +58,10 @@ ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自
* 一旦你完成了翻译,使用 `abp translate -a` 命令应用更改到相关的文件.
* 在GitHub上发送PR.
#### 手动翻译
如果你想更改特定的资源文件,你可以自己找到这个文件进行必要的更改(或为你的语言创建新文件),并在GitHub上发送PR。
### 博客文章和教程
### 手动翻译
如果你发布了一些ABP框架的教程或博客帖子, 请通知我们(通过创建[Github问题](https://github.com/abpframework/abp/issues)), 我们可能会在官方文档中添加指向你的教程或博客帖子的链接和在[推特](https://twitter.com/abpframework)上公布.
如果你想更改特定的资源文件,你可以自己找到这个文件进行必要的更改(或为你的语言创建新文件),并在GitHub上发送PR.
### Bug 报告
## Bug 报告
如果你发现任何Bug, 请[在Github存储库上创建一个问题](https://github.com/abpframework/abp/issues/new).

5
docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md

@ -43,7 +43,10 @@ return user.GetProperty<string>("Title");
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(32);
}
);
````

2
docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md

@ -61,7 +61,7 @@ context.Services.Replace(
````csharp
//[RemoteService(IsEnabled = false)] // 如果你在使用动态控制器,为了避免为应用服务创建重复的控制器, 你可以禁用远程访问.
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))]
public class MyIdentityUserAppService : IdentityUserAppService
{
//...

14
docs/zh-Hans/Data-Filtering.md

@ -151,11 +151,21 @@ public class Book : AggregateRoot<Guid>, IIsActive
ABP使用[EF Core的全局过滤](https://docs.microsoft.com/en-us/ef/core/querying/filters)系统用于[EF Core 集成](Entity-Framework-Core.md). 所以它很好的集成到EF Core中,即使你直接使用 `DbContext` 它也可以正常工作.
实现自定义过滤的最佳方法是为重写你的 `DbContext``CreateFilterExpression` 方法. 示例:
实现自定义过滤的最佳方法是为重写你的 `DbContext``ShouldFilterEntity``CreateFilterExpression` 方法. 示例:
````csharp
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
{
var expression = base.CreateFilterExpression<TEntity>();
@ -174,7 +184,7 @@ protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntit
````
* 添加 `IsActiveFilterEnabled` 属性用于检查是否启用了 `IIsActive` . 内部使用了之前介绍到的 `IDataFilter` 服务.
* 重写 `CreateFilterExpression` 方法检查给定实体是否实现 `IIsActive` 接口,在必要时组合表达式.
* 重写 `ShouldFilterEntity``CreateFilterExpression` 方法检查给定实体是否实现 `IIsActive` 接口,在必要时组合表达式.
### MongoDB

163
docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md

@ -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
docs/zh-Hans/Distributed-Event-Bus.md

@ -8,6 +8,7 @@
* `LocalDistributedEventBus` 是默认实现,实现作为进程内工作的分布式事件总线. 是的!如果没有配置真正的分布式提供程序,**默认实现的工作方式与[本地事件总线](Local-Event-Bus.md)一样**.
* `RabbitMqDistributedEventBus` 通过[RabbitMQ](https://www.rabbitmq.com/)实现分布式事件总线. 请参阅[RabbitMQ集成文档](Distributed-Event-Bus-RabbitMQ-Integration.md)了解如何配置它.
* `KafkaDistributedEventBus` 通过[Kafka](https://kafka.apache.org/)实现分布式事件总线. 请参阅[Kafka集成文档](Distributed-Event-Bus-Kafka-Integration.md)了解如何配置它.
使用本地事件总线作为默认具有一些重要的优点. 最重要的是:它允许你编写与分布式体系结构兼容的代码. 您现在可以编写一个整体应用程序,以后可以拆分成微服务. 最好通过分布式事件而不是本地事件在边界上下文之间(或在应用程序模块之间)进行通信.

6
docs/zh-Hans/Entities.md

@ -226,6 +226,12 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
虽然这种聚合根并不常见(也不建议使用),但实际上可以按照与上面提到的跟实体相同的方式定义复合键.在这种情况下,要使用非泛型的`AggregateRoot`基类.
### BasicAggregateRoot类
`AggregateRoot` 类实现了 `IHasExtraProperties``IHasConcurrencyStamp` 接口,这为派生类带来了两个属性. `IHasExtraProperties` 使实体可扩展(请参见下面的 *额外的属性*部分) 和 `IHasConcurrencyStamp` 添加了由ABP框架管理的 `ConcurrencyStamp` 属性实现[乐观并发](https://docs.microsoft.com/zh-cn/ef/core/saving/concurrency). 在大多数情况下,这些是聚合根需要的功能.
但是,如果你不需要这些功能,你的聚合根可以继承 `BasicAggregateRoot<TKey>`(或`BasicAggregateRoot`).
## 基类和接口的审计属性
有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**.

5
docs/zh-Hans/Entity-Framework-Core-Migrations.md

@ -414,7 +414,10 @@ public static class MyProjectNameEntityExtensions
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(128);
}
);
});
}

5
docs/zh-Hans/Entity-Framework-Core.md

@ -325,7 +325,10 @@ public class BookService
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(64);
}
);
````

3
docs/zh-Hans/Getting-Started-Console-Application.md

@ -1,3 +0,0 @@
## 在控制台应用中使用ABP
ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息.

3
docs/zh-Hans/Global-Features.md

@ -0,0 +1,3 @@
# Global Features
TODO...

9
docs/zh-Hans/How-To/Index.md

@ -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)

2
docs/zh-Hans/Local-Event-Bus.md

@ -156,6 +156,8 @@ namespace AbpDemo
> 事件处理程序类必须注册到依赖注入(DI),示例中使用了 `ITransientDependency`. 参阅[DI文档](Dependency-Injection.md)了解更多选项.
如果您执行**数据库操作**并在事件处理程序中使用[仓储](Repositories.md),那么您可能需要创建一个[工作单元](Unit-Of-Work.md),因为一些存储库方法需要在**活动的工作单元**中工作. 确保处理方法设置为 `virtual`,并为该方法添加一个 `[UnitOfWork]` attribute. 或者手动使用 `IUnitOfWorkManager` 创建一个工作单元范围.
## 事务和异常行为
当一个事件发布,订阅的事件处理程序将立即执行.所以;

1
docs/zh-Hans/MailKit.md

@ -0,0 +1 @@
TODO...

2
docs/zh-Hans/Module-Development-Basics.md

@ -1,4 +1,4 @@
## 模块开发
## 模块
### 介绍

27
docs/zh-Hans/Modules/Identity.md

@ -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";
});
```

3
docs/zh-Hans/Moodule-Entity-Extensions.md

@ -0,0 +1,3 @@
# Module Entity Extensions
参阅 https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions (文档会在近期完成).

32
docs/zh-Hans/Nightly-Builds.md

@ -2,40 +2,18 @@
所有框架和模块包每晚都部署到MyGet. 因此你可以使用或测试最新的代码,而无需等待下一个版本.
## 在Visual Studio配置
## 安装和卸载每晚预览包
> 需要Visual Studio 2017以上
1. 在VS中打开: `工具 > 选项 > NuGet 包管理器 > 程序包源`
2. 单击绿色的`+`图标
3. 在底部输入名称(ABP Nightly)和并粘贴URL(`https://www.myget.org/F/abp-nightly/api/v3/index.json`)到源上.
![night-build-add-nuget-source](images/night-build-add-nuget-source.png)
3. 单击`更新`按钮
4. 点击`确定`按钮保存
## 安装包
现在, 你可以从**管理NuGet包** 或 **程序包管理器控制台** 将预览/夜间程序包安装到你的项目中.
![night-build-add-nuget-package](images/night-build-add-nuget-package.png)
1. 在nuget浏览中,选择"包括预发行版".
2. 将包源更改为`全部`.
3. 搜索nuget包. 你将看到包的预发布格式为`(VERSION)-preview(DATE)` (如本示例中的**v0.16.0-preview20190401**).
4. 你可以单击`安装`按钮将包添加到项目中.
## 安装和卸载预览NPM包
预览NPM包的最新版本可以通过在应用程序的根文件夹命令运行命令安装:
可以通过在应用程序的根文件夹中运行以下命令安装最新版本的夜间预览软件包:
```bash
abp switch-to-preview --npm
abp switch-to-nightly
```
如果你正在使用ABP框架预览包,你可以使用此命令切换回稳定版本:
如果你正在使用ABP框架每晚预览包,你可以使用此命令切换回稳定版本:
```bash
abp switch-to-stable --npm
abp switch-to-stable
```
参阅 [ABP CLI 文档](./CLI.md) 了解更多信息.

1
docs/zh-Hans/Previews.md

@ -0,0 +1 @@
TODO...

6
docs/zh-Hans/Repositories.md

@ -6,7 +6,11 @@
## 通用(泛型)仓储
ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** . 你可以在服务中[注入](Dependency-Injection.md) `IRepository<TEntity, TKey>` 使用标准的**CRUD**操作. 用法示例:
ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** . 你可以在服务中[注入](Dependency-Injection.md) `IRepository<TEntity, TKey>` 使用标准的**CRUD**操作.
> 数据库提供程序层应正确配置为能够使用默认的通用存储库. 如果你已经使用启动模板创建了项目,则这些配置 **已经完成**了. 如果不是,请参考数据库提供程序文档([EF Core](Entity-Framework-Core.md) / [MongoDB](MongoDB.md))进行配置.
**默认通用仓储用法示例:**
````C#
public class PersonAppService : ApplicationService

27
docs/zh-Hans/Samples/Index.md

@ -15,17 +15,14 @@
一个简单的CRUD应用程序,展示了使用ABP框架开发应用程序的基本原理. 使用不同的技术实现了相同的示例:
* **Book Store: Razor Pages UI & Entity Framework Core**
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC)
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore)
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF)
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* **Book Store: Angular UI & MongoDB**
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG)
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG&DB=Mongo)
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
* **Book Store: Modular application (Razor Pages UI & EF Core)**
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular)
如果没有Razor Pages & MongoDB 结合,但你可以检查两个文档来理解它,因为DB和UI不会互相影响.
@ -33,11 +30,14 @@
### 其他示例
* **Entity Framework 迁移**: 演示如何将应用程序拆分为多个数据库的解决方案. 每个数据库包含不同的模块.
* [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [源码](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo)
* [EF Core数据库迁移文档](../Entity-Framework-Core-Migrations.md)
* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users.
* [源码](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo)
* [SignalR 集成文档](../SignalR-Integration.md)
* **分布式架构中的实时消息** (使用 SingalR & RabbitMQ)
* [源码](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo)
* [文章](https://community.abp.io/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17)
* **Dashboard Demo**: 一个简单的应用程序,展示了如何在ASP.NET Core MVC UI中使用widget系统.
* [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [Widget 文档](../UI/AspNetCore/Widgets.md)
@ -50,15 +50,20 @@
* [文本模板文档](../Text-Templating.md)
* **存储过程 Demo**: 演示如何以最佳实践使用存储过程,数据库视图和函数.
* [源码](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo)
* **无密码认证**: 演示如何添加自定义令牌提供者使用链接验证用户身份,而不是输入密码.
* [源码](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication)
* [文章](https://community.abp.io/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj)
* **自定义认证**: 如何为ASP.NET Core MVC / Razor Pages应用程序自定义身份验证的解决方案.
* [源码](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)
* 相关 "[How To](../How-To/Index.md)" 文档:
* [Azure Active Directory 认证](../How-To/Azure-Active-Directory-Authentication-MVC.md)
* [自定义登录页面](../How-To/Customize-Login-Page-MVC.md)
* [自定义 SignIn Manager](../How-To/Customize-SignIn-Manager.md)
* 相关文章:
* [Azure Active Directory 认证](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf)
* [自定义登录页面](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd)
* [自定义 SignIn Manager](https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753)
* **空的ASP.NET Core应用程序**: 从基本的ASP.NET Core应用程序使用ABP框架.
* [源码](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)
* [文档](../Getting-Started-AspNetCore-Application.md)
* **GRPC Demo**: 演示如何将gRPC服务添加到基于ABP框架的Web应用程序以及如何从控制台应用程序使用它.
* [源码](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)
* **空的控制台应用程序**: 从基本的控制台应用程序安装ABP框架.
* [源码](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication)
* [文档](../Getting-Started-Console-Application.md)

14
docs/zh-Hans/Startup-Templates/Application.md

@ -59,6 +59,20 @@ abp new Acme.BookStore -d mongodb
### 指定移动应用程序框架
此模板支持以下移动应用程序框架:
- `react-native`: React Native
使用 `-m` (或 `--mobile`) 选项指定移动应用程序框架:
````bash
abp new Acme.BookStore -m react-native
````
如果未指定,不会创建移动应用程序.
### 指定移动应用程序框架
该模板支持以下移动应用程序框架:
- `react-native`: React Native

1
docs/zh-Hans/UI/Angular/Environment.md

@ -0,0 +1 @@
TODO...

2
docs/zh-Hans/UI/Angular/Migration-Guide-v3.md

@ -450,4 +450,4 @@ export function switchLogos(store: Store) {
## 下一步是什么?
* [服务代理](Service-Proxies.md)
* [环境](./Environment.md)

1
docs/zh-Hans/UI/Angular/Multi-Tenancy.md

@ -0,0 +1 @@
TODO...

4
docs/zh-Hans/UI/Angular/Permission-Management.md

@ -4,7 +4,7 @@
你可以使用 `ConfigState``getGrantedPolicy` 选择器获取经过身份验证的用户的权限.
你可以从Store中获取权限的布尔值:
你可以获取权限的布尔值:
```js
import { Store } from '@ngxs/store';
@ -78,4 +78,4 @@ const routes: Routes = [
## 下一步是什么?
* [确认弹层](./Confirmation-Service.md)
* [多租户](./Multi-Tenancy.md)

8
docs/zh-Hans/UI/Angular/Toaster-Service.md

@ -24,7 +24,7 @@ class DemoComponent {
### 如何显示一个Toast Overlay
```js
this.toast.success('Message', 'Title');
this.toaster.success('Message', 'Title');
```
- `ToasterService` 方法接收三个参数,分别是 `message`, `title`, 和 `options`.
@ -70,9 +70,9 @@ const options: Partial<Toaster.ToastOptions> = {
已打开的toast overlay可以通过手动调用 `remove` 方法传递指定的 toast `id`删除.
```js
const toastId = this.toast.success('Message', 'Title')
const toastId = this.toaster.success('Message', 'Title')
this.toast.remove(toastId);
this.toaster.remove(toastId);
```
### 如何删除所有的Toasts
@ -80,7 +80,7 @@ this.toast.remove(toastId);
可以手动调用 `clear` 方法删除所有的已打开的toasts.
```js
this.toast.clear();
this.toaster.clear();
```
## API

2
docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md

@ -157,7 +157,7 @@ public class MyLoginModel : LoginModel
## 重写静态资源
重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,虚拟文件系统会自动处理它.
重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,[虚拟文件系统](../../Virtual-File-System.md)会自动处理它.
## 操作捆绑

1
docs/zh-Hans/Upgrading.md

@ -0,0 +1 @@
TODO...

116
docs/zh-Hans/Virtual-File-System.md

@ -27,13 +27,20 @@
如果需要添加多个文件, 这样做会很乏味. 作为选择, 你可以直接编辑 **.csproj** 文件:
````C#
<ItemGroup>
<EmbeddedResource Include="MyResources\**\*.*" />
<Content Remove="MyResources\**\*.*" />
</ItemGroup>
````
此配置以递归方式添加项目的 **MyResources** 文件夹下的所有文件(包括将来新添加的文件).
如果文件名包含一些特殊字符,在项目/程序集中嵌入文件可能会导致问题. 为了克服这个限制;
1. 将[Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet包添加到包含嵌入式资源的项目中.
2. 添加 `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>``.csproj` 文件的 `<PropertyConfig>...</PropertyConfig>` 部分中.
此配置以递归方式添加项目的 **MyResources** 文件夹下的所有文件(包括将来新添加的文件).
> 尽管这两个步骤是可选的,并且ABP无需这些配置即可工作,但强烈建议你这样做.
### 配置AbpVirtualFileSystemOptions
@ -74,53 +81,12 @@ Configure<AbpVirtualFileSystemOptions>(options =>
* 你的项目有一个名为 `MyFiles` 的目录.
* 你只想添加 `MyFiles` 目录到虚拟文件系统.
#### 在开发过程中处理嵌入式文件
将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难.
假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的.
我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能.
下面的示例展示了应用程序依赖于包含嵌入文件的模块("MyModule"), 并且应用程序可以在开发过程中直接使用模块的源代码.
````C#
[DependsOn(typeof(MyModule))]
public class MyWebAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(
Path.Combine(
hostingEnvironment.ContentRootPath,
string.Format(
"..{0}MyModuleProject",
Path.DirectorySeparatorChar
)
)
);
});
}
}
}
````
上面的代码假设`MyWebAppModule`和`MyModule`是Visual Studio解决方案中的两个不同的项目, `MyWebAppModule`依赖于`MyModule`.
> [应用程序启动模板]已经为本地化文件应用这个方法,所以当你更改一个本地化文件时,它会自动检测到更改.
## IVirtualFileProvider
将文件嵌入到程序集中并注册到虚拟文件系统后,可以使用 `IVirtualFileProvider` 接口来获取文件或目录内容:
````C#
public class MyService
public class MyService : ITransientDependency
{
private readonly IVirtualFileProvider _virtualFileProvider;
@ -129,7 +95,7 @@ public class MyService
_virtualFileProvider = virtualFileProvider;
}
public void Foo()
public void Test()
{
//Getting a single file
var file = _virtualFileProvider
@ -154,7 +120,9 @@ public class MyService
#### 虚拟文件中间件
虚拟文件中间件用于向客户端/浏览器提供嵌入式(js, css, image ...)文件, 就像 **wwwroot** 文件夹中的物理(静态)文件一样. 在静态文件中间件之后添加它, 如下所示:
虚拟文件中间件用于向客户端/浏览器提供嵌入式(js, css, image ...)文件, 就像 **wwwroot** 文件夹中的物理(静态)文件一样. 它同时涵盖了物理文件.
在你ASP.NET Core中间件配置中替换 `app.UseStaticFiles()``app.UseVirtualFiles()`:
````C#
app.UseVirtualFiles();
@ -170,6 +138,62 @@ app.UseVirtualFiles();
* Pages
* Views
* Components
* Themes
这允许你可以在 `.cshtml` 文件附近添加 `.js`, `.css`... 文件,更易于开发和维护你的项目.
#### 在开发过程中处理嵌入式文件
将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难.
假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的.
我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能.
下面的示例展示了应用程序依赖于包含嵌入文件的模块("MyModule"), 并且应用程序可以在开发过程中直接使用模块的源代码.
````C#
[DependsOn(typeof(MyModule))]
public class MyWebAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(
Path.Combine(
hostingEnvironment.ContentRootPath,
string.Format(
"..{0}MyModuleProject",
Path.DirectorySeparatorChar
)
)
);
});
}
}
}
````
上面的代码假设`MyWebAppModule`和`MyModule`是Visual Studio解决方案中的两个不同的项目, `MyWebAppModule`依赖于`MyModule`.
> [应用程序启动模板]已经为本地化文件应用这个方法,所以当你更改一个本地化文件时,它会自动检测到更改.
## 替换/重写虚拟文件
虚拟文件系统在运行时创建一个统一的文件系统,其中实际的文件在开发时被分配到不同的模块中.
如果两个模块将文件添加到相同的虚拟路径(如`my-path/my-file.css`),之后添加的模块将替换/替换前一个([模块依赖](Module-Development-Basics.md)顺序决定了添加文件的顺序).
此功能允许你的应用程序可以覆盖/替换定义应用程序所使用的模块的任何虚拟文件. 这是ABP框架的基本可扩展性功能之一.
因此,如果需要替换模块的文件,只需在模块/应用程序中完全相同的路径中创建该文件.
### 物理文件
物理文件总是覆盖虚拟文件. 这意味着如果你把一个文件放在 `/wwwroot/my-folder/my-file.css`,它将覆盖虚拟文件系统相同位置的文件.因此你需要知道在模块中定义的文件路径来覆盖它们.

56
docs/zh-Hans/docs-nav.json

@ -4,21 +4,16 @@
"text": "入门",
"items": [
{
"text": "从启动模板开始",
"text": "Web应用程序",
"path": "Getting-Started.md"
},
{
"text": "从空项目开始",
"items": [
{
"text": "使用ASP.NET Core Web Application",
"path": "Getting-Started-AspNetCore-Application.md"
},
{
"text": "使用Console Application",
"path": "Getting-Started-Console-Application.md"
}
]
"text": "控制台应用程序",
"path": "Startup-Templates/Console.md"
},
{
"text": "空Web应用程序t",
"path": "Getting-Started-AspNetCore-Application.md"
}
]
},
@ -66,10 +61,6 @@
}
]
},
{
"text": "\"如何\" 指南",
"path": "How-To/Index.md"
},
{
"text": "从ASP.NET Boilerplate迁移",
"path": "AspNet-Boilerplate-Migration-Guide.md"
@ -180,6 +171,10 @@
{
"text": "RabbitMQ 集成",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
},
{
"text": "Kafka 集成",
"path": "Distributed-Event-Bus-Kafka-Integration.md"
}
]
}
@ -197,6 +192,19 @@
"path": "Object-To-Object-Mapping.md"
},
{
"text": "邮件发送",
"items": [
{
"text": "邮件发送系统",
"path": "Emailing.md"
},
{
"text": "MailKit集成",
"path": "MailKit.md"
}
]
},
{
"text": "BLOB存储",
"items": [
@ -393,6 +401,10 @@
"text": "v2.x 到 v3 迁移指南",
"path": "UI/Angular/Migration-Guide-v3.md"
},
{
"text": "环境",
"path": "UI/Angular/Environment.md"
},
{
"text": "服务代理",
"path": "UI/Angular/Service-Proxies.md"
@ -409,6 +421,10 @@
"text": "权限管理",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "多租户",
"path": "UI/Angular/Multi-Tenancy.md"
},
{
"text": "确认弹层",
"path": "UI/Angular/Confirmation-Service.md"
@ -631,6 +647,10 @@
"text": "微服务架构",
"path": "Microservice-Architecture.md"
},
{
"text": "预览版本",
"path": "Previews.md"
},
{
"text": "每日构建",
"path": "Nightly-Builds.md"
@ -639,6 +659,10 @@
"text": "路线图",
"path": "Road-Map.md"
},
{
"text": "升级",
"path": "Upgrading.md"
},
{
"text": "贡献指南",
"path": "Contribution/Index.md"

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save