Merge remote-tracking branch 'origin/dev' into pr/2563

pull/3732/head
Galip Tolga Erdem 6 years ago
commit de2ad91855

@ -14,15 +14,17 @@ jobs:
build-test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-dotnet@master
with:
dotnet-version: "3.1.100"
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
with:
dotnet-version: 3.1.100
- name: Build All
run: .\build\build-all.ps1
shell: pwsh
- name: Build All
run: .\build-all.ps1
working-directory: .\build
shell: powershell
- name: Test All
run: .\build\test-all.ps1
shell: pwsh
- name: Test All
run: .\test-all.ps1
working-directory: .\build
shell: powershell

@ -14,6 +14,7 @@
"Permission:Delete": "Delete",
"Permission:Create": "Create",
"Menu:Organizations": "Organizations",
"Menu:Accounting": "Accounting",
"Menu:Packages": "Packages",
"NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?",
"NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?",
@ -92,6 +93,14 @@
"OrganizationNamePlaceholder": "Organization name...",
"UsernameOrEmail": "Username or email",
"UsernameOrEmailPlaceholder": "Username or email...",
"Member": "Member"
"Member": "Member",
"QuotationPurchasedOrderNo": "Quotation Purchased Order No",
"QuotationTime": "Quotation Time",
"CompanyName": "Company Name",
"CompanyAddress": "Company Address",
"Price": "Price",
"ExtraText": "Extra Text",
"ExtraAmount": "Extra Amount",
"DownloadQuotation": "Download Quotation"
}
}

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>2.6.0</Version>
<Version>2.7.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl>

@ -316,7 +316,7 @@ The `context` object contains necessary information about the exception occurred
Some exception types are automatically thrown by the framework:
- `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See [authorization](Authorization.md) for more.
- `AbpValidationException` is thrown if the input of the current request is not valid. See [validation] (Validation.md) for more.
- `AbpValidationException` is thrown if the input of the current request is not valid. See [validation](Validation.md) for more.
- `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](Repositories.md).
You can also throw these type of exceptions in your code (although it's rarely needed).

@ -172,11 +172,11 @@ namespace MyCompany.MyProject
{
options.Tenants = new[]
{
new TenantInformation(
new TenantConfiguration(
Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"), //Id
"tenant1" //Name
),
new TenantInformation(
new TenantConfiguration(
Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"), //Id
"tenant2" //Name
)
@ -252,7 +252,7 @@ TODO: This package implements ITenantStore using a real database...
#### Tenant Information
ITenantStore works with **TenantInformation** class that has several properties for a tenant:
ITenantStore works with **TenantConfiguration** class that has several properties for a tenant:
* **Id**: Unique Id of the tenant.
* **Name**: Unique name of the tenant.

@ -982,11 +982,13 @@ import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {

@ -574,11 +574,13 @@ import { GetBooks, CreateUpdateBook } from '../actions/books.actions'; //<== add
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {
@ -1330,11 +1332,13 @@ import { GetBooks, CreateUpdateBook, DeleteBook } from '../actions/books.actions
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {

@ -0,0 +1,163 @@
# Confirmation Popup
You can use the `ConfirmationService` in @abp/ng.theme.shared package to display a confirmation popup by placing at the root level in your project.
## Getting Started
You do not have to provide the `ConfirmationService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ConfirmationService } from '@abp/ng.theme.shared';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private confirmation: ConfirmationService) {}
}
```
## Usage
You can use the `success`, `warn`, `error`, and `info` methods of `ConfirmationService` to display a confirmation popup.
### How to Display a Confirmation Popup
```js
const confirmationStatus$ = this.confirmation.success('Message', 'Title');
```
- The `ConfirmationService` methods accept three parameters that are `message`, `title`, and `options`.
- `success`, `warn`, `error`, and `info` methods return an [RxJS Subject](https://rxjs-dev.firebaseapp.com/guide/subject) to listen to confirmation popup closing event. The type of event value is [`Confirmation.Status`](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts#L24) that is an enum.
### How to Listen Closing Event
You can subscribe to the confirmation closing event like below:
```js
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
constructor(private confirmation: ConfirmationService) {}
this.confirmation
.warn('::WillBeDeleted', { key: '::AreYouSure', defaultValue: 'Are you sure?' })
.subscribe((status: Confirmation.Status) => {
// your code here
});
```
- The `message` and `title` parameters accept a string, localization key or localization object. See the [localization document](./Localization.md)
- `Confirmation.Status` is an enum and has three properties;
- `Confirmation.Status.confirm` is a closing event value that will be emitted when the popup is closed by the confirm button.
- `Confirmation.Status.reject` is a closing event value that will be emitted when the popup is closed by the cancel button.
- `Confirmation.Status.dismiss` is a closing event value that will be emitted when the popup is closed by pressing the escape.
If you are not interested in the confirmation status, you do not have to subscribe to the returned observable:
```js
this.confirmation.error('You are not authorized.', 'Error');
```
### How to Display a Confirmation Popup With Given Options
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods:
```js
const options: Partial<Confirmation.Options> = {
hideCancelBtn: false,
hideYesBtn: false,
cancelText: 'Close',
yesText: 'Confirm',
messageLocalizationParams: ['Demo'],
titleLocalizationParams: [],
};
this.confirmation.warn(
'AbpIdentity::RoleDeletionConfirmationMessage',
'Are you sure?',
options,
);
```
- `hideCancelBtn` option hides the cancellation button when `true`. Default value is `false`
- `hideYesBtn` option hides the confirmation button when `true`. Default value is `false`
- `cancelText` is the text of the cancellation button. A localization key or localization object can be passed. Default value is `AbpUi::Cancel`
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`
- `messageLocalizationParams` is the interpolation parameters for the localization of the message.
- `titleLocalizationParams` is the interpolation parameters for the localization of the title.
With the options above, the confirmation popup looks like this:
![confirmation](./images/confirmation.png)
### How to Remove a Confirmation Popup
The open confirmation popup can be removed manually via the `clear` method:
```js
this.confirmation.clear();
```
## API
### success
```js
success(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Confirmation` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts)
### warn
```js
warn(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### error
```js
error(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### info
```js
info(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### clear
```js
clear(
status: Confirmation.Status = Confirmation.Status.dismiss
): void
```
- `status` parameter is the value of the confirmation closing event.
## What's Next?
- [Toast Overlay](./Toaster-Service.md)

@ -125,6 +125,16 @@ removeContent(element: HTMLScriptElement | HTMLStyleElement): void
- `element` parameter is the inserted `HTMLScriptElement` or `HTMLStyleElement` element, which was returned by `insertContent` method.
### has
```js
has(content: string): boolean
```
The `has` method returns a boolean value that indicates the given content has already been added to the DOM or not.
- `content` parameter is the content of the inserted `HTMLScriptElement` or `HTMLStyleElement` element.
## What's Next?
- [ContentProjectionService](./Content-Projection-Service.md)

@ -206,4 +206,4 @@ You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob
## What's Next?
* [Localization](./Localization.md)
* [Localization](./Localization.md)

@ -52,7 +52,7 @@ class DemoComponent {
The `load` method returns an observable to which you can subscibe in your component or with an `async` pipe. In the example above, the `NgIf` directive will render `<some-component>` only **if the script gets successfully loaded or is already loaded before**.
> You can subscribe multiple times in your template with `async` pipe. The styles will only be loaded once.
> You can subscribe multiple times in your template with `async` pipe. The Scripts will only be loaded once.
Please refer to [LoadingStrategy](./Loading-Strategy.md) to see all available loading strategies and how you can build your own loading strategy.

@ -76,4 +76,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`.
## What's Next?
* [Config State](./Config-State.md)
- [Confirmation Popup](./Confirmation-Service.md)

@ -0,0 +1,157 @@
# Toast Overlay
You can use the `ToasterService` in @abp/ng.theme.shared package to display messages in an overlay by placing at the root level in your project.
## Getting Started
You do not have to provide the `ToasterService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ToasterService } from '@abp/ng.theme.shared';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private toaster: ToasterService) {}
}
```
## Usage
You can use the `success`, `warn`, `error`, and `info` methods of `ToasterService` to display an overlay.
### How to Display a Toast Overlay
```js
this.toast.success('Message', 'Title');
```
- The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`.
- `success`, `warn`, `error`, and `info` methods return the id of opened toast overlay. The toast can be removed with this id.
### How to Display a Toast Overlay With Given Options
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods:
```js
import { Toaster, ToasterService } from '@abp/ng.theme.shared';
//...
constructor(private toaster: ToasterService) {}
//...
const options: Partial<Toaster.ToastOptions> = {
life: 10000,
sticky: false,
closable: true,
tapToDismiss: true,
messageLocalizationParams: ['Demo', '1'],
titleLocalizationParams: []
};
this.toaster.error('AbpUi::EntityNotFoundErrorMessage', 'AbpUi::Error', options);
```
- `life` option is the closing time in milliseconds. Default value is `5000`.
- `sticky` option keeps toast overlay on the screen by ignoring the `life` option when `true`. Default value is `false`.
- `closable` option displays the close icon on the toast overlay when it is `true`. Default value is `true`.
- `tapToDismiss` option, when `true`, allows closing the toast overlay by clicking over it. Default value is `false`.
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`.
- `messageLocalizationParams` is the interpolation parameters for the localization of the message.
- `titleLocalizationParams` is the interpolation parameters for the localization of the title.
With the options above, the toast overlay looks like this:
![toast](./images/toast.png)
### How to Remove a Toast Overlay
The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast:
```js
const toastId = this.toast.success('Message', 'Title')
this.toast.remove(toastId);
```
### How to Remove All Toasts
The all open toasts can be removed manually via the `clear` method:
```js
this.toast.clear();
```
## API
### success
```js
success(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
- `Config` namespace can be imported from `@abp/ng.core`.
- `Toaster` namespace can be imported from `@abp/ng.theme.shared`.
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Toaster` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts)
### warn
```js
warn(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### error
```js
error(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### info
```js
info(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### remove
```js
remove(id: number): void
```
Removes an open toast by the given id.
### clear
```js
clear(): void
```
Removes all open toasts.
## See Also
- [Confirmation Popup](./Confirmation-Service.md)
## What's Next?
- [Config State](./Config-State.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -12,18 +12,18 @@ ABP Framework also adds some **useful features** to the standard bootstrap compo
Here, the list of components those are wrapped by the ABP Framework:
* [Alerts](Alerts.md)
* [Buttons](Buttons.md)
* [Cards](Cards.md)
* [Alerts](Alerts.md)
* [Tabs](Tabs.md)
* [Grids](Grids.md)
* [Modals](Modals.md)
* [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md)
* [Grids](Grids.md)
* [List Groups](List-Groups.md)
* [Modals](Modals.md)
* [Paginator](Paginator.md)
* [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md)
* [Tabs](Tabs.md)
* [Tooltips](Tooltips.md)
* ...

@ -337,6 +337,14 @@
"text": "Permission Management",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "Confirmation Popup",
"path": "UI/Angular/Confirmation-Service.md"
},
{
"text": "Toast Overlay",
"path": "UI/Angular/Toaster-Service.md"
},
{
"text": "Config State",
"path": "UI/Angular/Config-State.md"

@ -537,11 +537,13 @@ import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {

@ -6,7 +6,7 @@ ABP可以按照惯例 **自动** 将你的应用程序服务配置为API控制
## 配置
基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示:
基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示:
````csharp
[DependsOn(BookStoreApplicationModule)]
@ -82,7 +82,7 @@ Configure<AbpAspNetCoreMvcOptions>(options =>
* 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`.
* 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复.
* 将结果转换为**camelCase**.
* 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称它将为空,因为`GetPhonesAsync`方法名称将为`phone`.
* 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`.
* 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method.
* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId').

@ -380,4 +380,4 @@ public class DistrictKey
### 生命周期
应用服务的生命周期是[transient](Dependency-Injection)的它们会自动注册到依赖注入系统.
应用服务的生命周期是[transient](Dependency-Injection)的,它们会自动注册到依赖注入系统.

@ -86,7 +86,7 @@ ABP框架定义了Tag Helper用于简单的创建bootstrap按钮.
### `icon-type`
`icon-type` 是一个可选参数它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它.
`icon-type` 是一个可选参数.它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它.
你可以为按钮选择以下图标类型:

@ -43,12 +43,12 @@ Configure<AbpAuditingOptions>(options =>
* `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统.
* `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志.
* `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表.
* `EntityHistorySelectors`选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分.
* `EntityHistorySelectors`:选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分.
* `Contributors`: `AuditLogContributor` 实现的列表. 贡献者是扩展审计日志系统的一种方式. 有关详细信息请参阅下面的"审计日志贡献者"部分.
### 实体历史选择器
保存的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**.
保存的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**.
要保存的所有实体的所有更改,只需使用 `AddAllEntities()` 扩展方法.
@ -131,7 +131,7 @@ public class HomeController : AbpController
可以为任何类型的类(注册到[依赖注入](Dependency-Injection.md)并从依赖注入解析)启用审计日志,默认情况下仅对控制器和应用程序服务启用.
对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,您的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录.
对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,你的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录.
### 启用/禁用 实体 & 属性
@ -211,8 +211,8 @@ public class MyUser : Entity<Guid>
* **AuditLogInfo**: 具有以下属性:
* `ApplicationName`: 当你保存不同的应用审计日志到同一个数据库,这个属性用来区分应用程序.
* `UserId`当前用户的Id,用户未登录为 `null`.
* `UserName`当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找).
* `UserId`:当前用户的Id,用户未登录为 `null`.
* `UserName`:当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找).
* `TenantId`: 当前租户的Id,对于多租户应用.
* `TenantName`: 当前租户的名称,对于多租户应用.
* `ExecutionTime`: 审计日志对象创建的时间.
@ -222,28 +222,28 @@ public class MyUser : Entity<Guid>
* `ClientIpAddress`: 客户端/用户设备的IP地址.
* `CorrelationId`: 当前[相关Id](CorrelationId.md). 相关Id用于在单个逻辑操作中关联由不同应用程序(或微服务)写入的审计日志.
* `BrowserInfo`: 当前用户的浏览器名称/版本信息,如果有的话.
* `HttpMethod`: 当前HTTP请求的方法(GETPOSTPUTDELETE ...等).
* `HttpMethod`: 当前HTTP请求的方法(GET,POST,PUT,DELETE ...等).
* `HttpStatusCode`: HTTP响应状态码.
* `Url`: 请求的URL.
* **AuditLogActionInfo**: 一个 审计日志动作通常是web请求期间控制器动作或[应用服务](Application-Services.md)方法调用. 一个审计日志可以包含多个动作. 动作对象具有以下属性:
* `ServiceName`执行的控制器/服务的名称.
* `MethodName`控制器/服务执行的方法的名称.
* `Parameters`传递给方法的参数的JSON格文本.
* `ServiceName`:执行的控制器/服务的名称.
* `MethodName`:控制器/服务执行的方法的名称.
* `Parameters`:传递给方法的参数的JSON格文本.
* `ExecutionTime`: 执行的时间.
* `ExecutionDuration`: 方法执行时长,以毫秒为单位. 可以用来观察方法的性能.
* **EntityChangeInfo**: 表示一个实体在Web请求中的变更. 审计日志可以包含0个或多个实体的变更. 实体变更具有以下属性:
* `ChangeTime`: 当实体被改变的时间.
* `ChangeType`具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2).
* `ChangeType`:具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2).
* `EntityId`: 更改实体的Id.
* `EntityTenantId`实体所属的租户Id.
* `EntityTenantId`:实体所属的租户Id.
* `EntityTypeFullName`: 实体的类型(类)的完整命名空间名称(例如Book实体的*Acme.BookStore.Book*.
* **EntityPropertyChangeInfo**: 表示一个实体的属性的更改.一个实体的更改信息(上面已说明)可含有具有以下属性的一个或多个属性的更改:
* `NewValue`: 属性的新值. 如果实体已被删除为 `null`.
* `OriginalValue`变更前旧/初始值. 如果实体是新创建为 `null`.
* `OriginalValue`:变更前旧/初始值. 如果实体是新创建为 `null`.
* `PropertyName`: 实体类的属性名称.
* `PropertyTypeFullName`属性类型的完整命名空间名称.
* `PropertyTypeFullName`:属性类型的完整命名空间名称.
* **Exception**: 审计日志对象可能包含零个或多个异常. 可以得到失败请求的异常信息.
* **Comment**用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释.
* **Comment**:用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释.
除了上面说明的标准属性之外,`AuditLogInfo`, `AuditLogActionInfo``EntityChangeInfo` 对象还实现了`IHasExtraProperties` 接口,你可以向这些对象添加自定义属性.
@ -331,7 +331,7 @@ public class MyService : ITransientDependency
### 手动创建审计日志范围
你很少需要手动创建审计日志的范围,但如果你需要,可以使用 `IAuditingManager` 创建审计日志的范围.
:
````csharp
public class MyService : ITransientDependency
@ -366,7 +366,7 @@ public class MyService : ITransientDependency
}
````
可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象.
可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象.
## 审计日志模块

@ -153,7 +153,7 @@ myGroup.AddPermission(
myGroup.AddPermission("Author_Management", isEnabled: false);
````
通常你不需要定义禁用权限(除非暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法.
通常你不需要定义禁用权限(除非暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法.
> 注意:检查一个未定义的权限会抛出异常,而被禁用的权限的返回禁止(false).

@ -2,7 +2,7 @@
## 介绍
背景工人在应用简单独立的线程在后台运行。一般来说,他们定期运行,以执行一些任务。例子;
背景工人在应用简单独立的线程在后台运行.一般来说,他们定期运行,以执行一些任务.例子;
后台工作者在应用程序后台运行的简单的独立线程,一般来说它们定期运行执行一些任务.例如;
* 后台工作者可以定期**删除过时的日志**.
@ -72,7 +72,7 @@ public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
}
````
* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性
* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性.
* 它需要实现 `DoWorkAsync` 方法**执行**定期任务.
* 最好使用 `PeriodicBackgroundWorkerContext` **解析依赖** 而不是构造函数. 因为 `AsyncPeriodicBackgroundWorkerBase` 使用 `IServiceScope` 在你的任务执行结束时会对其 **disposed**.
* `AsyncPeriodicBackgroundWorkerBase` **捕获并记录** 由 `DoWorkAsync` 方法抛出的 **异常**.

@ -13,7 +13,7 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
- 使用[Ocelot](https://github.com/ThreeMammals/Ocelot)库开发了多个**网关** / BFF(后端为前端(Backend for Frontends)).
- 使用[IdentityServer](https://identityserver.io/)框架开发**身份验证服务**.它也是一个带有必要UI的SSO(单点登录)应用程序.
- 有**多个数据库**.一些微服务有自己的数据库,而一些服务/应用程序共享一个数据库(以演示不同的用例).
- 具有不同类型的数据库**SQL Server**(使用**Entity Framework Core** ORM)和**MongoDB**.
- 具有不同类型的数据库:**SQL Server**(使用**Entity Framework Core** ORM)和**MongoDB**.
- 有一个**控制台应用程序**来显示通过身份验证使用服务的最简单方法.
- 使用[Redis](https://redis.io/)进行**分布式缓存**.
- 使用[RabbitMQ](https://www.rabbitmq.com/)进行服务到服务(service-to-service)的**消息传递**.
@ -28,27 +28,27 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
## 路线图
在第一个稳定版本(v1.0)之前还有很多工作要做.可以在GitHub仓库上看到[优先的积压项目](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog).
在第一个稳定版本(v1.0)之前还有很多工作要做.可以在GitHub仓库上看到[优先的积压项目](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog).
根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋.
我们还将完善[文档](https://abp.io/documents/abp/latest),因为它现在还远未完成.
第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将的想法写为对此帖的评论.
第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将的想法写为对此帖的评论.
## 中文网
中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming).
中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming).
## NDC {London} 2019
很高兴作为合作伙伴参加[NDC {London}](https://ndc-london.com/)2019 .我们已经与许多开发人员讨论过当前的ASP.NET Boilerplate和ABP vNext,我们得到了很好的反馈.
我们还有机会与[Scott Hanselman](https://twitter.com/shanselman)和[Jon Galloway](https://twitter.com/jongalloway)交谈.他们参观了我们的展位,我们谈到了ABP vNext的想法.他们喜欢新的ABP框架的功能,方法和目标.在twitter上查看一些照片和评论
我们还有机会与[Scott Hanselman](https://twitter.com/shanselman)和[Jon Galloway](https://twitter.com/jongalloway)交谈.他们参观了我们的展位,我们谈到了ABP vNext的想法.他们喜欢新的ABP框架的功能,方法和目标.在twitter上查看一些照片和评论:
![scott-and-jon](scott-and-jon.png)
## 跟上步伐
* 您可以标星并关注**GitHub**存储库:https://github.com/abpframework/abp
* 您可以关注官方**Twitter**帐户获取新闻:https://twitter.com/abpframework
* 你可以标星并关注**GitHub**存储库:https://github.com/abpframework/abp
* 你可以关注官方**Twitter**帐户获取新闻:https://twitter.com/abpframework

@ -4,7 +4,7 @@ ABP v0.18已发布, 包含解决的[80+个issue](https://github.com/abpframework
## 网站更改
[abp.io](https://abp.io)网站**完全更新**以突出ABP框架的目标和重要功能.文档和博客网址也会更改
[abp.io](https://abp.io)网站**完全更新**以突出ABP框架的目标和重要功能.文档和博客网址也会更改:
- `abp.io/documents`移至[docs.abp.io](https://docs.abp.io).
- `abp.io/blog`转移到[blog.abp.io](https://blog.abp.io).
@ -21,25 +21,25 @@ ABP CLI现在是创建新项目的首选方式,你仍然可以从[开始](https:
### 用法
使用命令行窗口安装ABP CLI
使用命令行窗口安装ABP CLI:
```` bash
dotnet tool install -g Volo.Abp.Cli
````
创建一个新应用程序
创建一个新应用程序:
```` bash
abp new Acme.BookStore
````
将模块添加到应用程序
将模块添加到应用程序:
```` bash
abp add-module Volo.Blogging
````
更新解决方案中所有与ABP相关的包
更新解决方案中所有与ABP相关的包:
```` bash
abp update
@ -59,7 +59,7 @@ abp update
## 更改日志
以下是此版本附带的一些其他功能和增强功能
以下是此版本附带的一些其他功能和增强功能:
* 新[Volo.Abp.Dapper](https://www.nuget.org/packages/Volo.Abp.Dapper)包.
* 新[Volo.Abp.Specifications](https://www.nuget.org/packages/Volo.Abp.Specifications)包.

@ -14,12 +14,12 @@ ABP v0.19已发布,包含解决的[~90个问题](https://github.com/abpframework
* 更新了[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)和[下载页面](https://abp.io/get-started),以便能够使用新的UI选项生成项目.
* 创建了[教程](https://docs.abp.io/en/abp/latest/Tutorials/Angular/Part-I)以使用新的UI选项快速入门.
我们基于最新的Angular工具和趋势创建了模板,文档和基础架构
我们基于最新的Angular工具和趋势创建了模板,文档和基础架构:
* 使用[NgBootstrap](https://ng-bootstrap.github.io/)和[PrimeNG](https://www.primefaces.org/primeng/)作为UI组件库.你可以使用自己喜欢的库,没问题,但预构建的模块可以使用这些库.
* 使用[NGXS](https://ngxs.gitbook.io/ngxs/)作为状态管理库.
Angular是第一个SPA UI选项,但它不是最后一个.在v1.0发布之后,我们将开始第二个UI选项的工作.虽然尚未决定,但候选的有Blazor,React和Vue.js. 等待你的反馈.你可以使用以下issue进行投票(thumb)
Angular是第一个SPA UI选项,但它不是最后一个.在v1.0发布之后,我们将开始第二个UI选项的工作.虽然尚未决定,但候选的有Blazor,React和Vue.js. 等待你的反馈.你可以使用以下issue进行投票(thumb):
* [Blazor](https://github.com/abpframework/abp/issues/394)
* [Vue.js](https://github.com/abpframework/abp/issues/1168)

@ -14,8 +14,8 @@ ABP框架越来越接近v1.0.我们打算在今年10月中旬发布1.0. 现在,
## Techorama荷兰2019
[Techorama NL](https://techorama.nl/)是欧洲最大的会议之一.今年,Volosoft是会议的赞助商,并将有一个展位与软件开发人员讨论ABP框架和软件开发.我们的展位墙如下图所示
[Techorama NL](https://techorama.nl/)是欧洲最大的会议之一.今年,Volosoft是会议的赞助商,并将有一个展位与软件开发人员讨论ABP框架和软件开发.我们的展位墙如下图所示:
![volosoft-booth](volosoft-booth.png)
如果您也参加会议,请到展位讨论ABP框架.我们还为您准备了一些私货:)
如果你也参加会议,请到展位讨论ABP框架.我们还为你准备了一些私货:)

@ -24,7 +24,7 @@
我们终于完成了**react native移动应用程序**.目前,它可以让你**登录**,管理**用户**和**租户**.它利用ABP框架相同的设置,授权和本地化系统.
应用程序的一些截图
应用程序的一些截图:
![mobile-ui](react-native-ui.png)
@ -34,7 +34,7 @@
从我们的Angular应用程序中调用服务器中的REST端点是很常见的.这种情况下,我们一般创建**服务**(在服务器上包含各个服务的方法)和**模型对象**(对应服务器上的[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)).
除了手动创建这样的与服务器交互的服务外,我们可以使用像[NSWAG](https://github.com/RicoSuter/NSwag)工具来为我们生成服务代理.但是NSWAG有以下几个我们遇到的问题
除了手动创建这样的与服务器交互的服务外,我们可以使用像[NSWAG](https://github.com/RicoSuter/NSwag)工具来为我们生成服务代理.但是NSWAG有以下几个我们遇到的问题:
* 它产生一个**大,单一**的.ts文件;
* 当你的应用程序增长时,它变得**太大**了.
@ -58,12 +58,12 @@ abp generate-proxy
### 添加模块的源代码
应用程序启动模板带有一些[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index), 以**Nuget和NPM包**的方式**预先安装了** .这样做有几个重要的优点
应用程序启动模板带有一些[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index), 以**Nuget和NPM包**的方式**预先安装了** .这样做有几个重要的优点:
* 当新版本可用时, 你可以 **轻松地[升级](https://docs.abp.io/en/abp/latest/CLI#update)** 这些模块.
* 你的解决方案**更干净**,这样你就可以专注于自己的代码.
但是,当你需要对一个依赖的模块**大量定制**时,就不如它的代码在你的应用程序中那么容易.为了解决这个问题,我们引入了一个[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)的新命令, 在你的解决方案中用代码**替换**Nuget包.用法很简单
但是,当你需要对一个依赖的模块**大量定制**时,就不如它的代码在你的应用程序中那么容易.为了解决这个问题,我们引入了一个[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)的新命令, 在你的解决方案中用代码**替换**Nuget包.用法很简单:
````bash
abp add-module --with-source-code
@ -75,19 +75,19 @@ abp add-module --with-source-code
此外,我们也创建了文档来说明如何定制依赖的模块而不改变它们的源代码(见下面的部分).仍然建议以包的方式使用模块,以便在以后可以轻松升级.
> 免费模块的源代码是**MIT**许可,所以你可以自由更改它们并添加到的解决方案中.
> 免费模块的源代码是**MIT**许可,所以你可以自由更改它们并添加到的解决方案中.
### 切换到预览版
ABP框架正在迅速发展,我们经常发布新版本.不过,如果你想更紧密地追随它,你可以使用**每日预览包**.
我们创建了一个ABP CLI命令来轻松地为你的解决方案**更新到最新的预览包**.在你的解决方案的根文件夹中运行以下命令
我们创建了一个ABP CLI命令来轻松地为你的解决方案**更新到最新的预览包**.在你的解决方案的根文件夹中运行以下命令:
````bash
abp switch-to-preview
````
它会修改所有ABP相关的NuGet和NPM包的版本.当你需要时你也可以**切换回最新稳定版**
它会修改所有ABP相关的NuGet和NPM包的版本.当你需要时你也可以**切换回最新稳定版**:
````bash
abp switch-to-stable
@ -131,7 +131,7 @@ abp switch-to-stable
## 下一步?
我们未来几个月的目标如下
我们未来几个月的目标如下:
* 完成**文档和示例**,写更多的教程.
* 使框架和现有模块的更加**可定制和可扩展**.

@ -41,12 +41,12 @@ abp new Acme.BookStore
* `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有:
* `app` (default): [应用程序模板](Startup-Templates/Application.md). 其他选项:
* `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项
* `mvc`: ASP.NET Core MVC.此模板的其他选项
* `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项:
* `mvc`: ASP.NET Core MVC.此模板的其他选项:
* `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的.如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景.
* `angular`: Angular. 这个模板还有一些额外的选项
* `angular`: Angular. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `none`: 无UI. 这个模板还有一些额外的选项
* `none`: 无UI. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `--mobile` 或者 `-m`: 指定移动应用程序框架. 默认框架是 `react-native`. 其他选项:
* `none`: 不包含移动应用程序.
@ -57,7 +57,7 @@ abp new Acme.BookStore
* `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,会希望使用最新的版本.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate``https://<your url>.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串!
@ -186,9 +186,9 @@ abp generate-proxy [options]
#### Options
* `--apiUrl` 或者 `-a`指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注!
* `--module` 或者 `-m`指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
示例:

@ -50,7 +50,7 @@ ObjectExtensionManager.Instance
* 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名).
* 你还需要提供一个使用[EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)定义数据库映射属性的操作.
> 必须在使用相关的 `DbContext` 之前执行此代码. 应用程序启动模板定义了一个名为 `YourProjectNameEntityExtensions` 的静态类. 你可以在此类中定义扩展确保在正确的时间执行它. 否则你需要自己处理.
> 必须在使用相关的 `DbContext` 之前执行此代码. 应用程序启动模板定义了一个名为 `YourProjectNameEfCoreEntityExtensionMappings` 的静态类. 你可以在此类中定义扩展确保在正确的时间执行它. 否则你需要自己处理.
定义实体扩展后你需要使用EF Core的[Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration)和[Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database)命令来创建code first迁移类并更新数据库.

@ -54,7 +54,7 @@ context.Services.Replace(
## 重写一个服务类
大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法
大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法.
### 示例: 重写服务方法

@ -72,7 +72,7 @@ public class BookAppService : ApplicationService, IBookAppService
* `BookAppService` 注入图书实体的默认[仓库](Repositories.md),使用`InsertAsync`方法插入 `Book` 到数据库中.
* `GuidGenerator`类型是 `IGuidGenerator`,它是在`ApplicationService`基类中定义的属性. ABP将这样常用属性预注入,所以不需要手动[注入](Dependency-Injection.md).
* 如果您想遵循DDD最佳实践请参阅下面的*聚合示例*部分.
* 如果你想遵循DDD最佳实践,请参阅下面的*聚合示例*部分.
### 具有复合键的实体
@ -228,7 +228,7 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
## 基类和接口的审计属性
有一些属性,像`CreationTime``CreatorId``LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**.
有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**.
### 审计接口
@ -285,7 +285,7 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
所有这些基类都有非泛型版本,可以使用 `AuditedEntity``FullAuditedAggregateRoot` 来支持复合主键;
所有这些基类也有 `... WithUser``FullAuditedAggregateRootWithUser<TUser>``FullAuditedAggregateRootWithUser<TKey, TUser>`. 这样就可以将导航属性添加到你的用户实体. 但在聚合根之间添加导航属性不是一个好做法,所以这种用法是不建议的(除非你使用EF Core之类的ORM可以很好地支持这种情况,并且你真的需要它. 请记住这种方法不适用于NoSQL数据库(如MongoDB),你必须真正实现聚合模式).
所有这些基类也有 `... WithUser`,`FullAuditedAggregateRootWithUser<TUser>``FullAuditedAggregateRootWithUser<TKey, TUser>`. 这样就可以将导航属性添加到你的用户实体. 但在聚合根之间添加导航属性不是一个好做法,所以这种用法是不建议的(除非你使用EF Core之类的ORM可以很好地支持这种情况,并且你真的需要它. 请记住这种方法不适用于NoSQL数据库(如MongoDB),你必须真正实现聚合模式).
## 额外的属性

@ -1,7 +1,7 @@

# EF Core数据库迁移
本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论可能希望为自己的应用程序实现的**各种场景**.
本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论可能希望为自己的应用程序实现的**各种场景**.
> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md).
@ -95,7 +95,7 @@ Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids";
这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ).
**每个模块都使用自己的 `DbContext` 类**来访问数据库同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体:
**每个模块都使用自己的 `DbContext` 类**来访问数据库.同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体:
````csharp
[ConnectionStringName("Default")]
@ -268,10 +268,10 @@ public class BackgroundJobsDbContext
##### 重用模块的表
可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择:
可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择:
1. 你可以**直接使用模块定义的实体**(你仍然可以在某种程度上[扩展实体](Customizing-Application-Modules-Extending-Entities.md)).
2. 你可以**创建一个新的实体**映射到同一个数据库表
2. 你可以**创建一个新的实体**映射到同一个数据库表.
###### 使用由模块定义的实体
@ -307,7 +307,7 @@ namespace Acme.BookStore
示例注入了 `IRepository<IdentityUser,Guid>`(默认仓储). 它定义了标准的存储库方法并实现了 `IQueryable` 接口.
另外身份模块定义了 `IIdentityUserRepository`(自定义仓储),你的应用程序也可以注入和使用它. `IIdentityUserRepository``IdentityUser` 实体提供了额外的定制方法,但它没有实现 `IQueryable`.
另外,身份模块定义了 `IIdentityUserRepository`(自定义仓储),你的应用程序也可以注入和使用它. `IIdentityUserRepository``IdentityUser` 实体提供了额外的定制方法,但它没有实现 `IQueryable`.
###### 创建一个新的实体
@ -352,7 +352,7 @@ namespace Acme.BookStore.Roles
* 它继承了[`AggregateRoot<Guid>`类](Entities.md)和实现了[`IMultiTenant`]接口(Multi-Tenancy.md),因为 `IdentityRole` 也做了同样的继承.
* 你可以添加 `IdentityRole` 实体定义的任何属性. 本例只加了 `TenantId``Name` 属性,因为我们这里只需要它们. 你可以把setters设置为私有(如同本例)以防意外更改身份模块的属性.
* 你可以添加自定义(附加)属性. 本例添加了 `Title` 属性.
* **构造函数是私有的**,所以它不允许直接创建一个新的 `AppRole` 实体创建角色身份模块的责任. 你可以查询角色,设置/更新自定义属性,但做为最佳实践你不应该在代码中创建和删除角色(尽管没有强制的限制).
* **构造函数是私有的**,所以它不允许直接创建一个新的 `AppRole` 实体.创建角色身份模块的责任. 你可以查询角色,设置/更新自定义属性,但做为最佳实践你不应该在代码中创建和删除角色(尽管没有强制的限制).
现在是时候定义EF Core映射. 打开应用程序的 `DbContext` (此示例中是 `BookStoreDbContext` )添加以下属性:
@ -360,7 +360,7 @@ namespace Acme.BookStore.Roles
public DbSet<AppRole> Roles { get; set; }
````
然后在 `OnModelCreating` 方法中配置映射(调用 `base.OnModelCreating(builder)` 之后)
然后在 `OnModelCreating` 方法中配置映射(调用 `base.OnModelCreating(builder)` 之后):
````csharp
protected override void OnModelCreating(ModelBuilder builder)

@ -12,12 +12,12 @@
## UseMySQL()
查找你的解决方案中 `UseSqlServer()`调用替换为 `UseMySQL()`. 检查下列文件:
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseMySQL()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构你可能发现更多需要改变代码的文件.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
## 更改连接字符串
@ -27,7 +27,7 @@ MySQL连接字符串与SQL Server连接字符串不同. 所以检查你的解决
## 更改迁移DbContext
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表.

@ -63,7 +63,7 @@ MySQL连接字符串与SQL Server连接字符串不同. 所以检查你的解决
## 更改迁移DbContext
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表.

@ -12,12 +12,12 @@
## UsePostgreSql()
查找你的解决方案中 `UseSqlServer()`调用替换为 `UsePostgreSql()`. 检查下列文件:
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UsePostgreSql()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构你可能发现更多需要改变代码的文件.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
## 更改连接字符串

@ -12,12 +12,12 @@
## UseSqlite()
查找你的解决方案中 `UseSqlServer()`调用替换为 `UseSqlite()`. 检查下列文件:
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseSqlite()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构你可能发现更多需要改变代码的文件.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
## 更改连接字符串

@ -108,7 +108,7 @@ protected override void OnModelCreating(ModelBuilder builder)
### 配置连接字符串选择
如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称.
:
```csharp
[ConnectionStringName("MySecondConnString")]
@ -274,7 +274,7 @@ public override async Task DeleteAsync(
## 访问 EF Core API
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例:
````csharp
public class BookService
@ -304,7 +304,7 @@ public class BookService
默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中.
实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档
实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档:
* [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md)
* [实体](Entities.md)
@ -313,7 +313,7 @@ public class BookService
### ObjectExtensionManager.Instance
`ObjectExtensionManager` 实现单例模式,因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作。
`ObjectExtensionManager` 实现单例模式,因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作.
### MapEfCoreProperty
@ -417,7 +417,7 @@ context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
});
````
现在,的自定义仓储也可以使用`IBookStoreDbContext`接口:
现在,的自定义仓储也可以使用`IBookStoreDbContext`接口:
````csharp
public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>, IBookRepository

@ -295,8 +295,8 @@ services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
框架会自动抛出以下异常类型:
- 当用户没有权限执行操作时,会抛出 `AbpAuthorizationException` 异常. 有关更多信息,请参阅授权文档(TODO:link).
- 如果当前请求的输入无效,则抛出`AbpValidationException 异常`. 有关更多信息,请参阅授权文档(TODO:link).
- 当用户没有权限执行操作时,会抛出 `AbpAuthorizationException` 异常. 有关更多信息,请参阅授权文档[authorization](Authorization.md).
- 如果当前请求的输入无效,则抛出`AbpValidationException 异常`. 有关更多信息,请参阅[验证文档](Validation.md).
- 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常大多数由 [repositories](Repositories.md) 抛出.
你同样可以在代码中抛出这些类型的异常(虽然很少需要这样做)

@ -156,7 +156,7 @@ services.AddApplication<AppModule>(options =>
});
````
4. 更新 `Program.cs`代码, 不再使用`WebHost.CreateDefaultBuilder()`方法(因为它使用默认的DI容器)
4. 更新 `Program.cs`代码, 不再使用`WebHost.CreateDefaultBuilder()`方法(因为它使用默认的DI容器):
````csharp
public class Program

@ -4,7 +4,7 @@
本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面.
如果你之前未安装请使用命令行安装ABP CLI:
如果你之前未安装,请使用命令行安装ABP CLI:
````bash
dotnet tool install -g Volo.Abp.Cli

@ -42,14 +42,14 @@ public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetE
{
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey"))
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey("XsrfKey"))
if (!items.ContainsKey(XsrfKey))
{
return null;
}

@ -12,7 +12,7 @@ ABP是一个**开源应用程序框架**,专注于基于ASP.NET Core的Web应用
* [ASP.NET Core MVC 模板](Getting-Started-AspNetCore-MVC-Template.md)
如果想从头开始(使用空项目),请手动安装ABP框架并使用以下教程:
如果想从头开始(使用空项目),请手动安装ABP框架并使用以下教程:
* [控制台应用程序](Getting-Started-Console-Application.md)
* [ASP.NET Core Web 应用程序](Getting-Started-AspNetCore-Application.md)

@ -326,7 +326,7 @@ There are no projects yet!
{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs/zh-Hans/","GitHubAccessToken":"***","GitHubUserAgent":""}
```
注意 `GitHubAccessToken``***` 掩盖. 这是一个私人令牌你必须从GitHub获取它. 请参阅 https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
注意 `GitHubAccessToken``***` 掩盖. 这是一个私人令牌,你必须从GitHub获取它. 请参阅 https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
- MainWebsiteUrl: `/`
@ -338,7 +338,7 @@ There are no projects yet!
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""}', N'/', N'master', N'')
```
请注意`GitHubAccessToken` 被屏蔽了.它是一个私人令牌,你必须获得自己的令牌并替换 `***` 字符串.
请注意,`GitHubAccessToken` 被屏蔽了.它是一个私人令牌,你必须获得自己的令牌并替换 `***` 字符串.
现在你可以运行应用程序并导航到 `/Documents`.
@ -378,9 +378,9 @@ INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocume
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'')
```
添加上面的一个示例项目后运行该应用程序. 在菜单中你会看到`文档` 链接点击菜单链接打开文档页面.
添加上面的一个示例项目后运行该应用程序. 在菜单中你会看到`文档` 链接,点击菜单链接打开文档页面.
到目前为止, 我们已经从abp.io网站创建了一个新的应用程序并为Docs模块做好准备.
到目前为止, 我们已经从abp.io网站创建了一个新的应用程序,并为Docs模块做好准备.
### 7- 添加一个新文档
@ -454,7 +454,7 @@ public class Person
}
```
因为并不是项目中的每个文档都有章节或者不需要所有的参数,你必须声明哪些参数将用于对文档进行分段在文档的任何地方都可以使用JSON块.
因为并不是项目中的每个文档都有章节或者不需要所有的参数,你必须声明哪些参数将用于对文档进行分段,在文档的任何地方都可以使用JSON块.
例如 [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md):
@ -570,7 +570,7 @@ This document assumes that you prefer to use **{{ UI_Value }}** as the UI framew
![Navigation menu](../images/docs-module_download-sample-navigation-menu.png)
最后,为您的项目添加了一个新的Docs模块, 该模块由GitHub提供.
最后,为你的项目添加了一个新的Docs模块, 该模块由GitHub提供.
## 全文搜索(Elastic Search)

@ -172,11 +172,11 @@ namespace MyCompany.MyProject
{
options.Tenants = new[]
{
new TenantInformation(
new TenantConfiguration(
Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"), //Id
"tenant1" //Name
),
new TenantInformation(
new TenantConfiguration(
Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"), //Id
"tenant2" //Name
)
@ -252,7 +252,7 @@ TODO: This package implements ITenantStore using a real database...
#### 租户信息
ITenantStore跟 **TenantInformation**类一起工作,并且包含了几个租户属性:
ITenantStore跟 **TenantConfiguration**类一起工作,并且包含了几个租户属性:
* **Id**:租户的唯一Id.
* **Name**: 租户的唯一名称.

@ -62,7 +62,7 @@ if (user.GetProperty<bool>("IsSuperUser"))
##### 非基本属性类型
如果您的属性类型不是原始类型(int,bool,枚举,字符串等),你需要使用 `GetProperty` 的非泛型版本,它会返回 `object`.
如果你的属性类型不是原始类型(int,bool,枚举,字符串等),你需要使用 `GetProperty` 的非泛型版本,它会返回 `object`.
#### HasProperty
@ -180,7 +180,7 @@ ObjectExtensionManager.Instance
假设你已向可扩展的实体对象添加了额外的属性并使用了自动[对象到对象的映射](Object-To-Object-Mapping.md)将该实体映射到可扩展的DTO类. 在这种情况下你需要格外小心,因为额外属性可能包含**敏感数据**,这些数据对于客户端不可用.
本节提供了一些**好的做法**,可以控制对象映射的额外属性
本节提供了一些**好的做法**,可以控制对象映射的额外属性.
### MapExtraPropertiesTo
@ -232,7 +232,7 @@ identityUser.MapExtraPropertiesTo(
#### AutoMapper集成
如果使用的是[AutoMapper](https://automapper.org/)库,ABP框架还提供了一种扩展方法来利用上面定义的 `MapExtraPropertiesTo` 方法.
如果使用的是[AutoMapper](https://automapper.org/)库,ABP框架还提供了一种扩展方法来利用上面定义的 `MapExtraPropertiesTo` 方法.
你可以在映射配置文件中使用 `MapExtraProperties()` 方法.
@ -247,7 +247,7 @@ public class MyProfile : Profile
}
````
它与 `MapExtraPropertiesTo()` 方法具有相同的参数
它与 `MapExtraPropertiesTo()` 方法具有相同的参数.
## Entity Framework Core 数据库映射

@ -168,7 +168,7 @@ public class MyProfile : Profile
假设你已经创建了一个**可重用的模块**,其中定义了AutoMapper配置文件,并在需要映射对象时使用 `IObjectMapper`. 根据[模块化](Module-Development-Basics.md)的性质,你的模块可以用于不同的应用程序.
`IObjectMapper` 是一个抽象,可以由最终应用程序替换使用另一个映射库. 这里的问题是你的可重用模块设计为使用AutoMapper因为它为其定义映射配置文件. 这种情况下即使最终应用程序使用另一个默认对象映射库,你也要保证模块始终使用AutoMapper.
`IObjectMapper` 是一个抽象,可以由最终应用程序替换使用另一个映射库. 这里的问题是你的可重用模块设计为使用AutoMapper,因为它为其定义映射配置文件. 这种情况下即使最终应用程序使用另一个默认对象映射库,你也要保证模块始终使用AutoMapper.
`IObjectMapper<TContext>`将对象映射器上下文化,你可以为不同的 模块/上下文 使用不同的库.
@ -201,7 +201,7 @@ public class UserAppService : ApplicationService
`UserAppService` 注入 `IObjectMapper<MyModule>`, 它是模块的特定对象映射器,用法与 `IObjectMapper` 完全相同.
上面的示例代码未使用 `ApplicationService` 中定义的 `ObjectMapper` 属性而是注入了 `IObjectMapper<MyModule>`. 但是 `ApplicationService` 定义了可以在类构造函数中设置的 `ObjectMapperContext` 属性, 因此仍然可以使用基类属性. 示例可以进行以下重写:
上面的示例代码未使用 `ApplicationService` 中定义的 `ObjectMapper` 属性,而是注入了 `IObjectMapper<MyModule>`. 但是 `ApplicationService` 定义了可以在类构造函数中设置的 `ObjectMapperContext` 属性, 因此仍然可以使用基类属性. 示例可以进行以下重写:
````csharp
public class UserAppService : ApplicationService

@ -9,7 +9,7 @@ ABP框架遵循选项模式,并定义了用于配置框架和模块的选项类(
## 配置选项
通常配置选项在 `Startup` 类的 `ConfigureServices` 方法中. 但由于ABP框架提供了模块化基础设施,因此你可以在[模块](Module-Development-Basics.md)的`ConfigureServices` 方法配置选项.
:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -34,7 +34,7 @@ public class MyOptions
}
````
然后开发人员可以像上面 `AbpAuditingOptions` 示例一样配置你的选项
然后开发人员可以像上面 `AbpAuditingOptions` 示例一样配置你的选项:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -80,9 +80,9 @@ public class MyService : ITransientDependency
如果你正在开发一个模块,可能需要让开发者能够设置一些选项,并在依赖注入注册阶段使用这些选项. 你可能需要根据选项值配置其他服务或更改依赖注入的注册代码.
对于此类情况,ABP为 `IServiceCollection` 引入了 `PreConfigure<TOptions>``ExecutePreConfiguredActions<TOptions>` 扩展方法. 该模式的工作原理如下所述
对于此类情况,ABP为 `IServiceCollection` 引入了 `PreConfigure<TOptions>``ExecutePreConfiguredActions<TOptions>` 扩展方法. 该模式的工作原理如下所述.
1. 你的模块中定义计划选项类. 例
1. 你的模块中定义计划选项类. 例:
````csharp
public class MyPreOptions
@ -92,7 +92,7 @@ public class MyPreOptions
````
然后任何依赖于模块的模块类都可以在其 `PreConfigureServices` 方法中使用 `PreConfigure<TOptions>` 方法.
:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)

@ -51,13 +51,13 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服
### 创建数据库
MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库的结构。其实你可以很轻松的创建数据库,因为这个解决方案配置了使用 Entity Core Code First 来做迁移。
MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库的结构.其实你可以很轻松的创建数据库,因为这个解决方案配置了使用 Entity Core Code First 来做迁移.
这个解决方案中有两个 SQL server 数据库
这个解决方案中有两个 SQL server 数据库.
#### MsDemo_Identity 数据库
* 右键 `AuthServer.Host` 项目然后点击 `设置为启动项目`.
* 右键 `AuthServer.Host` 项目,然后点击 `设置为启动项目`.
* 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台)
* 选择 `AuthServer.Host` 成为 **默认项目**.
* 执行 `Update-Database` 命令.
@ -66,7 +66,7 @@ MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库
#### MsDemo_ProductManagement
* 右键 `ProductService.Host` 项目然后点击 `设置为启动项目`.
* 右键 `ProductService.Host` 项目,然后点击 `设置为启动项目`.
* 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台)
* 选择 `ProductService.Host` 成为 **默认项目**.
* 执行 `Update-Database` 命令.
@ -334,7 +334,7 @@ context.Services.AddAuthentication(options =>
- 它需要额外的身份范围 *role*, *email* and *phone*.
- 它需要API资源范围 *PublicWebSiteGateway*,*BloggingService*和*ProductService*,因为它将这些服务用作API.
IdentityServer客户端设置存储在`appsettings.json`文件中
IdentityServer客户端设置存储在`appsettings.json`文件中:
```json
"AuthServer": {
@ -348,7 +348,7 @@ IdentityServer客户端设置存储在`appsettings.json`文件中:
PublicWebSite.Host项目有一个列出产品的页面 (`Pages/Products.cshtml`). 它还使用博客模块中的UI. 为此`PublicWebSiteHostModule`加入了`BloggingWebModule`(*[Volo.Blogging.Web](https://www.nuget.org/packages/Volo.Blogging.Web)* 包)的依赖项.
产品页面的屏幕截图
产品页面的屏幕截图:
![microservice-sample-public-product-list](../images/microservice-sample-public-product-list.png)
@ -390,7 +390,7 @@ PublicWebSite.Host项目有一个列出产品的页面 (`Pages/Products.cshtml`)
#### 远程服务配置
`appsettings.json`文件中的`RemoteService`配置很简单
`appsettings.json`文件中的`RemoteService`配置很简单:
````json
"RemoteServices": {
@ -418,7 +418,7 @@ PublicWebSite.Host项目有一个列出产品的页面 (`Pages/Products.cshtml`)
}
````
此示例使用`client_credentials` 授予类型,该类型需要`ClientId`和`ClientSecret`进行身份验证过程. 还有[其他授予类型](http://docs.identityserver.io/en/latest/topics/grant_types.html). 例如, 你可以使用以下配置切换到`password`(Resource Owner Password)授予类型
此示例使用`client_credentials` 授予类型,该类型需要`ClientId`和`ClientSecret`进行身份验证过程. 还有[其他授予类型](http://docs.identityserver.io/en/latest/topics/grant_types.html). 例如, 你可以使用以下配置切换到`password`(Resource Owner Password)授予类型:
````json
"IdentityClients": {
@ -571,7 +571,7 @@ app.UseOcelot().Wait();
#### 权限管理
后端管理应用程序提供权限管理UI(之前见过),并使用此网关获取/设置权限. 权限管理API托管在网关内,而不是单独的服务. 这是一个设计决策,但如果愿意,它可以作为另一个微服务托管.
后端管理应用程序提供权限管理UI(之前见过),并使用此网关获取/设置权限. 权限管理API托管在网关内,而不是单独的服务. 这是一个设计决策,但如果愿意,它可以作为另一个微服务托管.
#### Dependencies
@ -1229,7 +1229,7 @@ public class ProductCodeAlreadyExistsException : BusinessException
}
````
`PM000001`是发送给客户端的异常类型的代码,因此他们可以理解错误类型. 在这种情况下没有实现,但也可以本地化业务异常. 请参阅[异常处理文档](../Exception-Handling.md).
`PM:000001`是发送给客户端的异常类型的代码,因此他们可以理解错误类型. 在这种情况下没有实现,但也可以本地化业务异常. 请参阅[异常处理文档](../Exception-Handling.md).
#### 应用层
@ -1278,7 +1278,7 @@ public async Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input)
分布式事件(事件总线)是一种消息传递方式,其中服务引发/触发事件,而其他服务注册/侦听这些事件,以便在发生重要事件时得到通知. ABP通过提供约定,服务和集成使分布式事件更易于使用.
已经看到`Product`类使用以下代码行发布事件:
已经看到`Product`类使用以下代码行发布事件:
````csharp
AddDistributedEvent(new ProductStockCountChangedEto(Id, StockCount, stockCount));
@ -1406,7 +1406,7 @@ Kibana URL默认为`http://localhost:5601/`.
ABP提供自动审计日志记录,详细保存每个请求(当前用户,浏览器/客户端,执行了哪些操作,哪些实体更改,甚至实体的哪些属性已更新). 有关详细信息,请参阅[审计日志文档](../Audit-Logging.md).
所有服务和应用程序都配置为编写审核日志. 审核日志将保存到MsDemo_Identity SQL数据库中. 因此,可以从单个点查询所有应用程序的所有审核日志.
所有服务和应用程序都配置为编写审核日志. 审核日志将保存到MsDemo_Identity SQL数据库中. 因此,可以从单个点查询所有应用程序的所有审核日志.
审核日志记录具有`CorrelationId`属性,可用于跟踪请求. 当服务在单个Web请求中调用另一个服务时,它们都会使用相同的`CorrelationId`保存审核日志. 请参阅数据库中的`AbpAuditLogs`表.

@ -226,6 +226,6 @@ Configure<AbpSettingOptions>(options =>
## 设置管理模块
设置系统核心是相当独立的,不做任何关于如何管理(更改)设置值的假设. 默认的`ISettingStore`实现也是`NullSettingStore`它为所有设置值返回null.
设置系统核心是相当独立的,不做任何关于如何管理(更改)设置值的假设. 默认的`ISettingStore`实现也是`NullSettingStore`,它为所有设置值返回null.
设置管理模块通过管理数据库中的设置值来完成逻辑(实现`ISettingStore`).有关更多信息参阅[设置管理模块](Modules/Setting-Management.md)学习更多.

@ -0,0 +1,3 @@
## 规约
TODO..

@ -348,7 +348,7 @@ Home模块是一个可延迟加载的模块, 它加载应用程序的根地址.
React 本机应用程序是用 [Expo](https://expo.io/)生成的. Expo 是一套基于 React Native 构建的工具, 帮助你快速启动一个应用程序, 尽管它有很多功能.
React Native 应用文件夹结构, 如下图所示
React Native 应用文件夹结构, 如下图所示:
![react-native-folder-structure](../images/react-native-folder-structure.png)
@ -362,7 +362,7 @@ React Native 应用文件夹结构, 如下图所示:
#### Components组件
可以在所有屏幕上使用的组件是在 `src/components` 文件夹中创建的. 所有组件都是作为一个能够使用 [hooks](https://reactjs.org/docs/hooks-intro.html) 的函数创建的.
可以在所有屏幕上使用的组件是在 `src/components` 文件夹中创建的. 所有组件都是作为一个能够使用 [hooks](https://reactjs.org/docs/hooks-intro.html) 的函数创建的.
#### Screens屏幕
@ -378,50 +378,50 @@ Screens 是通过在 `src/screens` 文件夹中创建将名称分开的文件夹
#### State Management状态管理
[Redux](https://redux.js.org/) 被用作状态管理库. [Redux Toolkit](https://redux-toolkit.js.org/) 库被用作高效Redux开发的工具集.
[Redux](https://redux.js.org/) 被用作状态管理库. [Redux Toolkit](https://redux-toolkit.js.org/) 库被用作高效Redux开发的工具集.
`src/store` 文件夹中创建 Actions, reducers, sagas, selectors. 存储文件夹如下
`src/store` 文件夹中创建 Actions, reducers, sagas, selectors. 存储文件夹如下:
![react-native-store-folder](../images/react-native-store-folder.png)
* [**Store**](https://redux.js.org/basics/store) 在 `src/store/index.js` 文件中定义.
* [**Actions**](https://redux.js.org/basics/actions/) 是将数据从应用程序发送到存储的有效信息负载.
* [**Reducers**](https://redux.js.org/basics/reducers) 指定应用程序的状态如何更改以响应发送到存储的操作.
* [**Redux-Saga**](https://redux-saga.js.org/) 是一个库, 旨在使应用程序的副作用(即异步的事情, 如数据获取和不纯的事情, 如访问浏览器缓存)更容易管理. Sagas 是在 `src/store/sagas` 文件夹中创建的.
* [**Redux-Saga**](https://redux-saga.js.org/) 是一个库, 旨在使应用程序的副作用(即异步的事情, 如数据获取和不纯的事情, 如访问浏览器缓存)更容易管理. Sagas 是在 `src/store/sagas` 文件夹中创建的.
* [**Reselect**](https://github.com/reduxjs/reselect) 库用于创建缓存的选择器. 选择器是在 `src/store/selectors` 文件夹中创建的.
#### APIs
[Axios](https://github.com/axios/axios) 用作HTTP客户端库. Axios 实例从 `src/api/API.js` 导出 . 使用相同的配置进行HTTP调用. `src/api` 文件夹中还有为 API 调用创建的 API 文件.
[Axios](https://github.com/axios/axios) 用作HTTP客户端库. Axios 实例从 `src/api/API.js` 导出 . 使用相同的配置进行HTTP调用. `src/api` 文件夹中还有为 API 调用创建的 API 文件.
#### Theming主题
[Native Base](https://nativebase.io/) 被用作UI组件库. 本地基本组件可以很容易地进行自定义。参见 [Native Base customize](https://docs.nativebase.io/Customize.html#Customize) 文档。我们沿着同样的路走。
[Native Base](https://nativebase.io/) 被用作UI组件库. 本地基本组件可以很容易地进行自定义.参见[Native Base customize](https://docs.nativebase.io/Customize.html#Customize) 文档.我们沿着同样的路走.
* Native Base 主题变量在 `src/theme/variables` 文件夹中
* Native Base 组件样式在 `src/theme/components` 文件夹中。这些文件是用 Native Base's `ejectTheme` 脚本生成的。
* 组件样式用 `src/theme/overrides` 文件夹下的文件覆盖
* Native Base 主题变量在 `src/theme/variables` 文件夹中.
* Native Base 组件样式在 `src/theme/components` 文件夹中.这些文件是用 Native Base's `ejectTheme` 脚本生成的.
* 组件样式用 `src/theme/overrides` 文件夹下的文件覆盖.
#### Testing单元测试
将创建单元测试
将创建单元测试.
参见[测试概述](https://reactjs.org/docs/testing.html)文档
参见[测试概述](https://reactjs.org/docs/testing.html)文档.
#### Depended Libraries依赖库
* [Native Base](https://nativebase.io/) 用作UI组件库
* [React Navigation](https://reactnavigation.org/) 用作导航库
* [Axios](https://github.com/axios/axios) 用作HTTP客户端库
* [Redux](https://redux.js.org/) 用作状态管理库
* [Redux Toolkit](https://redux-toolkit.js.org/) 库被用作高效Redux开发的工具集
* [Redux-Saga](https://redux-saga.js.org/) 用于管理异步进程
* [Redux Persist](https://github.com/rt2zz/redux-persist) 被用作状态持久化
* [Reselect](https://github.com/reduxjs/reselect) 用于创建缓存的选择器
* [i18n-js](https://github.com/fnando/i18n-js) 作为国际化库使用
* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) 库可以轻松加载字体
* [Formik](https://github.com/jaredpalmer/formik) 用于构建表单
* [Yup](https://github.com/jquense/yup) 用于表单验证
* [Native Base](https://nativebase.io/) 用作UI组件库.
* [React Navigation](https://reactnavigation.org/) 用作导航库.
* [Axios](https://github.com/axios/axios) 用作HTTP客户端库.
* [Redux](https://redux.js.org/) 用作状态管理库.
* [Redux Toolkit](https://redux-toolkit.js.org/) 库被用作高效Redux开发的工具集.
* [Redux-Saga](https://redux-saga.js.org/) 用于管理异步进程.
* [Redux Persist](https://github.com/rt2zz/redux-persist) 被用作状态持久化.
* [Reselect](https://github.com/reduxjs/reselect) 用于创建缓存的选择器.
* [i18n-js](https://github.com/fnando/i18n-js) 作为国际化库使用.
* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) 库可以轻松加载字体.
* [Formik](https://github.com/jaredpalmer/formik) 用于构建表单.
* [Yup](https://github.com/jquense/yup) 用于表单验证.
## 下一步是什么?

@ -2,7 +2,7 @@
虽然你可以从一个空项目开始并手动添加所需的包,但启动模板可以非常轻松,舒适地使用ABP框架启动新的解决方案.
单击下面列表中的名称以查看相关启动模板的文档
单击下面列表中的名称以查看相关启动模板的文档:
* [**app**](Application.md): 应用程序模板.
* [**module**](Module.md): 模块/服务模板.

@ -33,7 +33,7 @@
- `Acme.BookStore.Domain`包含你的[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities), [领域服务](https://docs.abp.io/zh-Hans/abp/latest/Domain-Services)和其他核心域对象.
- `Acme.BookStore.Domain.Shared`包含可与客户共享的常量,枚举或其他域相关对象.
在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示:
在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示:
````C#
using System;
@ -132,7 +132,7 @@ PM> Update-Database
#### 添加示例数据
`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行以便在页面上显示它们:
`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行,以便在页面上显示它们:
![bookstore-books-table](images/bookstore-books-table.png)
@ -290,7 +290,7 @@ namespace Acme.BookStore
#### Swagger UI
启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用自己的端口替换XXXX)作为URL.
启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用自己的端口替换XXXX)作为URL.
你会看到一些内置的接口和`Book`的接口,它们都是REST风格的:
@ -380,7 +380,7 @@ context.Menu.AddItem(
![bookstore-localization-files](images/bookstore-localization-files-v2.png)
打开`en.json`文件,将`MenuBookStore`和`MenuBooks`键的本地化文本添加到文件末尾:
打开`en.json`文件,将`Menu:BookStore`和`Menu:Books`键的本地化文本添加到文件末尾:
````json
{

@ -14,7 +14,7 @@
### 解决方案中的测试项目
解决方案中有多个测试项目
解决方案中有多个测试项目:
![bookstore-test-projects-v2](images/bookstore-test-projects-v2.png)
@ -68,7 +68,7 @@ namespace Acme.BookStore
````
* 注入`IRepository<Book,Guid>`并在`SeedAsync`中使用它来创建两个书实体作为测试数据.
* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../../../Guid-Generation.md)了解更多信息).
* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试,但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../../../Guid-Generation.md)了解更多信息).
### 测试 BookAppService

@ -1,3 +0,0 @@
## Creating a Settings Tab
TODO...

@ -2,7 +2,7 @@
你可以将一些ABP的组件替换为你自己的自定义组件.
可以**替换**但**不能自定义**默认ABP组件的原因是禁用或更改该组件的一部分可能会导致问题. 所以我们把这些组件称为可替换组件.
可以**替换**但**不能自定义**默认ABP组件的原因是禁用或更改该组件的一部分可能会导致问题. 所以我们把这些组件称为可替换组件.
### 如何替换组件

@ -1,3 +1,294 @@
## 配置状态
TODO...
`ConfigStateService` 是一个单例服务,即在应用程序的根级别提供,用于与 `Store` 中的应用程序配置状态进行交互.
## 使用前
为了使用 `ConfigStateService`,你必须将其注入到你的类中.
```js
import { ConfigStateService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private config: ConfigStateService) {}
}
```
你不必在模块或组件/指令级别提供 `ConfigStateService`,因为它已经在**根中**提供.
## 选择器方法
`ConfigStateService` 有许多选择器方法允许你从 `Store` 获取特定或所有的配置.
### 如何从Store获取所有的配置
你可以使用 `ConfigStateService``getAll` 方法从Store获取所有的配置对象. 用法如下:
```js
// this.config is instance of ConfigStateService
const config = this.config.getAll();
```
### 如何从Store获取特定的配置
你可以使用 `ConfigStateService``getOne` 方法从Store获取特定的配置属性. 你需要将属性名做为参数传递给方法:
```js
// this.config is instance of ConfigStateService
const currentUser = this.config.getOne("currentUser");
```
有时你想要获取具体信息,而不是当前用户. 例如你只想获取到 `tenantId`:
```js
const tenantId = this.config.getDeep("currentUser.tenantId");
```
或通过提供键数组作为参数:
```js
const tenantId = this.config.getDeep(["currentUser", "tenantId"]);
```
`getDeep` 可以执行 `getOne` 的所有操作. 但 `getOne` 的执行效率要高一些.
#### 配置状态属性
请参阅 `Config.State` 类型,你可以通过 `getOne``getDeep` 获取所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L7)中找到.
### 如何从Store获取应用程序信息
`getApplicationInfo` 方法从存储为配置状态存储的环境变量中获取应用程序信息. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const appInfo = this.config.getApplicationInfo();
```
该方法不会返回 `undefined``null`,而是会返回一个空对象(`{}`). 换句话说,当你使用上面代码中的 `appInfo` 属性时,永远不会出现错误.
#### 应用程序信息属性
请参阅 `Config.State` 类型,你可以通过 `getApplicationInfo` 获取所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L21)中找到.
### 如何从Store获取
`getApplicationInfo` 方法从存储为配置状态存储的环境变量中获取特定的API URL. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const apiUrl = this.config.getApiUrl();
// environment.apis.default.url
const searchUrl = this.config.getApiUrl("search");
// environment.apis.search.url
```
该方法返回给定键的特定的API `url`. 如果没有Key,则使用 `default`.
### 如何从Store获取所有的设置
你可以使用 `ConfigStateService``getSettings` 获取配置状态所有的设置对象. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const settings = this.config.getSettings();
```
实际上该方法可以通过**传递关键字**来搜索设置.
```js
const localizationSettings = this.config.getSettings("Localization");
/*
{
'Abp.Localization.DefaultLanguage': 'en'
}
*/
```
请注意, **设置搜索区分大小写**.
### 如何从Store获取特定的设置
你可以使用 `ConfigStateService``getSetting` 获取配置状态特定的设置. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage");
// 'en'
```
### 如何从Store获取特定的权限
你可以使用 `ConfigStateService``getGrantedPolicy` 获取配置状态特定的权限. 你应该将策略key做为参数传递给方法:
```js
// this.config is instance of ConfigStateService
const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity");
// true
```
你还可以使用 **组合策略key** 来微调你的选择:
```js
// this.config is instance of ConfigStateService
const hasIdentityAndAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity && Abp.Account"
);
// false
const hasIdentityOrAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity || Abp.Account"
);
// true
```
创建权限选择器时,请考虑以下**规则**:
- 最多可组合两个键.
- `&&` 操作符查找两个键.
- `||` 操作符查找任意一个键.
- 空字符串 `''` 做为键将返回 `true`
- 使用没有第二个键的操作符将返回 `false`
### 如何从Store中获取翻译
`ConfigStateService``getLocalization` 方法用于翻译. 这里有一些示例:
```js
// this.config is instance of ConfigStateService
const identity = this.config.getLocalization("AbpIdentity::Identity");
// 'identity'
const notFound = this.config.getLocalization("AbpIdentity::IDENTITY");
// 'AbpIdentity::IDENTITY'
const defaultValue = this.config.getLocalization({
key: "AbpIdentity::IDENTITY",
defaultValue: "IDENTITY"
});
// 'IDENTITY'
```
请参阅[本地化文档](./Localization.md)了解详情.
## 分发方法
`ConfigStateService` 有几种分发方法,让你方便地将预定义操作分发到 `Store`.
### 如何从服务器获取应用程序配置
`dispatchGetAppConfiguration` 触发对端点的请求,该端点使用应用程序状态进行响应,然后将此响应作为配置状态放置到 `Store`中.
```js
// this.config is instance of ConfigStateService
this.config.dispatchGetAppConfiguration();
// returns a state stream which emits after dispatch action is complete
```
请注意,**你不必在应用程序启动时调用此方法**,因为在启动时已经从服务器收到了应用程序配置.
### 如何修补路由配置
`dispatchPatchRouteByName` 根据名称查找路由, 并将其在 `Store` 中的配置替换为作为第二个参数传递的新配置.
```js
// this.config is instance of ConfigStateService
const newRouteConfig: Partial<ABP.Route> = {
name: "Home",
path: "home",
children: [
{
name: "Dashboard",
path: "dashboard"
}
]
};
this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
### 如何添加新路由配置
`dispatchAddRoute``Store` 的配置状态添加一个新路由. 应该将路由配置做为方法参数传递.
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
`newRoute` 将被放置在根级别,没有任何父路由,并且其url将存储为 `'/path'`.
如果你想要**添加一个子路由,你可以这样做:**
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
parentName: "AbpAccount::Login",
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
`newRoute` 做为 `'AbpAccount::Login'` 父路由的子路由被放置,它的url被设置为 `'/account/login/page'`.
#### 路由配置属性
请参阅 `ABP.Route` 类型,获取可在参数中传递给 `dispatchSetEnvironment` 的所有属性. 你可以在[common.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27)中找到.
### 如何设置环境
`dispatchSetEnvironment` 将传递给它的环境变量放在 `Store` 中的配置状态下. 使用方法如下:
```js
// this.config is instance of ConfigStateService
this.config.dispatchSetEnvironment({
/* environment properties here */
});
// returns a state stream which emits after dispatch action is complete
```
注意,**你不必在应用程序启动时调用此方法**,因为环境变量已经在启动时存储了.
#### 环境属性
请参阅 `Config.Environment` 类型,获取可在参数中传递给 `dispatchSetEnvironment` 的所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13)中找到.
## 下一步是什么?
* [组件替换](./Component-Replacement.md)

@ -1,6 +1,6 @@
# ContentStrategy
`ContentStrategy` 是@abp/ng.core包暴露出的抽象类. 它可以帮助创建内联脚本或样式.
`ContentStrategy` 是@abp/ng.core包暴露出的抽象类. 它可以帮助创建内联脚本或样式.
## API

@ -76,7 +76,7 @@ setContext(): undefined
CONTEXT_STRATEGY.None()
```
该策略不会将任何上下文传递到投影内容
该策略不会将任何上下文传递到投影内容.
### Component

@ -1,3 +1,140 @@
# Dom插入(Scripts与Styles)
TODO...
你可以使用@abp/ng.core包提供的 `DomInsertionService` 以简单的方式的插入脚本与样式.
## 入门
你不必在模块或组件级别提供 `DomInsertionService` ,因为它已经在**根中**提供. 你可以在组件,指令或服务中直接注入并使用它.
```js
import { DomInsertionService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
}
```
## 用法
你可以使用 `DomInsertionService` 提供的 `insertContent` 方法去创建一个 `<script>``<style>` 元素到DOM的指定位置. 还有 `projectContent` 方法用于渲染组件和模板.
### 如何插入Scripts
`insertContent` 方法的第一个参数需要一个 `ContentStrategy`. 如果传递 `ScriptContentStrategy` 实例, `DomInsertionService` 将创建具有给定内容的 `<script>` 元素并放置在指定的DOM位置.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
const scriptElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendScriptToBody('alert()')
);
}
}
```
在上面的示例中,将 `<script>alert()</script>` 元素放置在 `<body>`的末尾, `scriptElement` 类型是一个 `HTMLScriptElement`.
请参考[ContentStrategy](./Content-Strategy.md)查看所有可用的内容策略以及如何构建自己的内容策略.
> 重要说明: `DomInsertionService` 不会两次插入相同的内容. 为了再次添加内容你首先应该使用 `removeContent` 方法删除旧内容.
### 如何插入Styles
`insertContent` 方法的第一个参数需要一个 `ContentStrategy`. 如果传递 `StyleContentStrategy` 实例, `DomInsertionService` 将创建具有给定内容的 `<style>` 元素并放置在指定的DOM位置.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
const styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
}
```
在上面的示例中,将 `<style>body {margin: 0;}</style>` 元素放置在 `<head>`的末尾, `styleElement` 类型是一个 `HTMLStyleElement`.
请参考[ContentStrategy](./Content-Strategy.md)查看所有可用的内容策略以及如何构建自己的内容策略.
.
> 重要说明: `DomInsertionService` 不会两次插入相同的内容. 为了再次添加内容你首先应该使用 `removeContent` 方法删除旧内容.
### 如何删除已插入的 Scripts & Styles
如果你传递 `HTMLScriptElement``HTMLStyleElement` 做为 `removeContent` 方法的第一个参数, `DomInsertionService` 将删除给定的元素.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
private styleElement: HTMLStyleElement;
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
ngOnDestroy() {
this.domInsertionService.removeContent(this.styleElement);
}
}
```
在上面的示例中,销毁组件时,将从 `<head>` 中删除 `<style>body {margin: 0;}</style>` 元素.
## API
### insertContent
```js
insertContent<T extends HTMLScriptElement | HTMLStyleElement>(
contentStrategy: ContentStrategy<T>,
): T
```
- `contentStrategy` 是方法的重要参数,已经在上方进行说明.
- 根据给定的策略返回 `HTMLScriptElement``HTMLStyleElement`.
### removeContent
```js
removeContent(element: HTMLScriptElement | HTMLStyleElement): void
```
- `element` 参数是已插入的 `HTMLScriptElement``HTMLStyleElement` 元素,它们应由 `insertContent` 方法返回.
### has
```js
has(content: string): boolean
```
`has` 返回一个布尔值,用于表示给定的内容是否插入到DOM.
- `content` 参数是 `HTMLScriptElement``HTMLStyleElement` 元素的内容.
## 下一步是什么?
- [ContentProjectionService](./Content-Projection-Service.md)

@ -1,3 +1,179 @@
## HTTP请求
TODO...
## 关于 HttpClient
Angular具有很棒的 `HttpClient` 与后端服务进行通信. 它位于顶层,是[XMLHttpRequest Web API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)的封装. 同时也是Angular建议用于任何HTTP请求的代理,在你的ABP项目中使用 `HttpClient` 是最佳做法.
但是 `HttpClient` 将错误处理留给调用方,换句话说HTTP错误是通过手动处理的,通过挂接到返回的 `Observable` 的观察者中来处理.
```js
getConfig() {
this.http.get(this.configUrl).subscribe(
config => this.updateConfig(config),
error => {
// Handle error here
},
);
}
```
上面的代码尽管清晰灵活,但即使将错误处理委派给Store或任何其他注入. 以这种方式处理错误也是重复性的工作.
`HttpInterceptor` 能够捕获 `HttpErrorResponse` 并可用于集中的错误处理. 然而,在必须放置错误处理程序(也就是拦截器)的情况下,需要额外的工作以及对Angular内部机制的理解. 检查[这个issue](https://github.com/angular/angular/issues/20203)了解详情.
## RestService
ABP核心模块有用于HTTP请求的实用程序服务: `RestService`. 除非另有明确配置,否则它将捕获HTTP错误并调度 `RestOccurError` 操作, 然后由 `ThemeSharedModule` 引入的 `ErrorHandler` 捕获此操作. 你应该已经在应用程序中导入了此模块,在使用 `RestService` 时,默认情况下将自动处理所有HTTP错误.
### RestService 入门
为了使用 `RestService`, 你必须将它注入到你的类中.
```js
import { RestService } from '@abp/ng.core';
@Injectable({
/* class metadata here */
})
class DemoService {
constructor(private rest: RestService) {}
}
```
你不必在模块或组件/指令级别提供 `estService`,因为它已经在**根中**中提供了.
### 如何使用RestService发出请求
你可以使用 `RestService``request` 方法来处理HTTP请求. 示例:
```js
getFoo(id: number) {
const request: Rest.Request<null> = {
method: 'GET',
url: '/api/some/path/to/foo/' + id,
};
return this.rest.request<null, FooResponse>(request);
}
```
`request` 方法始终返回 `Observable<T>`. 无论何时使用 `getFoo` 方法,都可以执行以下操作:
```js
doSomethingWithFoo(id: number) {
this.demoService.getFoo(id).subscribe(
foo => {
// Do something with foo.
}
)
}
```
**你不必担心关于取消订阅**. `RestService` 在内部使用 `HttpClient`,因此它返回的每个可观察对象都是有限的可观察对象,成功或出错后将自动关闭订阅.
如你所见,`request` 方法获取一个具有 `Rest.Reques<T>` 类型的请求选项对象. 此泛型类型需要请求主体的接口. 当没有正文时,例如在 `GET``DELETE` 请求中,你可以传递 `null`. 示例:
```js
postFoo(body: Foo) {
const request: Rest.Request<Foo> = {
method: 'POST',
url: '/api/some/path/to/foo',
body
};
return this.rest.request<Foo, FooResponse>(request);
}
```
你可以在[此处检查](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23)完整的 `Rest.Request<T>` 类型,与Angular中的[HttpRequest](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23)类相比只有很少的改动.
### 如何禁用RestService的默认错误处理程序
默认 `request` 方法始终处理错误. 让我们看看如何改变这种行为并由自己处理错误:
```js
deleteFoo(id: number) {
const request: Rest.Request<null> = {
method: 'DELETE',
url: '/api/some/path/to/foo/' + id,
};
return this.rest.request<null, void>(request, { skipHandleError: true });
}
```
`skipHandleError` 配置选项设置为 `true` 时,禁用错误处理程序,并返回 `observable` 引发错误,你可以在订阅中捕获该错误.
```js
removeFooFromList(id: number) {
this.demoService.deleteFoo(id).subscribe(
foo => {
// Do something with foo.
},
error => {
// Do something with error.
}
)
}
```
### 如何从应用程序配置获取特定的API端点
`request` 方法接收到的另一个配置选项是 `apiName` (在v2.4中可用),它用于从应用程序配置获取特定的模块端点.
```js
putFoo(body: Foo, id: string) {
const request: Rest.Request<Foo> = {
method: 'PUT',
url: '/' + id,
body
};
return this.rest.request<Foo, void>(request, {apiName: 'foo'});
}
```
上面的putFoo将请求 `https://localhost:44305/api/some/path/to/foo/{id}` 当环境变量如下:
```js
// environment.ts
export const environment = {
apis: {
default: {
url: 'https://localhost:44305',
},
foo: {
url: 'https://localhost:44305/api/some/path/to/foo',
},
},
/* rest of the environment variables here */
}
```
### 如何观察响应对象或HTTP事件而不是正文
`RestService` 假定你通常对响应的正文感兴趣,默认情况下将 `observe` 属性设置为 `body`. 但是有时你可能对其他内容(例如自定义标头)非常感兴趣. 为此, `request` 方法在 `config` 对象中接收 `watch` 属性.
```js
getSomeCustomHeaderValue() {
const request: Rest.Request<null> = {
method: 'GET',
url: '/api/some/path/that/sends/some-custom-header',
};
return this.rest.request<null, HttpResponse<any>>(
request,
{observe: Rest.Observe.Response},
).pipe(
map(response => response.headers.get('Some-Custom-Header'))
);
}
```
你可以在[此处](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10)找到 `Rest.Observe` 枚举.
## 下一步是什么?
* [本地化](./Localization.md)

@ -1,3 +1,189 @@
# 如何懒加载 Scripts 与 Styles
TODO...
你可以使用@abp/ng.core包中的 `LazyLoadService` 以简单明了的方式延迟加载脚本和样式.
## 入门
你不必在模块或组件/指令级别提供 `LazyLoadService`,因为它已经在**根中**中提供了. 你可以在组件,指令或服务中注入并使用它.
```js
import { LazyLoadService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private lazyLoadService: LazyLoadService) {}
}
```
## 用法
你可以使用 `LazyLoadService``load` 方法在DOM中的所需位置创建 `<script>``<link>` 元素并强制浏览器下载目标资源.
### 如何加载 Scripts
`load` 方法的第一个参数需要一个 `LoadingStrategy`. 如果传递 `ScriptLoadingStrategy` 实例,`LazyLoadService` 将使用给定的 `src` 创建一个 `<script>` 元素并放置在指定的DOM位置.
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
@Component({
template: `
<some-component *ngIf="libraryLoaded$ | async"></some-component>
`
})
class DemoComponent {
libraryLoaded$ = this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/some-library.js'),
);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
`load` 方法返回一个 `observable`,你可以在组件中或通过 `AsyncPipe` 订阅它. 在上面的示例中**仅当脚本成功加载或之前已经加载脚本时**, `NgIf` 指令才会呈现 `<some-component>`.
> 你可以使用 `async` 管道在模板中多次订阅,脚本将仅加载一次
请参阅[LoadingStrategy](./Loading-Strategy.md)查看所有可用的加载策略以及如何构建自己的加载策略.
### 如何加载 Styles
如果传递给 `load` 方法第一个参数为 `StyleLoadingStrategy` 实例,`LazyLoadService` 将使用给定的 `href` 创建一个 `<link>` 元素并放置在指定的DOM位置.
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
@Component({
template: `
<some-component *ngIf="stylesLoaded$ | async"></some-component>
`
})
class DemoComponent {
stylesLoaded$ = this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousStyleToHead('/assets/some-styles.css'),
);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
`load` 方法返回一个 `observable`,你可以在组件中或通过 `AsyncPipe` 订阅它. 在上面的示例中**仅当样式成功加载或之前已经加载样式时**, `NgIf` 指令才会呈现 `<some-component>`.
> 你可以使用 `async` 管道在模板中多次订阅,样式将仅加载一次
请参阅[LoadingStrategy](./Loading-Strategy.md)查看所有可用的加载策略以及如何构建自己的加载策略.
### 高级用法
你有**很大的自由度来定义延迟加载的工作方式**. 示例:
```js
const domStrategy = DOM_STRATEGY.PrependToHead();
const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous(
'sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh',
);
const loadingStrategy = new StyleLoadingStrategy(
'https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css',
domStrategy,
crossOriginStrategy,
);
this.lazyLoad.load(loadingStrategy, 1, 2000);
```
此代码将创建具有给定URL和完整性哈希的 `<link>` 元素,将其插入到 `<head>` 元素的顶部,如果第一次尝试失败,则在2秒后重试一次.
一个常见的用例是在**使用功能之前加载多个脚本/样式**:
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
import { frokJoin } from 'rxjs';
@Component({
template: `
<some-component *ngIf="scriptsAndStylesLoaded$ | async"></some-component>
`
})
class DemoComponent {
private stylesLoaded$ = forkJoin(
this.lazyLoad.load(
LOADING_STRATEGY.PrependAnonymousStyleToHead('/assets/library-dark-theme.css'),
),
this.lazyLoad.load(
LOADING_STRATEGY.PrependAnonymousStyleToHead('/assets/library.css'),
),
);
private scriptsLoaded$ = forkJoin(
this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/library.js'),
),
this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/other-library.css'),
),
);
scriptsAndStylesLoaded$ = forkJoin(this.scriptsLoaded$, this.stylesLoaded$);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
RxJS `forkJoin` 并行加载所有脚本和样式,并仅在加载所有脚本和样式时才放行. 因此放置 `<some-component>` 时,所有必需的依赖项都可用的.
> 注意到我们在文档头上添加了样式吗? 有时这是必需的因为你的应用程序样式可能会覆盖某些库样式. 在这种情况下你必须注意前置样式的顺序. 它们将一一放置,**并且在放置前,最后一个放置在最上面**.
另一个常见的用例是**按顺序加载依赖脚本**:
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
import { concat } from 'rxjs';
@Component({
template: `
<some-component *ngIf="scriptsLoaded$ | async"></some-component>
`
})
class DemoComponent {
scriptsLoaded$ = concat(
this.lazyLoad.load(
LOADING_STRATEGY.PrependAnonymousScriptToHead('/assets/library.js'),
),
this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/script-that-requires-library.js'),
),
);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
在此示例中,第二个文件需要预先加载第一个文件, RxJS `concat` 函数将允许你以给定的顺序一个接一个地加载所有脚本,并且仅在加载所有脚本时放行.
## API
### loaded
```js
loaded: Set<string>
```
所有以前加载的路径都可以通过此属性访问. 它是一个简单的[JavaScript集]
### load
```js
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event>
```
- `strategy` 是主要参数,上面已经介绍过.
- `retryTimes` 定义加载失败前再次尝试多少次(默认值:2).
- `retryDelay` 定义重试之间的延迟(默认为1000).
## 下一步是什么?
- [DomInsertionService](./Dom-Insertion-Service.md)

@ -0,0 +1,95 @@
# LoadingStrategy
`LoadingStrategy` 是@abp/ng.core包暴露的抽象类. 扩展它的有两种加载策略: `ScriptLoadingStrategy``StyleLoadingStrategy`. 它们实现相同的方法和属性,这两种策略都可以帮助你定义延迟加载的工作方式.
## API
### constructor
```js
constructor(
public path: string,
protected domStrategy?: DomStrategy,
protected crossOriginStrategy?: CrossOriginStrategy
)
```
- `path``<script>` 元素做为 `src``<link>` 元素做为 `href` 属性.
- `domStrategy` 是在插入创建的元素时将使用的 `DomStrategy`. (默认值: AppendToHead_)
- `crossOriginStrategy``CrossOriginStrategy`,它在插入元素之前在创建的元素上使用. (默认值: Anonymous_)
请参阅[DomStrategy](./Dom-Strategy.md)和[CrossOriginStrategy](./Cross-Origin-Strategy.md)文档以了解其用法.
### createElement
```js
createElement(): HTMLScriptElement | HTMLLinkElement
```
该方法创建并返回 `path` 设置为 `src``href``<script>``<link>` 的元素.
### createStream
```js
createStream(): Observable<Event>
```
该方法创建并返回一个observable流,该流在成功时发出,在错误时抛出.
## ScriptLoadingStrategy
`ScriptLoadingStrategy` 是扩展 `LoadingStrategy` 的类. 它使你可以**延迟加载脚本**.
## StyleLoadingStrategy
`StyleLoadingStrategy` 是扩展 `LoadingStrategy` 的类. 它使你可以**延迟加载样式**.
## 预定义的加载策略
可通过 `LOADING_STRATEGY` 常量访问预定义的加载策略.
### AppendAnonymousScriptToHead
```js
LOADING_STRATEGY.AppendAnonymousScriptToHead(src: string, integrity?: string)
```
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<script>` 元素的属性,并放置在文档中 `<head>` 标签的**末尾**.
### PrependAnonymousScriptToHead
```js
LOADING_STRATEGY.PrependAnonymousScriptToHead(src: string, integrity?: string)
```
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<script>` 元素的属性,并放置在文档中 `<head>` 标签的**开始**.
### AppendAnonymousScriptToBody
```js
LOADING_STRATEGY.AppendAnonymousScriptToBody(src: string, integrity?: string)
```
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<script>` 元素的属性,并放置在文档中 `<body>` 标签的**末尾**.
### AppendAnonymousStyleToHead
```js
LOADING_STRATEGY.AppendAnonymousStyleToHead(href: string, integrity?: string)
```
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<style>` 元素的属性,并放置在文档中 `<head>` 标签的**末尾**.
### PrependAnonymousStyleToHead
```js
LOADING_STRATEGY.PrependAnonymousStyleToHead(href: string, integrity?: string)
```
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<style>` 元素的属性,并放置在文档中 `<head>` 标签的**开始**.
## 另请参阅
- [LazyLoadService](./Lazy-Load-Service.md)

@ -1,3 +1,139 @@
# Localization
# 本地化
TODO...
在阅读本地化管道和本地化服务之前你应该了解本地化Key.
本地化key格式由两个部分组成,分别是**资源名**和**Key**
`ResourceName::Key`
> 如果你没有指定资源名称,它默认是在 `environment.ts` 中声明的 `defaultResourceName`.
```js
const environment = {
//...
localization: {
defaultResourceName: 'MyProjectName',
},
};
```
所以这两个结果是一样的:
```html
<h1>{%{{{ '::Key' | abpLocalization }}}%}</h1>
<h1>{%{{{ 'MyProjectName::Key' | abpLocalization }}}%}</h1>
```
## 使用本地化管道
你可以使用 `abpLocalization` 管道来获取本地化的文本. 例:
```html
<h1>{%{{{ 'Resource::Key' | abpLocalization }}}%}</h1>
```
管道将用本地化的文本替换Key.
你还可以指定一个默认值,如下所示:
```html
<h1>{%{{{ { key: 'Resource::Key', defaultValue: 'Default Value' } | abpLocalization }}}%}</h1>
```
要使用插值,必须将插值作为管道参数给出. 例如:
本地化数据存储在键值对中:
```js
{
//...
AbpAccount: { // AbpAccount is the resource name
Key: "Value",
PagerInfo: "Showing {0} to {1} of {2} entries"
}
}
```
所以我们可以这样使用Key:
```html
<h1>{%{{{ 'AbpAccount::PagerInfo' | abpLocalization:'20':'30':'50' }}}%}</h1>
<!-- Output: Showing 20 to 30 of 50 entries -->
```
### 使用本地化服务
首先应该从 **@abp/ng.core** 导入 `LocalizationService`.
```js
import { LocalizationService } from '@abp/ng.core';
class MyClass {
constructor(private localizationService: LocalizationService) {}
}
```
之后你就可以使用本地化服务.
> 你可以将插值参数作为参数添加到 `instant()``get()` 方法中.
```js
this.localizationService.instant('AbpIdentity::UserDeletionConfirmation', 'John');
// with fallback value
this.localizationService.instant(
{ key: 'AbpIdentity::UserDeletionConfirmation', defaultValue: 'Default Value' },
'John',
);
// Output
// User 'John' will be deleted. Do you confirm that?
```
要获取[_Observable_](https://rxjs.dev/guide/observable)的本地化文本,应该使用 `get` 方法而不是 `instant`:
```js
this.localizationService.get('Resource::Key');
// with fallback value
this.localizationService.get({ key: 'Resource::Key', defaultValue: 'Default Value' });
```
### 使用配置状态
要使用 `getLocalization` 方法,你应该导入 `ConfigState`.
```js
import { ConfigState } from '@abp/ng.core';
```
然后你可以按以下方式使用它:
```js
this.store.selectSnapshot(ConfigState.getLocalization('ResourceName::Key'));
```
`getLocalization` 方法可以与 `本地化key` 和 [`LocalizationWithDefault`](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L34) 接口一起使用.
```js
this.store.selectSnapshot(
ConfigState.getLocalization(
{
key: 'AbpIdentity::UserDeletionConfirmation',
defaultValue: 'Default Value',
},
'John',
),
);
```
本地化资源存储在 `ConfigState``localization` 属性中.
## 另请参阅
* [ASP.NET Core中的本地化](../../Localization.md)
## 下一步是什么?
* [权限管理](./Permission-Management.md)

@ -1,3 +1,79 @@
# Permission Management
# 权限管理
TODO...
权限是为特定用户,角色或客户端授予或禁止的简单策略. 你可以在[ABP授权文档](../../Authorization.md)中阅读更多信息.
你可以使用 `ConfigState``getGrantedPolicy` 选择器获取经过身份验证的用户的权限.
你可以从Store中获取权限的布尔值:
```js
import { Store } from '@ngxs/store';
import { ConfigState } from '../states';
export class YourComponent {
constructor(private store: Store) {}
ngOnInit(): void {
const canCreate = this.store.selectSnapshot(ConfigState.getGrantedPolicy('AbpIdentity.Roles.Create'));
}
// ...
}
```
或者你可以通过 `ConfigStateService` 获取它:
```js
import { ConfigStateService } from '../services/config-state.service';
export class YourComponent {
constructor(private configStateService: ConfigStateService) {}
ngOnInit(): void {
const canCreate = this.configStateService.getGrantedPolicy('AbpIdentity.Roles.Create');
}
// ...
}
```
## 权限指令
你可以使用 `PermissionDirective` 来根据用户的权限控制DOM元素是否可见.
```html
<div *abpPermission="AbpIdentity.Roles">
仅当用户具有`AbpIdentity.Roles`权限时,此内容才可见.
</div>
```
如上所示,你可以使用 `abpPermission` 结构指令从DOM中删除元素.
该指令也可以用作属性指令,但是我们建议你将其用作结构指令.
## 权限守卫
如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `PermissionGuard`.
`requiredPolicy` 添加到路由模块中的 `routes`属性.
```js
const routes: Routes = [
{
path: 'path',
component: YourComponent,
canActivate: [PermissionGuard],
data: {
routes: {
requiredPolicy: 'AbpIdentity.Roles.Create',
},
},
},
];
```
授予的策略存储在 `ConfigState``auth` 属性中.
## 下一步是什么?
* [Config State](./Config-State.md)

@ -0,0 +1,179 @@
# ProjectionStrategy
`ProjectionStrategy` 是@abp/ng.core包暴露出的抽象类. 有三种扩展它的投影策略: `ComponentProjectionStrategy`, `RootComponentProjectionStrategy``TemplateProjectionStrategy`. 它们实现相同的方法和属性,均可以帮助你定义内容投影的工作方式.
## ComponentProjectionStrategy
`ComponentProjectionStrategy` 是扩展 `ProjectionStrategy` 的类. 它使你可以将**组件投影到容器中**.
### constructor
```js
constructor(
component: T,
private containerStrategy: ContainerStrategy,
private contextStrategy?: ContextStrategy,
)
```
- `component` 是你要投影的组件的类.
- `containerStrategy` 是在投影组件时将使用的 `ContainerStrategy`.
- `contextStrategy` 是将在投影组件上使用的 `ContextStrategy`. (默认值: None_)
请参阅[ContainerStrategy](./Container-Strategy.md)和[ContextStrategy](./Context-Strategy.md)文档以了解其用法.
### injectContent
```js
injectContent(injector: Injector): ComponentRef<T>
```
该方法准备容器,解析组件,设置其上下文并将其投影到容器中. 它返回一个 `ComponentRef` 实例,你应该保留该实例以便以后清除投影的组件.
## RootComponentProjectionStrategy
`RootComponentProjectionStrategy` 是扩展 `ProjectionStrategy` 的类. 它使你可以将**组件投影到文档中**,例如将其附加到 `<body>`.
### constructor
```js
constructor(
component: T,
private contextStrategy?: ContextStrategy,
private domStrategy?: DomStrategy,
)
```
- `component` 是你要投影的组件的类.
- `contextStrategy` 是将在投影组件上使用的 `ContextStrategy`. (默认值: None_)
- `domStrategy` 是插入组件时将使用的 `DomStrategy`. (默认值: AppendToBody_)
请参阅[ContextStrategy](./Context-Strategy.md)和[DomStrategy](./Dom-Strategy.md)文档以了解其用法.
### injectContent
```js
injectContent(injector: Injector): ComponentRef<T>
```
该方法解析组件,设置其上下文并将其投影到文档中. 它返回一个 `ComponentRef` 实例,你应该保留该实例以便以后清除投影的组件.
## TemplateProjectionStrategy
`TemplateProjectionStrategy` 是扩展 `ProjectionStrategy` 的类.它使你可以将**模板投影到容器中**.
### constructor
```js
constructor(
template: T,
private containerStrategy: ContainerStrategy,
private contextStrategy?: ContextStrategy,
)
```
- `template` 是你要投影的 `TemplateRef`.
- `containerStrategy` 是在投影组件时将使用的 `ContainerStrategy`.
- `contextStrategy` 是将在投影组件上使用的 `ContextStrategy`. (默认值: None_)
请参阅[ContainerStrategy](./Container-Strategy.md)和[ContextStrategy](./Context-Strategy.md)文档以了解其用法.
### injectContent
```js
injectContent(): EmbeddedViewRef<T>
```
该方法准备容器,并将模板及其定义的上下文一起投影到容器. 它返回一个 `EmbeddedViewRef` 实例,你应该保留该实例以便以后清除投影的模板.
## 预定义的投影策略
可以通过 `PROJECTION_STRATEGY` 常量访问预定义的投影策略.
### AppendComponentToBody
```js
PROJECTION_STRATEGY.AppendComponentToBody(
component: T,
contextStrategy?: ComponentContextStrategy<T>,
)
```
将给定上下文设置到组件并将放置在文档中 `<body>` 标签的**末尾**.
### AppendComponentToContainer
```js
PROJECTION_STRATEGY.AppendComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
将给定上下文设置到组件并将放置在容器的**末尾**.
### AppendTemplateToContainer
```js
PROJECTION_STRATEGY.AppendTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
将给定上下文设置到模板并将其放置在容器的**末尾**.
### PrependComponentToContainer
```js
PROJECTION_STRATEGY.PrependComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
将给定上下文设置到组件并将其放置在容器的**开头**.
### PrependTemplateToContainer
```js
PROJECTION_STRATEGY.PrependTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
将给定上下文设置到模板并将其放置在容器的**开头**.
### ProjectComponentToContainer
```js
PROJECTION_STRATEGY.ProjectComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
清除容器,将给定的上下文设置到组件并放在**已清除**的容器中.
### ProjectTemplateToContainer
```js
PROJECTION_STRATEGY.ProjectTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
清除容器,将给定的上下文设置到模板并放在**已清除**的容器中.
## 另请参阅
- [DomInsertionService](./Dom-Insertion-Service.md)

@ -1,3 +1,68 @@
## 服务代理
TODO...
从Angular应用程序中调用服务器中的REST端点是很常见的, 在这种情况下我们通常创建**服务**(在服务器端具有针对每个服务方法的方法)和**模型对象**(与服务器端[DTO](../../Data-Transfer-Objects)匹配).
除了手动创建这样的服务器交互服务之外,我们还可以使用[NSWAG](https://github.com/RicoSuter/NSwag)之类的工具来为我们生成服务代理. 但使用NSWAG过程中我们遇到以下问题:
* 它生成一个**大的单个.ts文件**,该文件存在一些问题;
* 当你的应用程序增长时,它会变的**越来越大**.
* 它不适合ABP框架的[模块化](../../Module-Development-Basics)方法
* 它创建了一些**难看的代码**. 我们希望有一个干净的代码(就像我们手动编写一样).
* 它不能生成在服务器端声明的相同**方法签名**(因为swagger.json并不完全反映后端服务的方法签名). 我们已经创建了一个端点公开了服务器端方法信息,以便客户端生成更好的一致的客户端代理.
ABP CLI 的`generate-proxies` 命令在 `src/app` 文件夹中创建按模块名称分隔的文件夹,自动生成typescript客户端代理.
在angular应用程序的**根文件夹**中运行以下命令:
```bash
abp generate-proxy
```
它只为你自己的应用程序的服务创建代理. 不会为你正在使用的应用程序模块的服务创建代理(默认情况下). 有几个选项,参见[CLI文档](../../CLI).
使用 `--module all` 选项生成的文件如下所示:
![generated-files-via-generate-proxy](./images/generated-files-via-generate-proxy.png)
### Services
每个生成的服务都与后端控制器匹配. 服务方法通过[RestService](./Http-Requests#restservice)调用后端API.
在每个服务中都定义了一个名为 `apiName` 的变量(自v2.4起可用). `apiName` 与模块的 `RemoteServiceName` 匹配. 在每次请求时该变量将作为参数传递给 `RestService`. 如果环境中未定义微服务API, `RestService` 使用默认值. 请参阅[从应用程序配置中获取特定的API端点](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config)
服务的 `providerIn` 属性定义为 `'root'`. 因此无需将服务作为提供程序添加到模块. 你可以通过将服务注入到构造函数中来使用它,如下所示:
```js
import { AbpApplicationConfigurationService } from '../app/shared/services';
//...
export class HomeComponent{
constructor(private appConfigService: AbpApplicationConfigurationService) {}
ngOnInit() {
this.appConfigService.get().subscribe()
}
}
```
Angular编译器会从最终输出中删除那些没有被注入的服务. 参见[摇树优化的提供者文档](https://angular.cn/guide/dependency-injection-providers#tree-shakable-providers).
### Models
生成的模型与后端中的dto匹配. 每个模型在 `src/app/*/shared/models` 文件夹生成一个类.
`@abp/ng.core` 包有一些[基类](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts). 一些模型扩展了这些类.
可以如下所示创建一个类的实例:
```js
import { IdentityRoleCreateDto } from '../identity/shared/models';
//...
const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true})
```
可以选择将初始值传递给每个类构造函数.
## 下一步是什么?
* [HTTP请求](./Http-Requests)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

@ -234,7 +234,7 @@ public class MyExtensionStyleBundleContributor : BundleContributor
#### 贡献者扩展
在某些高级应用场景中, 当用到一个bundle贡献者时你可能想做一些额外的配置. 贡献者扩展可以和被扩展的贡献者无缝衔接.
在某些高级应用场景中, 当用到一个bundle贡献者时,你可能想做一些额外的配置. 贡献者扩展可以和被扩展的贡献者无缝衔接.
下面的示例为 prism.js 脚本库添加一些样式:

@ -12,18 +12,18 @@ ABP框架还向标准bootstrap组件添加了一些**实用的功能**.
这里是ABP框架包装的组件列表:
* [Alerts](Alerts.md)
* [Buttons](Buttons.md)
* [Cards](Cards.md)
* [Alerts](Alerts.md)
* [Tabs](Tabs.md)
* [Grids](Grids.md)
* [Modals](Modals.md)
* [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md)
* [Grids](Grids.md)
* [List Groups](List-Groups.md)
* [Modals](Modals.md)
* [Paginator](Paginator.md)
* [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md)
* [Tabs](Tabs.md)
* [Tooltips](Tooltips.md)
* ...

@ -47,7 +47,7 @@
### min-value
进度条的最小值. 默认值是0
进度条的最小值. 默认值是0.
### max-value

@ -8,7 +8,7 @@ ABP增加了以下优点:
* 定义 `IValidationEnabled` 向任意类添加自动验证. 所有的[应用服务](Application-Services.md)都实现了该接口,所以它们会被自动验证.
* 自动将数据注解属性的验证错误信息本地化.
* 提供可扩展的服务来验证方法调用或对象的状态
* 提供可扩展的服务来验证方法调用或对象的状态.
* 提供[FluentValidation](https://fluentvalidation.net/)的集成.
## 验证DTO
@ -80,7 +80,7 @@ namespace Acme.BookStore
#### 解析服务
如果你需要从[依赖注入系统](Dependency-Injection.md)解析服务,可以使用 `ValidationContext` 对象.
:
````csharp
var myService = validationContext.GetRequiredService<IMyService>();

@ -35,7 +35,7 @@ namespace MyCompany.MyProject
![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png)
如果需要添加多个文件, 这样做会很乏味. 作为选择 你可以直接编辑 **.csproj** 文件:
如果需要添加多个文件, 这样做会很乏味. 作为选择, 你可以直接编辑 **.csproj** 文件:
````C#
<ItemGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

@ -53,10 +53,11 @@ namespace Volo.Abp.Cli.Commands
private async Task UpdateNugetPackages(CommandLineArgs commandLineArgs, string directory)
{
var includePreviews =
commandLineArgs.Options.GetOrNull(Options.IncludePreviews.Short, Options.IncludePreviews.Long) != null;
var includePreviews = commandLineArgs
.Options
.GetOrNull(Options.IncludePreviews.Short, Options.IncludePreviews.Long) != null;
var solution = Directory.GetFiles(directory, "*.sln").FirstOrDefault();
var solution = Directory.GetFiles(directory, "*.sln", SearchOption.AllDirectories).FirstOrDefault();
if (solution != null)
{
@ -123,7 +124,7 @@ namespace Volo.Abp.Cli.Commands
public const string Short = "sp";
public const string Long = "solution-path";
}
public static class IncludePreviews
{
public const string Short = "p";

@ -12,6 +12,7 @@ using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
using Volo.Abp.IO;
namespace Volo.Abp.Cli.ProjectModification
{
@ -78,12 +79,9 @@ namespace Volo.Abp.Cli.ProjectModification
private async Task DeleteNpmrcFileAsync(string directoryName)
{
var fileName = Path.Combine(directoryName, ".npmrc");
FileHelper.DeleteIfExists(Path.Combine(directoryName, ".npmrc"));
if (File.Exists(fileName))
{
File.Delete(fileName);
}
await Task.CompletedTask;
}
private async Task CreateNpmrcFileAsync(string directoryName)

@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.8" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.11" />
</ItemGroup>
<ItemGroup>

@ -0,0 +1,600 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('just-compare')) :
typeof define === 'function' && define.amd ? define('@abp/utils', ['exports', 'just-compare'], factory) :
(global = global || self, factory((global.abp = global.abp || {}, global.abp.utils = global.abp.utils || {}, global.abp.utils.common = {}), global.compare));
}(this, (function (exports, compare) { 'use strict';
compare = compare && Object.prototype.hasOwnProperty.call(compare, 'default') ? compare['default'] : compare;
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __exportStar(m, exports) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
}
function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
}
function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
}
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
function __importStar(mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result.default = mod;
return result;
}
function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}
function __classPrivateFieldGet(receiver, privateMap) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return privateMap.get(receiver);
}
function __classPrivateFieldSet(receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to set private field on non-instance");
}
privateMap.set(receiver, value);
return value;
}
/* tslint:disable:no-non-null-assertion */
var ListNode = /** @class */ (function () {
function ListNode(value) {
this.value = value;
}
return ListNode;
}());
var LinkedList = /** @class */ (function () {
function LinkedList() {
this.size = 0;
}
Object.defineProperty(LinkedList.prototype, "head", {
get: function () {
return this.first;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LinkedList.prototype, "tail", {
get: function () {
return this.last;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LinkedList.prototype, "length", {
get: function () {
return this.size;
},
enumerable: true,
configurable: true
});
LinkedList.prototype.attach = function (value, previousNode, nextNode) {
if (!previousNode)
return this.addHead(value);
if (!nextNode)
return this.addTail(value);
var node = new ListNode(value);
node.previous = previousNode;
previousNode.next = node;
node.next = nextNode;
nextNode.previous = node;
this.size++;
return node;
};
LinkedList.prototype.attachMany = function (values, previousNode, nextNode) {
if (!values.length)
return [];
if (!previousNode)
return this.addManyHead(values);
if (!nextNode)
return this.addManyTail(values);
var list = new LinkedList();
list.addManyTail(values);
list.first.previous = previousNode;
previousNode.next = list.first;
list.last.next = nextNode;
nextNode.previous = list.last;
this.size += values.length;
return list.toNodeArray();
};
LinkedList.prototype.detach = function (node) {
if (!node.previous)
return this.dropHead();
if (!node.next)
return this.dropTail();
node.previous.next = node.next;
node.next.previous = node.previous;
this.size--;
return node;
};
LinkedList.prototype.add = function (value) {
var _this = this;
return {
after: function () {
var _a;
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
return (_a = _this.addAfter).call.apply(_a, __spread([_this, value], params));
},
before: function () {
var _a;
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
return (_a = _this.addBefore).call.apply(_a, __spread([_this, value], params));
},
byIndex: function (position) { return _this.addByIndex(value, position); },
head: function () { return _this.addHead(value); },
tail: function () { return _this.addTail(value); },
};
};
LinkedList.prototype.addMany = function (values) {
var _this = this;
return {
after: function () {
var _a;
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
return (_a = _this.addManyAfter).call.apply(_a, __spread([_this, values], params));
},
before: function () {
var _a;
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
return (_a = _this.addManyBefore).call.apply(_a, __spread([_this, values], params));
},
byIndex: function (position) { return _this.addManyByIndex(values, position); },
head: function () { return _this.addManyHead(values); },
tail: function () { return _this.addManyTail(values); },
};
};
LinkedList.prototype.addAfter = function (value, previousValue, compareFn) {
if (compareFn === void 0) { compareFn = compare; }
var previous = this.find(function (node) { return compareFn(node.value, previousValue); });
return previous ? this.attach(value, previous, previous.next) : this.addTail(value);
};
LinkedList.prototype.addBefore = function (value, nextValue, compareFn) {
if (compareFn === void 0) { compareFn = compare; }
var next = this.find(function (node) { return compareFn(node.value, nextValue); });
return next ? this.attach(value, next.previous, next) : this.addHead(value);
};
LinkedList.prototype.addByIndex = function (value, position) {
if (position < 0)
position += this.size;
else if (position >= this.size)
return this.addTail(value);
if (position <= 0)
return this.addHead(value);
var next = this.get(position);
return this.attach(value, next.previous, next);
};
LinkedList.prototype.addHead = function (value) {
var node = new ListNode(value);
node.next = this.first;
if (this.first)
this.first.previous = node;
else
this.last = node;
this.first = node;
this.size++;
return node;
};
LinkedList.prototype.addTail = function (value) {
var node = new ListNode(value);
if (this.first) {
node.previous = this.last;
this.last.next = node;
this.last = node;
}
else {
this.first = node;
this.last = node;
}
this.size++;
return node;
};
LinkedList.prototype.addManyAfter = function (values, previousValue, compareFn) {
if (compareFn === void 0) { compareFn = compare; }
var previous = this.find(function (node) { return compareFn(node.value, previousValue); });
return previous ? this.attachMany(values, previous, previous.next) : this.addManyTail(values);
};
LinkedList.prototype.addManyBefore = function (values, nextValue, compareFn) {
if (compareFn === void 0) { compareFn = compare; }
var next = this.find(function (node) { return compareFn(node.value, nextValue); });
return next ? this.attachMany(values, next.previous, next) : this.addManyHead(values);
};
LinkedList.prototype.addManyByIndex = function (values, position) {
if (position < 0)
position += this.size;
if (position <= 0)
return this.addManyHead(values);
if (position >= this.size)
return this.addManyTail(values);
var next = this.get(position);
return this.attachMany(values, next.previous, next);
};
LinkedList.prototype.addManyHead = function (values) {
var _this = this;
return values.reduceRight(function (nodes, value) {
nodes.unshift(_this.addHead(value));
return nodes;
}, []);
};
LinkedList.prototype.addManyTail = function (values) {
var _this = this;
return values.map(function (value) { return _this.addTail(value); });
};
LinkedList.prototype.drop = function () {
var _this = this;
return {
byIndex: function (position) { return _this.dropByIndex(position); },
byValue: function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
return _this.dropByValue.apply(_this, params);
},
byValueAll: function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
return _this.dropByValueAll.apply(_this, params);
},
head: function () { return _this.dropHead(); },
tail: function () { return _this.dropTail(); },
};
};
LinkedList.prototype.dropMany = function (count) {
var _this = this;
return {
byIndex: function (position) { return _this.dropManyByIndex(count, position); },
head: function () { return _this.dropManyHead(count); },
tail: function () { return _this.dropManyTail(count); },
};
};
LinkedList.prototype.dropByIndex = function (position) {
if (position < 0)
position += this.size;
var current = this.get(position);
return current ? this.detach(current) : undefined;
};
LinkedList.prototype.dropByValue = function (value, compareFn) {
if (compareFn === void 0) { compareFn = compare; }
var position = this.findIndex(function (node) { return compareFn(node.value, value); });
return position < 0 ? undefined : this.dropByIndex(position);
};
LinkedList.prototype.dropByValueAll = function (value, compareFn) {
if (compareFn === void 0) { compareFn = compare; }
var dropped = [];
for (var current = this.first, position = 0; current; position++, current = current.next) {
if (compareFn(current.value, value)) {
dropped.push(this.dropByIndex(position - dropped.length));
}
}
return dropped;
};
LinkedList.prototype.dropHead = function () {
var head = this.first;
if (head) {
this.first = head.next;
if (this.first)
this.first.previous = undefined;
else
this.last = undefined;
this.size--;
return head;
}
return undefined;
};
LinkedList.prototype.dropTail = function () {
var tail = this.last;
if (tail) {
this.last = tail.previous;
if (this.last)
this.last.next = undefined;
else
this.first = undefined;
this.size--;
return tail;
}
return undefined;
};
LinkedList.prototype.dropManyByIndex = function (count, position) {
if (count <= 0)
return [];
if (position < 0)
position = Math.max(position + this.size, 0);
else if (position >= this.size)
return [];
count = Math.min(count, this.size - position);
var dropped = [];
while (count--) {
var current = this.get(position);
dropped.push(this.detach(current));
}
return dropped;
};
LinkedList.prototype.dropManyHead = function (count) {
if (count <= 0)
return [];
count = Math.min(count, this.size);
var dropped = [];
while (count--)
dropped.unshift(this.dropHead());
return dropped;
};
LinkedList.prototype.dropManyTail = function (count) {
if (count <= 0)
return [];
count = Math.min(count, this.size);
var dropped = [];
while (count--)
dropped.push(this.dropTail());
return dropped;
};
LinkedList.prototype.find = function (predicate) {
for (var current = this.first, position = 0; current; position++, current = current.next) {
if (predicate(current, position, this))
return current;
}
return undefined;
};
LinkedList.prototype.findIndex = function (predicate) {
for (var current = this.first, position = 0; current; position++, current = current.next) {
if (predicate(current, position, this))
return position;
}
return -1;
};
LinkedList.prototype.forEach = function (callback) {
for (var node = this.first, position = 0; node; position++, node = node.next) {
callback(node, position, this);
}
};
LinkedList.prototype.get = function (position) {
return this.find(function (_, index) { return position === index; });
};
LinkedList.prototype.indexOf = function (value, compareFn) {
if (compareFn === void 0) { compareFn = compare; }
return this.findIndex(function (node) { return compareFn(node.value, value); });
};
LinkedList.prototype.toArray = function () {
var array = new Array(this.size);
this.forEach(function (node, index) { return (array[index] = node.value); });
return array;
};
LinkedList.prototype.toNodeArray = function () {
var array = new Array(this.size);
this.forEach(function (node, index) { return (array[index] = node); });
return array;
};
LinkedList.prototype.toString = function (mapperFn) {
if (mapperFn === void 0) { mapperFn = JSON.stringify; }
return this.toArray()
.map(function (value) { return mapperFn(value); })
.join(' <-> ');
};
// Cannot use Generator type because of ng-packagr
LinkedList.prototype[Symbol.iterator] = function () {
var node, position;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
node = this.first, position = 0;
_a.label = 1;
case 1:
if (!node) return [3 /*break*/, 4];
return [4 /*yield*/, node.value];
case 2:
_a.sent();
_a.label = 3;
case 3:
position++, node = node.next;
return [3 /*break*/, 1];
case 4: return [2 /*return*/];
}
});
};
return LinkedList;
}());
exports.LinkedList = LinkedList;
exports.ListNode = ListNode;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=abp-utils.umd.js.map

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

Loading…
Cancel
Save