Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into dev

pull/3398/head
Armağan Ünlü 6 years ago
parent
commit
8e4aaad857
  1. 53
      docs/en/UI/Angular/Component-Replacement.md
  2. 628
      docs/en/UI/Angular/Linked-List.md
  3. 6
      docs/en/UI/Angular/Service-Proxies.md
  4. 2
      docs/en/docs-nav.json
  5. 7
      docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md
  6. 30
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  7. 9
      docs/zh-Hans/Entities.md
  8. 99
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  9. 107
      docs/zh-Hans/Entity-Framework-Core.md
  10. 12
      modules/docs/app/VoloDocs.Web/Controllers/HomeController.cs
  11. 18
      modules/docs/app/VoloDocs.Web/Pages/Index.cshtml
  12. 42
      modules/docs/app/VoloDocs.Web/Pages/Index.cshtml.cs
  13. 2796
      modules/docs/app/VoloDocs.Web/package-lock.json
  14. 2
      modules/docs/app/VoloDocs.Web/package.json
  15. 12
      modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs
  16. 20
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs
  17. 7
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainConsts.cs
  18. 15
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs
  19. 15
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs
  20. 69
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs
  21. 22
      modules/docs/src/Volo.Docs.Domain/Volo/Extensions/NewtonsoftJsonExtensions.cs
  22. 109
      modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs
  23. 1
      modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj
  24. 2
      npm/ng-packs/angular.json
  25. 3
      npm/ng-packs/apps/dev-app/src/app/app.module.ts
  26. 9
      npm/ng-packs/packages/account/src/lib/services/account.service.ts
  27. 41
      npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts
  28. 6
      npm/ng-packs/packages/core/src/lib/models/common.ts
  29. 12
      npm/ng-packs/packages/core/src/lib/services/application-configuration.service.ts
  30. 18
      npm/ng-packs/packages/core/src/lib/services/profile.service.ts
  31. 4
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  32. 102
      npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts
  33. 870
      npm/ng-packs/packages/core/src/lib/tests/linked-list.spec.ts
  34. 257
      npm/ng-packs/packages/core/src/lib/utils/linked-list.ts
  35. 8
      npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts
  36. 34
      npm/ng-packs/packages/identity/src/lib/services/identity.service.ts
  37. 15
      npm/ng-packs/packages/permission-management/src/lib/services/permission-management.service.ts
  38. 28
      npm/ng-packs/packages/tenant-management/src/lib/services/tenant-management.service.ts
  39. 23
      npm/ng-packs/packages/theme-basic/src/lib/services/initial.service.ts
  40. 6
      templates/app/angular/src/app/app.module.ts
  41. 5
      templates/module/angular/.prettierrc
  42. 6
      templates/module/angular/src/app/app.module.ts

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

@ -1,10 +1,10 @@
# Component Replacement
## Component Replacement
You can replace some ABP components with your custom components.
The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_.
## How to Replace a Component
### How to Replace a Component
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
@ -29,7 +29,54 @@ export class AppComponent {
![Example Usage](./images/component-replacement.gif)
## Available Replaceable Components
### How to Replace a Layout
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced with the same way.
> A layout component template should contain `<router-outlet></router-outlet>` element.
The below example describes how to replace the `ApplicationLayoutComponent`:
Run the following command to generate a layout in `angular` folder:
```bash
yarn ng generate component shared/my-application-layout --export --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Add the following code in your layout template (`my-layout.component.html`) where you want the page to be loaded.
```html
<router-outlet></router-outlet>
```
Open the `app.component.ts` and add the below content:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added below content
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: 'Theme.ApplicationLayoutComponent',
}),
);
//...
}
}
```
### Available Replaceable Components
| Component key | Description |
| -------------------------------------------------- | --------------------------------------------- |

628
docs/en/UI/Angular/Linked-List.md

@ -26,7 +26,7 @@ The constructor does not get any parameters.
### How to Add New Nodes
There are a few methods to create new nodes in a linked list and all of them are separately available as well as revealed from an `add` method.
There are several methods to create new nodes in a linked list and all of them are separately available as well as revealed by `add` and `addMany` methods.
@ -50,6 +50,22 @@ list.addHead('c');
#### addManyHead(values: T\[\]): ListNode\<T\>\[\]
Adds multiple nodes with given values as the first nodes in list:
```js
list.addManyHead(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
list.addManyHead(['x', 'y', 'z']);
// "x" <-> "y" <-> "z" <-> "a" <-> "b" <-> "c"
```
#### addTail(value: T): ListNode\<T\>
Adds a node with given value as the last node in list:
@ -70,6 +86,22 @@ list.addTail('c');
#### addManyTail(values: T\[\]): ListNode\<T\>\[\]
Adds multiple nodes with given values as the last nodes in list:
```js
list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
list.addManyTail(['x', 'y', 'z']);
// "a" <-> "b" <-> "c" <-> "x" <-> "y" <-> "z"
```
#### addAfter(value: T, previousValue: T, compareFn = compare): ListNode\<T\>
Adds a node with given value after the first node that has the previous value:
@ -109,6 +141,40 @@ list.addAfter({ x: 0 }, { x: 2 }, (v1, v2) => v1.x === v2.x);
#### addManyAfter(values: T\[\], previousValue: T, compareFn = compare): ListNode\<T\>\[\]
Adds multiple nodes with given values after the first node that has the previous value:
```js
list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
list.addManyAfter(['x', 'y'], 'b');
// "a" <-> "b" <-> "x" <-> "y" <-> "b" <-> "c"
```
You may pass a custom compare function to detect the searched value:
```js
list.addManyTail([{ x: 1 },{ x: 2 },{ x: 3 }]);
// {"x":1} <-> {"x":2} <-> {"x":3}
list.addManyAfter([{ x: 4 }, { x: 5 }], { x: 2 }, (v1, v2) => v1.x === v2.x);
// {"x":1} <-> {"x":2} <-> {"x":4} <-> {"x":5} <-> {"x":3}
```
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
#### addBefore(value: T, nextValue: T, compareFn = compare): ListNode\<T\>
Adds a node with given value before the first node that has the next value:
@ -148,6 +214,40 @@ list.addBefore({ x: 0 }, { x: 2 }, (v1, v2) => v1.x === v2.x);
#### addManyBefore(values: T\[\], nextValue: T, compareFn = compare): ListNode\<T\>\[\]
Adds multiple nodes with given values before the first node that has the next value:
```js
list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
list.addManyBefore(['x', 'y'], 'b');
// "a" <-> "x" <-> "y" <-> "b" <-> "b" <-> "c"
```
You may pass a custom compare function to detect the searched value:
```js
list.addManyTail([{ x: 1 },{ x: 2 },{ x: 3 }]);
// {"x":1} <-> {"x":2} <-> {"x":3}
list.addManyBefore([{ x: 4 }, { x: 5 }], { x: 2 }, (v1, v2) => v1.x === v2.x);
// {"x":1} <-> {"x":4} <-> {"x":5} <-> {"x":2} <-> {"x":3}
```
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
#### addByIndex(value: T, position: number): ListNode\<T\>
Adds a node with given value at the specified position in the list:
@ -166,6 +266,52 @@ list.addByIndex('x', 2);
It works with negative index too:
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
// "a" <-> "b" <-> "c"
list.addByIndex('x', -1);
// "a" <-> "b" <-> "x" <-> "c"
```
#### addManyByIndex(values: T\[\], position: number): ListNode\<T\>\[\]
Adds multiple nodes with given values at the specified position in the list:
```js
list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
list.addManyByIndex(['x', 'y'], 2);
// "a" <-> "b" <-> "x" <-> "y" <-> "c"
```
It works with negative index too:
```js
list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
list.addManyByIndex(['x', 'y'], -1);
// "a" <-> "b" <-> "x" <-> "y" <-> "c"
```
#### add(value: T).head(): ListNode\<T\>
Adds a node with given value as the first node in list:
@ -314,10 +460,172 @@ list.add('x').byIndex(2);
It works with negative index too:
```js
list.add('a').tail();
list.add('b').tail();
list.add('c').tail();
// "a" <-> "b" <-> "c"
list.add('x').byIndex(-1);
// "a" <-> "b" <-> "x" <-> "c"
```
> This is an alternative API for `addByIndex`.
#### addMany(values: T\[\]).head(): ListNode\<T\>\[\]
Adds multiple nodes with given values as the first nodes in list:
```js
list.addMany(['a', 'b', 'c']).head();
// "a" <-> "b" <-> "c"
list.addMany(['x', 'y', 'z']).head();
// "x" <-> "y" <-> "z" <-> "a" <-> "b" <-> "c"
```
> This is an alternative API for `addManyHead`.
#### addMany(values: T\[\]).tail(): ListNode\<T\>\[\]
Adds multiple nodes with given values as the last nodes in list:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.addMany(['x', 'y', 'z']).tail();
// "a" <-> "b" <-> "c" <-> "x" <-> "y" <-> "z"
```
> This is an alternative API for `addManyTail`.
#### addMany(values: T\[\]).after(previousValue: T, compareFn = compare): ListNode\<T\>\[\]
Adds multiple nodes with given values after the first node that has the previous value:
```js
list.addMany(['a', 'b', 'b', 'c']).tail();
// "a" <-> "b" <-> "b" <-> "c"
list.addMany(['x', 'y']).after('b');
// "a" <-> "b" <-> "x" <-> "y" <-> "b" <-> "c"
```
You may pass a custom compare function to detect the searched value:
```js
list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }]).tail();
// {"x":1} <-> {"x":2} <-> {"x":3}
list.addMany([{ x: 4 }, { x: 5 }]).after({ x: 2 }, (v1, v2) => v1.x === v2.x);
// {"x":1} <-> {"x":2} <-> {"x":4} <-> {"x":5} <-> {"x":3}
```
> This is an alternative API for `addManyAfter`.
>
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
#### addMany(values: T\[\]).before(nextValue: T, compareFn = compare): ListNode\<T\>\[\]
Adds multiple nodes with given values before the first node that has the next value:
```js
list.addMany(['a', 'b', 'b', 'c']).tail();
// "a" <-> "b" <-> "b" <-> "c"
list.addMany(['x', 'y']).before('b');
// "a" <-> "x" <-> "y" <-> "b" <-> "b" <-> "c"
```
You may pass a custom compare function to detect the searched value:
```js
list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }]).tail();
// {"x":1} <-> {"x":2} <-> {"x":3}
list.addMany([{ x: 4 }, { x: 5 }]).before({ x: 2 }, (v1, v2) => v1.x === v2.x);
// {"x":1} <-> {"x":4} <-> {"x":5} <-> {"x":2} <-> {"x":3}
```
> This is an alternative API for `addManyBefore`.
>
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
#### addMany(values: T\[\]).byIndex(position: number): ListNode\<T\>\[\]
Adds multiple nodes with given values at the specified position in the list:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.addMany(['x', 'y']).byIndex(2);
// "a" <-> "b" <-> "x" <-> "y" <-> "c"
```
It works with negative index too:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.addMany(['x', 'y']).byIndex(-1);
// "a" <-> "b" <-> "x" <-> "y" <-> "c"
```
> This is an alternative API for `addManyByIndex`.
### How to Remove Nodes
There are a few methods to remove nodes from a linked list and all of them are separately available as well as revealed from a `drop` method.
@ -329,9 +637,7 @@ There are a few methods to remove nodes from a linked list and all of them are s
Removes the first node from the list:
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
@ -342,14 +648,28 @@ list.dropHead();
#### dropManyHead(count: number): ListNode\<T\>\[\]
Removes the first nodes from the list based on given count:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.dropManyHead(2);
// "c"
```
#### dropTail(): ListNode\<T\> | undefined
Removes the last node from the list:
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
@ -360,14 +680,28 @@ list.dropTail();
#### dropManyTail(count: number): ListNode\<T\>\[\]
Removes the last nodes from the list based on given count:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.dropManyTail(2);
// "a"
```
#### dropByIndex(position: number): ListNode\<T\> | undefined
Removes the node with the specified position from the list:
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
@ -378,16 +712,56 @@ list.dropByIndex(1);
It works with negative index too:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.dropByIndex(-2);
// "a" <-> "c"
```
#### dropManyByIndex(count: number, position: number): ListNode\<T\>\[\]
Removes the nodes starting from the specified position from the list based on given count:
```js
list.addMany(['a', 'b', 'c', 'd']).tail();
// "a" <-> "b" <-> "c" <-> "d
list.dropManyByIndex(2, 1);
// "a" <-> "d"
```
It works with negative index too:
```js
list.addMany(['a', 'b', 'c', 'd']).tail();
// "a" <-> "b" <-> "c" <-> "d
list.dropManyByIndex(2, -2);
// "a" <-> "d"
```
#### dropByValue(value: T, compareFn = compare): ListNode\<T\> | undefined
Removes the first node with given value from the list:
```js
list.addTail('a');
list.addTail('x');
list.addTail('b');
list.addTail('x');
list.addTail('c');
list.addMany(['a', 'x', 'b', 'x', 'c']).tail();
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
@ -401,11 +775,7 @@ list.dropByValue('x');
You may pass a custom compare function to detect the searched value:
```js
list.addTail({ x: 1 });
list.addTail({ x: 0 });
list.addTail({ x: 2 });
list.addTail({ x: 0 });
list.addTail({ x: 3 });
list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail();
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -425,11 +795,7 @@ list.dropByValue({ x: 0 }, (v1, v2) => v1.x === v2.x);
Removes all nodes with given value from the list:
```js
list.addTail('a');
list.addTail('x');
list.addTail('b');
list.addTail('x');
list.addTail('c');
list.addMany(['a', 'x', 'b', 'x', 'c']).tail();
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
@ -443,11 +809,7 @@ list.dropByValueAll('x');
You may pass a custom compare function to detect the searched value:
```js
list.addTail({ x: 1 });
list.addTail({ x: 0 });
list.addTail({ x: 2 });
list.addTail({ x: 0 });
list.addTail({ x: 3 });
list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail();
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -467,9 +829,7 @@ list.dropByValue({ x: 0 }, (v1, v2) => v1.x === v2.x);
Removes the first node in list:
```js
list.add('a').tail();
list.add('b').tail();
list.add('c').tail();
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
@ -489,9 +849,7 @@ list.drop().head();
Removes the last node in list:
```js
list.add('a').tail();
list.add('b').tail();
list.add('c').tail();
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
@ -511,9 +869,7 @@ list.drop().tail();
Removes the node with the specified position from the list:
```js
list.add('a').tail();
list.add('b').tail();
list.add('c').tail();
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
@ -524,6 +880,20 @@ list.drop().byIndex(1);
It works with negative index too:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.drop().byIndex(-2);
// "a" <-> "c"
```
> This is an alternative API for `dropByIndex`.
@ -533,11 +903,7 @@ list.drop().byIndex(1);
Removes the first node with given value from the list:
```js
list.add('a').tail();
list.add('x').tail();
list.add('b').tail();
list.add('x').tail();
list.add('c').tail();
list.addMany(['a', 'x', 'b', 'x', 'c']).tail();
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
@ -551,11 +917,7 @@ list.drop().byValue('x');
You may pass a custom compare function to detect the searched value:
```js
list.add({ x: 1 }).tail();
list.add({ x: 0 }).tail();
list.add({ x: 2 }).tail();
list.add({ x: 0 }).tail();
list.add({ x: 3 }).tail();
list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail();
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -577,11 +939,7 @@ list.drop().byValue({ x: 0 }, (v1, v2) => v1.x === v2.x);
Removes all nodes with given value from the list:
```js
list.add('a').tail();
list.add('x').tail();
list.add('b').tail();
list.add('x').tail();
list.add('c').tail();
list.addMany(['a', 'x', 'b', 'x', 'c']).tail();
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
@ -595,11 +953,7 @@ list.drop().byValueAll('x');
You may pass a custom compare function to detect the searched value:
```js
list.add({ x: 1 }).tail();
list.add({ x: 0 }).tail();
list.add({ x: 2 }).tail();
list.add({ x: 0 }).tail();
list.add({ x: 3 }).tail();
list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail();
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -616,6 +970,80 @@ list.drop().byValueAll({ x: 0 }, (v1, v2) => v1.x === v2.x);
#### dropMany(count: number).head(): ListNode\<T\>\[\]
Removes the first nodes from the list based on given count:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.dropMany(2).head();
// "c"
```
> This is an alternative API for `dropManyHead`.
#### dropMany(count: number).tail(): ListNode\<T\>\[\]
Removes the last nodes from the list based on given count:
```js
list.addMany(['a', 'b', 'c']).tail();
// "a" <-> "b" <-> "c"
list.dropMany(2).tail();
// "a"
```
> This is an alternative API for `dropManyTail`.
#### dropMany(count: number).byIndex(position: number): ListNode\<T\>\[\]
Removes the nodes starting from the specified position from the list based on given count:
```js
list.addMany(['a', 'b', 'c', 'd']).tail();
// "a" <-> "b" <-> "c" <-> "d
list.dropMany(2).byIndex(1);
// "a" <-> "d"
```
It works with negative index too:
```js
list.addMany(['a', 'b', 'c', 'd']).tail();
// "a" <-> "b" <-> "c" <-> "d
list.dropMany(2).byIndex(-2);
// "a" <-> "d"
```
> This is an alternative API for `dropManyByIndex`.
### How to Find Nodes
There are a few methods to find specific nodes in a linked list.
@ -627,10 +1055,7 @@ There are a few methods to find specific nodes in a linked list.
Finds the first node from the list that matches the given predicate:
```js
list.addTail('a');
list.addTail('b');
list.addTail('b');
list.addTail('c');
list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
@ -650,10 +1075,7 @@ found.next.value === "b"
Finds the position of the first node from the list that matches the given predicate:
```js
list.addTail('a');
list.addTail('b');
list.addTail('b');
list.addTail('c');
list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
@ -677,9 +1099,7 @@ i3 === -1
Finds and returns the node with specific position in the list:
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -699,10 +1119,7 @@ found.next.value === "c"
Finds the position of the first node from the list that has the given value:
```js
list.addTail('a');
list.addTail('b');
list.addTail('b');
list.addTail('c');
list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
@ -724,11 +1141,7 @@ i3 === -1
You may pass a custom compare function to detect the searched value:
```js
list.addTail({ x: 1 });
list.addTail({ x: 0 });
list.addTail({ x: 2 });
list.addTail({ x: 0 });
list.addTail({ x: 3 });
list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]);
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -764,9 +1177,7 @@ There are a few ways to iterate over or display a linked list.
Runs a callback function on all nodes in a linked list from head to tail:
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -784,9 +1195,7 @@ list.forEach((node, index) => console.log(node.value + index));
A linked list is iterable. In other words, you may use methods like `for...of` on it.
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -801,14 +1210,12 @@ for(const node of list) {
#### toArray(): T[]
#### toArray(): T\[\]
Converts a linked list to an array:
Converts a linked list to an array of values:
```js
list.addTail('a');
list.addTail('b');
list.addTail('c');
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -821,15 +1228,32 @@ arr === ['a', 'b', 'c']
#### toNodeArray(): T\[\]
Converts a linked list to an array of nodes:
```js
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
const arr = list.toNodeArray();
/*
arr[0].value === 'a'
arr[1].value === 'a'
arr[2].value === 'a'
*/
```
#### toString(): string
Converts a linked list to a string representation of nodes and their relations:
```js
list.addTail('a');
list.addTail(2);
list.addTail('c');
list.addTail({ k: 4, v: 'd' });
list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]);
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
@ -842,3 +1266,19 @@ str === '"a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}'
You may pass a custom mapper function to map values before stringifying them:
```js
list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }]).tail();
// {"x":1} <-> {"x":2} <-> {"x":3} <-> {"x":4} <-> {"x":5}
const str = list.toString(value => value.x);
/*
str === '1 <-> 2 <-> 3 <-> 4 <-> 5'
*/
```

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

@ -25,9 +25,9 @@ The files generated with the `--module all` option like below:
### Services
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./HTTP-Requests.md#restservice).
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./Http-Requests#restservice).
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](HTTP-Requests#how-to-get-a-specific-api-endpoint-from-application-config)
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config)
The `providedIn` property of the services is defined as `'root'`. Therefore no need to add a service as a provider to a module. You can use a service by injecting it into a constructor as shown below:
@ -64,4 +64,4 @@ Initial values ​​can optionally be passed to each class constructor.
## What's Next?
* [Http Requests](./Http-Requests.md)
* [HTTP Requests](./Http-Requests)

2
docs/en/docs-nav.json

@ -5,7 +5,7 @@
"items": [
{
"text": "From Startup Templates",
"path": "Getting-Started-With-Startup-Templates.md"
"path": "Getting-Started-With-Startup-Templates.md",
"items": [
{
"text": "Application with MVC (Razor Pages) UI",

7
docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md

@ -89,20 +89,23 @@ public static class IdentityDbContextModelBuilderExtensions
builder.Entity<IdentityUser>(b =>
{
b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity
});
builder.Entity<IdentityUserClaim>(b =>
{
b.ToTable(options.TablePrefix + "UserClaims", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity
});
});
//code omitted for brevity
}
}
````
* **推荐** 为每个Enttiy映射调用 `b.ConfigureByConvention();`(如上所示).
* **推荐** 通过继承 `ModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 例如:
````C#

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

@ -25,7 +25,35 @@ return user.GetProperty<string>("Title");
参阅[实体文档](Entities.md)了解更多关于额外系统.
> 可以基于额外的属性执行**业务逻辑**. 你可以**override**服务方法获取或设置值. 重写服务在下面进行讨论.
> 可以基于额外的属性执行**业务逻辑**. 你可以[重写服务方法](Customizing-Application-Modules-Overriding-Services.md). 然后获取或设置如上所示的值.
## 实体扩展 (EF Core)
如上所述,实体所有的额外属性都作为单个JSON对象存储在数据库表中. 它不适用复杂的场景,特别是在你需要的时候.
* 使用额外属性创建**索引**和**外键**.
* 使用额外属性编写**SQL**或**LINQ**(例如根据属性值搜索).
* 创建你**自己的实体**映射到相同的表,但在实体中定义一个额外属性做为 **常规属性**(参阅 [EF Core迁移文档](Entity-Framework-Core-Migrations.md)了解更多).
为了解决上面的问题,用于EF Core的ABP框架实体扩展系统允许你使用上面定义相同的额外属性API,但将所需的属性存储在单独的数据库表字段中.
假设你想要添加 `SocialSecurityNumber` 到[身份模块](Modules/Identity.md)的 `IdentityUser` 实体. 你可以使用 `EntityExtensionManager` 静态类:
````csharp
EntityExtensionManager.AddProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
);
````
* 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名).
* 你还需要提供一个使用[EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)定义数据库映射属性的操作.
> 必须在使用相关的 `DbContext` 之前执行此代码. 应用程序启动模板定义了一个名为 `YourProjectNameEntityExtensions` 的静态类. 你可以在此类中定义扩展确保在正确的时间执行它. 否则你需要自己处理.
定义实体扩展后你需要使用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迁移类并更新数据库.
然后你可以使用上一部分中定义的相同额外属性系统来操纵实体上的属性.
## 创建新实体映射到同一个数据库表/Collection

9
docs/zh-Hans/Entities.md

@ -365,14 +365,17 @@ public static class IdentityUserExtensions
存储字典的方式取决于你使用的数据库提供程序.
* 对于 [Entity Framework Core](Entity-Framework-Core.md), 它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成.
* 对于 [Entity Framework Core](Entity-Framework-Core.md),这是两种类型的配置;
* 默认它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成.
* 如果需要,你可以使用 `EntityExtensionManager` 为所需的额外属性定义一个单独的数据库字段. 那些使用 `EntityExtensionManager` 配置的属性继续使用单个 `JSON` 字段. 当你使用预构建的[应用模块](Modules/Index.md)并且想要[扩展模块的实体](Customizing-Application-Modules-Extending-Entities.md). 参阅[EF Core迁移文档](Entity-Framework-Core.md)了解如何使用 `EntityExtensionManager`.
* 对于 [MongoDB](MongoDB.md), 它以 **常规字段** 存储, 因为 MongoDB 天生支持这种 [额外](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) 系统.
### 讨论额外的属性
如果你使用**可重复使用的模块**,其中定义了一个实体,你想使用简单的方式get/set此实体相关的一些数据,那么额外的属性系统是非常有用的. 通常 **不需要** 为自己的实体使用这个系统,是因为它有以下缺点:
如果你使用**可重复使用的模块**,其中定义了一个实体,你想使用简单的方式get/set此实体相关的一些数据,那么额外的属性系统是非常有用的.
你通常 **不需要** 为自己的实体使用这个系统,是因为它有以下缺点:
* 它不是**完全类型安全的**.
* 它不是**完全类型安全的**,因为它使用字符串用作属性名称.
* 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**.
* 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易.

99
docs/zh-Hans/Entity-Framework-Core-Migrations.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")]
@ -119,15 +119,15 @@ public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
builder.Entity<AppUser>(b =>
{
//Sharing the same table "AbpUsers" with the IdentityUser
b.ToTable("AbpUsers");
//Sharing the same Users table with the IdentityUser
b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users");
//Configure base properties
b.ConfigureByConvention();
b.ConfigureAbpUser();
//Moved customization of the "AbpUsers" table to an extension method
b.ConfigureCustomUserProperties();
/* Configure mappings for your additional properties
* Also see the MyProjectNameEntityExtensions class
*/
});
/* Configure your own tables/entities inside the ConfigureBookStore method */
@ -190,12 +190,6 @@ public class BookStoreMigrationsDbContext : AbpDbContext<BookStoreMigrationsDbCo
builder.ConfigureFeatureManagement();
builder.ConfigureTenantManagement();
/* Configure customizations for entities from the modules included */
builder.Entity<IdentityUser>(b =>
{
b.ConfigureCustomUserProperties();
});
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
}
@ -276,7 +270,7 @@ public class BackgroundJobsDbContext
您可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择:
1. 你可以**直接使用模块定义的实体**.
1. 你可以**直接使用模块定义的实体**(你仍然可以在某种程度上[扩展实体](Customizing-Application-Modules-Extending-Entities.md)).
2. 你可以**创建一个新的实体**映射到同一个数据库表。
###### 使用由模块定义的实体
@ -379,10 +373,8 @@ protected override void OnModelCreating(ModelBuilder builder)
builder.Entity<AppRole>(b =>
{
b.ToTable("AbpRoles");
b.ConfigureByConvention();
b.ConfigureCustomRoleProperties();
b.Property(x => x.Title).HasMaxLength(128);
});
...
@ -399,68 +391,42 @@ protected override void OnModelCreating(ModelBuilder builder)
builder.Entity<AppRole>(b =>
{
b.ToTable("AbpRoles");
b.ConfigureByConvention();
b.ConfigureCustomRoleProperties();
b.Property(x => x.Title).HasMaxLength(128);
});
````
* 它映射到 `AbpRoles` 表,与 `IdentityRole` 实体共享.
* `ConfigureByConvention()` 配置了标准/基本属性(像`TenantId`),建议总是调用它.
`ConfigureCustomRoleProperties()` 还不存在. 在 `BookStoreDbContextModelCreatingExtensions` 类中定义它 (在 `.EntityFrameworkCore` 项目的 `DbContext` 附近):
你已经为你的 `DbContext` 配置自定义属性,该属性在应用程序运行时使用.
与其直接更改 `MigrationsDbContext`,我们应该使用ABP框架的实体扩展系统,在解决方案的 `.EntityFrameworkCore` 项目中找到 `YourProjectNameEntityExtensions` 类(本示例中是 `BookStoreEntityExtensions`)并且进行以下更改:
````csharp
public static void ConfigureCustomRoleProperties<TRole>(this EntityTypeBuilder<TRole> b)
where TRole : class, IEntity<Guid>
public static class MyProjectNameEntityExtensions
{
b.Property<string>(nameof(AppRole.Title)).HasMaxLength(128);
}
````
* 这个方法只定义实体的**自定义属性**.
* 遗憾的是,我们不能在这里充分的利用**类型安全**(通过引用`AppRole`实体). 我们能做的最好就是使用 `Title` 名称做为类型安全。
你已经为运行应用程序使用的 `DbContext` 配置了自定义属性. 我们还需要配置 `MigrationsDbContext`.
打开`MigrationsDbContext`(本例是 `BookStoreMigrationsDbContext`)进行以下更改:
````csharp
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
...
/* Configure customizations for entities from the modules included */
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
//CONFIGURE THE CUSTOM ROLE PROPERTIES
builder.Entity<IdentityRole>(b =>
public static void Configure()
{
b.ConfigureCustomRoleProperties();
});
...
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
OneTimeRunner.Run(() =>
{
EntityExtensionManager.AddProperty<IdentityRole, string>(
"Title",
b => { b.HasMaxLength(128); }
);
});
}
}
````
只增加下面几行:
> 我们建议使用 `nameof(AppRole.Title)` 而不是硬编码 "Title" 字符串
````csharp
builder.Entity<IdentityRole>(b =>
{
b.ConfigureCustomRoleProperties();
});
````
`EntityExtensionManager` 用于添加属性到现有的实体. 由于 `EntityExtensionManager` 是静态的,因此应调用一次. `OneTimeRunner` 是ABP框架定义简单的工具类.
通过这种方式,我们重用了用于为角色配置自定义属性映射的扩展方法. 但是对 `IdentityRole` 实体进行了相同的自定义.
参阅[EF Core集成文档](Entity-Framework-Core.md)了解更多关于实体扩展系统.
我们在两个类中都重复了类似的数据库映射代码,例如 `HasMaxLength(128)`.
现在你可以在包管理控制台(记得选择 `.EntityFrameworkCore.DbMigrations` 做为PMC的默认项目并将 `.Web` 项目设置为启动项目)使用标准的 `Add-Migration` 命令添加一个新的EF Core数据库迁移.
@ -540,7 +506,7 @@ public class AppRoleAppService : ApplicationService, IAppRoleAppService
###### 使用ExtraProperties
所有从 `AggregateRoot` 派生的实体都可以在 `ExtraProperties` 属性中存储键值对, 它是 `Dictionary<string, object>` 类型在数据库中被序列化为JSON. 所以你可以在字典中添加值用于查询,无需更改实体.
所有从 `AggregateRoot` 派生的实体都可以在 `ExtraProperties` 属性(因为它们都实现了 `IHasExtraProperties` 接口)中存储键值对, 它是 `Dictionary<string, object>` 类型在数据库中被序列化为JSON. 所以你可以在字典中添加值用于查询,无需更改实体.
例如你可以将查询属性 `Title` 存储在 `IdentityRole` 中,而不是创建一个新的实体.
例:
@ -558,16 +524,13 @@ public class IdentityRoleExtendingService : ITransientDependency
public async Task<string> GetTitleAsync(Guid id)
{
var role = await _identityRoleRepository.GetAsync(id);
return role.GetProperty<string>("Title");
}
public async Task SetTitleAsync(Guid id, string newTitle)
{
var role = await _identityRoleRepository.GetAsync(id);
role.SetProperty("Title", newTitle);
await _identityRoleRepository.UpdateAsync(role);
}
}
@ -580,6 +543,12 @@ public class IdentityRoleExtendingService : ITransientDependency
* 所有的额外属性都存储在数据库中的一个**JSON对象**,它们不是作为表的字段存储,与简单的表字段相比创建索引和针对此属性使用SQL查询将更加困难.
* 属性名称是字符串,他们**不是类型安全的**. 建议这些类型的属性定义常量,以防止拼写错误.
###### 使用实体扩展系统
实体扩展系统解决了额外属性主要的问题: 它可以将额外属性做为**标准表字段**存储到数据库.
你需要做的就是如上所诉使用 `EntityExtensionManager` 定义额外属性, 然后你就可以使得 `GetProperty``SetProperty` 方法对实体的属性进行get/set,但是这时它存储在数据库表的单独字段中.
###### 创建新表
你可以创建**自己的表**来存储属性,而不是创建新实体并映射到同一表. 你通常复制原始实体的一些值. 例如可以将 `Name` 字段添加到你自己的表中,它是原表中 `Name` 字段的副本.

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

@ -58,6 +58,53 @@ namespace MyCompany.MyProject
}
````
### 关于EF Core Fluent Mapping
[应用程序启动模板](Startup-Templates/Application.md)已配置使用[EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/)映射你的实体到数据库表.
你依然为你的实体属性使用**data annotation attributes**(像`[Required]`),而ABP文档通常遵循**fluent mapping API** approach方法. 如何使用取决与你.
ABP框架有一些**实体基类**和**约定**(参阅[实体文档](Entities.md))提供了一些有用的扩展方法来配置从基本实体类继承的属性.
#### ConfigureByConvention 方法
`ConfigureByConvention()` 是主要的扩展方法,它对你的实体**配置所有的基本属性**和约定. 所以在你的流利映射代码中为你所有的实体调用这个方法是 **最佳实践**,
**示例**: 假设你有一个直接继承 `AggregateRoot<Guid>` 基类的 `Book` 实体:
````csharp
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; }
}
````
你可以在你的 `DbContext` 重写 `OnModelCreating` 方法并且做以下配置:
````csharp
protected override void OnModelCreating(ModelBuilder builder)
{
//Always call the base method
base.OnModelCreating(builder);
builder.Entity<Book>(b =>
{
b.ToTable("Books");
//Configure the base properties
b.ConfigureByConvention();
//Configure other properties (if you are using the fluent API)
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
}
````
* 这里调用了 `b.ConfigureByConvention()` 它对于**配置基本属性**非常重要.
* 你可以在这里配置 `Name` 属性或者使用**data annotation attributes**(参阅[EF Core 文档](https://docs.microsoft.com/zh-cn/ef/core/modeling/entity-properties)).
> 尽管有许多扩展方法可以配置基本属性,但如果需要 `ConfigureByConvention()` 内部会调用它们. 因此仅调用它就足够了.
### 配置连接字符串选择
如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称.
@ -225,7 +272,7 @@ public override async Task DeleteAsync(
}
````
### 访问 EF Core API
## 访问 EF Core API
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例:
@ -251,9 +298,59 @@ public class BookService
> 要点: 你必须在使用`DbContext`的项目里引用`Volo.Abp.EntityFrameworkCore`包. 这会破坏封装,但在这种情况下,这就是你需要的.
### 高级主题
## Extra Properties & Entity Extension Manager
额外属性系统允许你为实现了 `IHasExtraProperties` 的实体set/get动态属性. 当你想将自定义属性添加到[应用程序模块](Modules/Index.md)中定义的实体时,它特别有用.
默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中. 实体扩展系统允许你存储额外属性在数据库的单独字段中.
#### 设置默认仓储类
有关额外属性和实体扩展系统的更多信息,请参阅下列文档:
* [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md)
* [实体](Entities.md)
本节只解释了 `EntityExtensionManager` 及其用法.
### AddProperty 方法
`EntityExtensionManager``AddProperty` 方法允许你实体定义附加的属性.
**示例**: 添加 `Title` 属性 (数据库字段)到 `IdentityRole` 实体:
````csharp
EntityExtensionManager.AddProperty<IdentityRole, string>(
"Title",
b => { b.HasMaxLength(128); }
);
````
如果相关模块已实现此功能(通过使用下面说明的 `ConfigureExtensions`)则将新属性添加到模型中. 然后你需要运行标准的 `Add-Migration``Update-Database` 命令更新数据库以添加新字段.
>`AddProperty` 方法必须在使用相关的 `DbContext` 之前调用,它是一个静态方法. 最好的方法是尽早的应用程序中使用它. 应用程序启动模板含有 `YourProjectNameEntityExtensions` 类,可以在放心的在此类中使用此方法.
### ConfigureExtensions
如果你正在开发一个可重用使用的模块,并允许应用程序开发人员将属性添加到你的实体,你可以在实体映射使用 `ConfigureExtensions` 扩展方法:
````csharp
builder.Entity<YourEntity>(b =>
{
b.ConfigureExtensions();
//...
});
````
如果你调用 `ConfigureByConvention()` 扩展方法(在此示例中 `b.ConfigureByConvention`),ABP框架内部会调用 `ConfigureExtensions` 方法. 使用 `ConfigureByConvention` 方法是**最佳实践**,因为它还按照约定配置基本属性的数据库映射.
参阅上面提到的 "*ConfigureByConvention 方法*" 了解更多信息.
### GetPropertyNames
`EntityExtensionManager.GetPropertyNames` 静态方法可以用作为此实体定义的扩展属性的名称. 应用程序代码通常不需要,但是ABP框架在内部使用它.
## 高级主题
### 设置默认仓储类
默认的通用仓储的默认实现是`EfCoreRepository`类,你可以创建自己的实现,并将其做为默认实现
@ -343,3 +440,7 @@ context.Services.AddAbpDbContext<OtherDbContext>(options =>
````
在这个例子中,`OtherDbContext`实现了`IBookStoreDbContext`. 此功能允许你在开发时使用多个DbContext(每个模块一个),但在运行时可以使用单个DbContext(实现所有DbContext的所有接口).
## 另请参阅
* [实体](Entities.md)

12
modules/docs/app/VoloDocs.Web/Controllers/HomeController.cs

@ -1,12 +0,0 @@
using Volo.Abp.AspNetCore.Mvc;
namespace VoloDocs.Web.Controllers
{
public class HomeController : AbpController
{
public void Index()
{
}
}
}

18
modules/docs/app/VoloDocs.Web/Pages/Index.cshtml

@ -3,3 +3,21 @@
@{
}
@if (!Model.Projects.Any())
{
<abp-alert alert-type="Warning">
<strong>No projects found!</strong><br />
See <a href=" https://docs.abp.io/en/abp/latest/Modules/Docs">documentation</a> to see how you can create a new one.
</abp-alert>
}
else
{
<h1>Projects</h1>
<abp-list-group class="mt-5">
@foreach (var project in Model.Projects)
{
<abp-list-group-item href="@Model.GetUrlForProject(project)">@project.Name</abp-list-group-item>
}
</abp-list-group>
}

42
modules/docs/app/VoloDocs.Web/Pages/Index.cshtml.cs

@ -1,29 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using Volo.Docs;
using Volo.Docs.Projects;
namespace VoloDocs.Web.Pages
{
public class IndexModel : PageModel
{
public IReadOnlyList<ProjectDto> Projects { get; set; }
private readonly DocsUiOptions _urlUiOptions;
public IndexModel(IOptions<DocsUiOptions> urlOptions)
private readonly IProjectAppService _projectAppService;
public IndexModel(IOptions<DocsUiOptions> urlOptions, IProjectAppService projectAppService)
{
_projectAppService = projectAppService;
_urlUiOptions = urlOptions.Value;
}
public IActionResult OnGet()
public async Task<IActionResult> OnGetAsync()
{
//TODO: Create HomeController & Index instead of Page. Otherwise, we have an empty Index.cshtml file.
if (!_urlUiOptions.RoutePrefix.IsNullOrWhiteSpace())
var projects = await _projectAppService.GetListAsync();
if (projects.Items.Count == 1)
{
return Redirect("." + _urlUiOptions.RoutePrefix);
return await RedirectToProjectAsync(projects.Items.First());
}
else if (projects.Items.Count > 1)
{
Projects = projects.Items;
}
return Page();
}
private async Task<IActionResult> RedirectToProjectAsync(ProjectDto project, string language = "en", string version = null)
{
var path = GetUrlForProject(project, language, version);
return await Task.FromResult(Redirect(path));
}
//Eg: "/en/abp/latest"
public string GetUrlForProject(ProjectDto project, string language = "en", string version = null)
{
return "." +
_urlUiOptions.RoutePrefix.EnsureStartsWith('/').EnsureEndsWith('/') +
language.EnsureEndsWith('/') +
project.ShortName.EnsureEndsWith('/') +
(version ?? DocsAppConsts.Latest);
}
}
}
}

2796
modules/docs/app/VoloDocs.Web/package-lock.json

File diff suppressed because it is too large

2
modules/docs/app/VoloDocs.Web/package.json

@ -6,4 +6,4 @@
"@abp/aspnetcore.mvc.ui.theme.basic": "^1.0.2",
"@abp/docs": "^1.0.2"
}
}
}

12
modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs

@ -4,11 +4,13 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Newtonsoft.Json;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Docs.Documents;
using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.Projects;
using Volo.Extensions;
namespace Volo.Docs.Admin.Documents
{
@ -38,15 +40,19 @@ namespace Volo.Docs.Admin.Documents
{
var project = await _projectRepository.GetAsync(input.ProjectId);
var navigationFile = await GetDocumentAsync(
var navigationDocument = await GetDocumentAsync(
project,
project.NavigationDocumentName,
input.LanguageCode,
input.Version
);
var nav = JsonConvert.DeserializeObject<NavigationNode>(navigationFile.Content);
var leafs = nav.Items.GetAllNodes(x => x.Items)
if (!JsonConvertExtensions.TryDeserializeObject<NavigationNode>(navigationDocument.Content, out var navigation))
{
throw new UserFriendlyException($"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}.");
}
var leafs = navigation.Items.GetAllNodes(x => x.Items)
.Where(x => x.IsLeaf && !x.Path.IsNullOrWhiteSpace())
.ToList();

20
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs

@ -7,9 +7,11 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.Projects;
using Volo.Extensions;
namespace Volo.Docs.Documents
{
@ -32,8 +34,8 @@ namespace Volo.Docs.Documents
IDistributedCache<LanguageConfig> languageCache,
IDistributedCache<DocumentResourceDto> resourceCache,
IDistributedCache<DocumentUpdateInfo> documentUpdateCache,
IHostEnvironment hostEnvironment,
IDocumentFullSearch documentFullSearch,
IHostEnvironment hostEnvironment,
IDocumentFullSearch documentFullSearch,
IOptions<DocsElasticSearchOptions> docsElasticSearchOptions)
{
_projectRepository = projectRepository;
@ -82,7 +84,10 @@ namespace Volo.Docs.Documents
input.Version
);
var navigationNode = JsonConvert.DeserializeObject<NavigationNode>(navigationDocument.Content);
if (!JsonConvertExtensions.TryDeserializeObject<NavigationNode>(navigationDocument.Content, out var navigationNode))
{
throw new UserFriendlyException($"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}.");
}
var leafs = navigationNode.Items.GetAllNodes(x => x.Items)
.Where(x => !x.Path.IsNullOrWhiteSpace())
@ -173,11 +178,16 @@ namespace Volo.Docs.Documents
input.Version
);
return JsonConvert.DeserializeObject<DocumentParametersDto>(document.Content);
if (!JsonConvertExtensions.TryDeserializeObject<DocumentParametersDto>(document.Content, out var documentParameters))
{
throw new UserFriendlyException($"Cannot validate document parameters file '{project.ParametersDocumentName}' for the project {project.Name}.");
}
return documentParameters;
}
catch (DocumentNotFoundException)
{
Logger.LogWarning($"Parameter file ({project.ParametersDocumentName}) not found.");
Logger.LogWarning($"Parameter file ({project.ParametersDocumentName}) not found!");
return new DocumentParametersDto();
}
}

7
modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainConsts.cs

@ -0,0 +1,7 @@
namespace Volo.Docs
{
public class DocsDomainConsts
{
public static string LanguageConfigFileName = "docs-langs.json";
}
}

15
modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs

@ -4,11 +4,13 @@ using System.IO;
using System.Security;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Volo.Abp;
using Volo.Abp.Domain.Services;
using Volo.Abp.IO;
using Volo.Docs.Documents;
using Volo.Docs.FileSystem.Projects;
using Volo.Docs.Projects;
using Volo.Extensions;
namespace Volo.Docs.FileSystem.Documents
{
@ -22,7 +24,7 @@ namespace Volo.Docs.FileSystem.Documents
var path = Path.Combine(projectFolder, languageCode, documentName);
CheckDirectorySecurity(projectFolder, path);
var content = await FileHelper.ReadAllTextAsync(path);
var localDirectory = "";
@ -55,10 +57,15 @@ namespace Volo.Docs.FileSystem.Documents
public async Task<LanguageConfig> GetLanguageListAsync(Project project, string version)
{
var path = Path.Combine(project.GetFileSystemPath(), "docs-langs.json");
var configAsJson = await FileHelper.ReadAllTextAsync(path);
var path = Path.Combine(project.GetFileSystemPath(), DocsDomainConsts.LanguageConfigFileName);
var configJsonContent = await FileHelper.ReadAllTextAsync(path);
if (!JsonConvertExtensions.TryDeserializeObject<LanguageConfig>(configJsonContent, out var languageConfig))
{
throw new UserFriendlyException($"Cannot validate language config file '{DocsDomainConsts.LanguageConfigFileName}' for the project {project.Name}.");
}
return JsonConvert.DeserializeObject<LanguageConfig>(configAsJson);
return languageConfig;
}
public async Task<DocumentResource> GetResource(Project project, string resourceName, string languageCode, string version)

15
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs

@ -10,6 +10,8 @@ using Volo.Docs.GitHub.Projects;
using Volo.Docs.Projects;
using Newtonsoft.Json.Linq;
using Octokit;
using Volo.Abp;
using Volo.Extensions;
using Project = Volo.Docs.Projects.Project;
namespace Volo.Docs.GitHub.Documents
@ -55,11 +57,11 @@ namespace Volo.Docs.GitHub.Documents
var lastSignificantUpdateTime = !isNavigationDocument && !isParameterDocument && version == project.LatestVersionBranchName ?
await GetLastSignificantUpdateTime(
fileCommits,
project,
project,
project.GetGitHubInnerUrl(languageCode, documentName),
lastKnownSignificantUpdateTime,
documentCreationTime
) ?? lastKnownSignificantUpdateTime
) ?? lastKnownSignificantUpdateTime
: null;
var document = new Document(GuidGenerator.Create(),
@ -179,11 +181,16 @@ namespace Volo.Docs.GitHub.Documents
var rootUrl = project.GetGitHubUrl(version);
var userAgent = project.GetGithubUserAgentOrNull();
var url = CalculateRawRootUrl(rootUrl) + "docs-langs.json";
var url = CalculateRawRootUrl(rootUrl) + DocsDomainConsts.LanguageConfigFileName;
var configAsJson = await DownloadWebContentAsStringAsync(url, token, userAgent);
return JsonConvert.DeserializeObject<LanguageConfig>(configAsJson);
if (!JsonConvertExtensions.TryDeserializeObject<LanguageConfig>(configAsJson, out var languageConfig))
{
throw new UserFriendlyException($"Cannot validate language config file '{DocsDomainConsts.LanguageConfigFileName}' for the project {project.Name} - v{version}.");
}
return languageConfig;
}
private async Task<IReadOnlyList<Release>> GetReleasesAsync(Project project)

69
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs

@ -23,54 +23,25 @@ namespace Volo.Docs.GitHub.Documents
public async Task<string> GetFileRawStringContentAsync(string rawUrl, string token, string userAgent)
{
var httpClient = _clientFactory.CreateClient(HttpClientName);
if (!token.IsNullOrWhiteSpace())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token);
}
if (!userAgent.IsNullOrWhiteSpace())
{
httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent);
}
using var httpClient = CreateHttpClient(token, userAgent);
return await httpClient.GetStringAsync(new Uri(rawUrl));
}
public async Task<byte[]> GetFileRawByteArrayContentAsync(string rawUrl, string token, string userAgent)
{
var httpClient = _clientFactory.CreateClient(HttpClientName);
if (!token.IsNullOrWhiteSpace())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token);
}
if (!userAgent.IsNullOrWhiteSpace())
{
httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent);
}
using var httpClient = CreateHttpClient(token, userAgent);
return await httpClient.GetByteArrayAsync(new Uri(rawUrl));
}
public async Task<IReadOnlyList<Release>> GetReleasesAsync(string name, string repositoryName, string token)
{
var client = token.IsNullOrWhiteSpace()
? new GitHubClient(new ProductHeaderValue(name))
: new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token)));
return (await client
.Repository
.Release
.GetAll(name, repositoryName)).ToList();
var client = GetGitHubClient(name, token);
return await client.Repository.Release.GetAll(name, repositoryName);
}
public async Task<IReadOnlyList<GitHubCommit>> GetFileCommitsAsync(string name, string repositoryName, string version, string filename, string token)
{
var client = token.IsNullOrWhiteSpace()
? new GitHubClient(new ProductHeaderValue(name))
: new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token)));
var client = GetGitHubClient(name, token);
var repo = await client.Repository.Get(name, repositoryName);
var request = new CommitRequest { Path = filename, Sha = version };
return await client.Repository.Commit.GetAll(repo.Id, request);
@ -78,12 +49,32 @@ namespace Volo.Docs.GitHub.Documents
public async Task<GitHubCommit> GetSingleCommitsAsync(string name, string repositoryName, string sha, string token)
{
var client = token.IsNullOrWhiteSpace()
? new GitHubClient(new ProductHeaderValue(name))
: new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token)));
var client = GetGitHubClient(name, token);
var repo = await client.Repository.Get(name, repositoryName);
return await client.Repository.Commit.Get(repo.Id, sha);
}
private HttpClient CreateHttpClient(string token, string userAgent)
{
var httpClient = _clientFactory.CreateClient(HttpClientName);
if (!token.IsNullOrWhiteSpace())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token);
}
if (!userAgent.IsNullOrWhiteSpace())
{
httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent);
}
return httpClient;
}
private static GitHubClient GetGitHubClient(string name, string token)
{
return token.IsNullOrWhiteSpace()
? new GitHubClient(new ProductHeaderValue(name))
: new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token)));
}
}
}

22
modules/docs/src/Volo.Docs.Domain/Volo/Extensions/NewtonsoftJsonExtensions.cs

@ -0,0 +1,22 @@
using System;
using Newtonsoft.Json;
namespace Volo.Extensions
{
public static class JsonConvertExtensions
{
public static bool TryDeserializeObject<T>(string jsonContent, out T result)
{
try
{
result = JsonConvert.DeserializeObject<T>(jsonContent);
return true;
}
catch
{
result = default;
return false;
}
}
}
}

109
modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs

@ -7,16 +7,17 @@ using Newtonsoft.Json;
using Scriban;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Docs.Documents;
using Volo.Abp;
using Volo.Extensions;
namespace Volo.Docs.HtmlConverting
{
public class ScribanDocumentSectionRenderer : IDocumentSectionRenderer
{
private const string jsonOpener = "````json";
private const string jsonCloser = "````";
private const string docs_param = "//[doc-params]";
private const string docs_templates = "//[doc-template]";
private const string JsonOpener = "````json";
private const string JsonCloser = "````";
private const string DocsParam = "//[doc-params]";
private const string DocsTemplates = "//[doc-template]";
public ILogger<ScribanDocumentSectionRenderer> Logger { get; set; }
@ -40,6 +41,7 @@ namespace Volo.Docs.HtmlConverting
}
var result = await scribanTemplate.RenderAsync(parameters);
return RemoveOptionsJson(result);
}
@ -47,7 +49,7 @@ namespace Volo.Docs.HtmlConverting
{
try
{
if (!document.Contains(jsonOpener) || !document.Contains(docs_param))
if (!document.Contains(JsonOpener) || !document.Contains(DocsParam))
{
return new Dictionary<string, List<string>>();
}
@ -59,9 +61,14 @@ namespace Volo.Docs.HtmlConverting
return new Dictionary<string, List<string>>();
}
var pureJson = insideJsonSection.Replace(docs_param, "").Trim();
var pureJson = insideJsonSection.Replace(DocsParam, "").Trim();
if (!JsonConvertExtensions.TryDeserializeObject<Dictionary<string, List<string>>>(pureJson, out var availableParameters))
{
throw new UserFriendlyException("ERROR-20200327: Cannot validate JSON content for `AvailableParameters`!");
}
return JsonConvert.DeserializeObject<Dictionary<string, List<string>>>(pureJson);
return await Task.FromResult(availableParameters);
}
catch (Exception)
{
@ -70,12 +77,13 @@ namespace Volo.Docs.HtmlConverting
}
}
private string RemoveOptionsJson(string document)
private static string RemoveOptionsJson(string document)
{
var orgDocument = document;
try
{
if (!document.Contains(jsonOpener) || !document.Contains(docs_param))
if (!document.Contains(JsonOpener) || !document.Contains(DocsParam))
{
return orgDocument;
}
@ -88,8 +96,9 @@ namespace Volo.Docs.HtmlConverting
}
return document.Remove(
jsonBeginningIndex - jsonOpener.Length, (jsonEndingIndex + jsonCloser.Length) - (jsonBeginningIndex - jsonOpener.Length)
);
jsonBeginningIndex - JsonOpener.Length,
(jsonEndingIndex + JsonCloser.Length) - (jsonBeginningIndex - JsonOpener.Length)
);
}
catch (Exception)
{
@ -97,25 +106,25 @@ namespace Volo.Docs.HtmlConverting
}
}
private (int, int, string) GetJsonBeginEndIndexesAndPureJson(string document)
private static (int, int, string) GetJsonBeginEndIndexesAndPureJson(string document)
{
var searchedIndex = 0;
while (searchedIndex < document.Length)
{
var jsonBeginningIndex = document.Substring(searchedIndex).IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length + searchedIndex;
var jsonBeginningIndex = document.Substring(searchedIndex).IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length + searchedIndex;
if (jsonBeginningIndex < 0)
{
return (-1, -1, "");
}
var jsonEndingIndex = document.Substring(jsonBeginningIndex).IndexOf(jsonCloser, StringComparison.Ordinal) + jsonBeginningIndex;
var jsonEndingIndex = document.Substring(jsonBeginningIndex).IndexOf(JsonCloser, StringComparison.Ordinal) + jsonBeginningIndex;
var insideJsonSection = document[jsonBeginningIndex..jsonEndingIndex];
if (insideJsonSection.IndexOf(docs_param) < 0)
if (insideJsonSection.IndexOf(DocsParam, StringComparison.Ordinal) < 0)
{
searchedIndex = jsonEndingIndex + jsonCloser.Length;
searchedIndex = jsonEndingIndex + JsonCloser.Length;
continue;
}
@ -129,68 +138,84 @@ namespace Volo.Docs.HtmlConverting
{
var templates = new List<DocumentPartialTemplateWithValuesDto>();
while (documentContent.Contains(jsonOpener))
while (documentContent.Contains(JsonOpener))
{
var afterJsonOpener = documentContent.Substring(
documentContent.IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length);
documentContent.IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length
);
var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0,
afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal));
afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal)
);
documentContent = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal) + jsonCloser.Length);
afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + JsonCloser.Length
);
if (!betweenJsonOpenerAndCloser.Contains(docs_templates))
if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates))
{
continue;
}
var json = betweenJsonOpenerAndCloser.Substring(betweenJsonOpenerAndCloser.IndexOf(docs_templates, StringComparison.Ordinal) + docs_templates.Length);
var json = betweenJsonOpenerAndCloser.Substring(betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) + DocsTemplates.Length);
var template = JsonConvert.DeserializeObject<DocumentPartialTemplateWithValuesDto>(json);
if (!JsonConvertExtensions.TryDeserializeObject<DocumentPartialTemplateWithValuesDto>(json, out var template))
{
throw new UserFriendlyException($"ERROR-20200327: Cannot validate JSON content for `AvailableParameters`!");
}
templates.Add(template);
}
return templates;
return await Task.FromResult(templates);
}
private string SetPartialTemplates(string document, List<DocumentPartialTemplateContent> templates)
private static string SetPartialTemplates(string document, IReadOnlyCollection<DocumentPartialTemplateContent> templates)
{
var newDocument = new StringBuilder();
while (document.Contains(jsonOpener))
while (document.Contains(JsonOpener))
{
var beforeJson = document.Substring(0,
document.IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length);
document.IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length
);
var afterJsonOpener = document.Substring(
document.IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length);
document.IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length
);
var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0,
afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal));
afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal)
);
if (!betweenJsonOpenerAndCloser.Contains(docs_templates))
if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates))
{
document = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal) + jsonCloser.Length);
newDocument.Append(beforeJson + betweenJsonOpenerAndCloser + jsonCloser);
afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + JsonCloser.Length
);
newDocument.Append(beforeJson + betweenJsonOpenerAndCloser + JsonCloser);
continue;
}
var json = betweenJsonOpenerAndCloser.Substring(betweenJsonOpenerAndCloser.IndexOf(docs_templates, StringComparison.Ordinal) + docs_templates.Length);
var templatePath = JsonConvert.DeserializeObject<DocumentPartialTemplateWithValuesDto>(json)?.Path;
var json = betweenJsonOpenerAndCloser.Substring(
betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) + DocsTemplates.Length
);
var template = templates.FirstOrDefault(t => t.Path == templatePath);
if (JsonConvertExtensions.TryDeserializeObject<DocumentPartialTemplateWithValuesDto>(json, out var documentPartialTemplateWithValuesDto))
{
var template = templates.FirstOrDefault(t => t.Path == documentPartialTemplateWithValuesDto.Path);
var beforeTemplate = document.Substring(0,
document.IndexOf(jsonOpener, StringComparison.Ordinal));
var beforeTemplate = document.Substring(0,
document.IndexOf(JsonOpener, StringComparison.Ordinal)
);
newDocument.Append(beforeTemplate + template?.Content + jsonCloser);
newDocument.Append(beforeTemplate + template?.Content + JsonCloser);
document = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal) + jsonCloser.Length);
document = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + JsonCloser.Length
);
}
}
newDocument.Append(document);

1
modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj

@ -19,6 +19,7 @@
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Packages\Volo.Abp.AspNetCore.Mvc.UI.Packages.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" />
<ProjectReference Include="..\Volo.Docs.Domain\Volo.Docs.Domain.csproj" />
<ProjectReference Include="..\Volo.Docs.HttpApi\Volo.Docs.HttpApi.csproj" />
<PackageReference Include="Markdig.Signed" Version="0.18.0" />
<PackageReference Include="Scriban" Version="2.1.1" />

2
npm/ng-packs/angular.json

@ -526,5 +526,5 @@
}
}
},
"defaultProject": "core"
"defaultProject": "dev-app"
}

3
npm/ng-packs/apps/dev-app/src/app/app.module.ts

@ -21,9 +21,6 @@ const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })];
imports: [
CoreModule.forRoot({
environment,
requirements: {
layouts: LAYOUTS,
},
}),
ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot({ redirectUrl: '/' }),

9
npm/ng-packs/packages/account/src/lib/services/account.service.ts

@ -7,6 +7,8 @@ import { RegisterResponse, RegisterRequest, TenantIdResponse } from '../models';
providedIn: 'root',
})
export class AccountService {
apiName = 'AbpAccount';
constructor(private rest: RestService) {}
findTenant(tenantName: string): Observable<TenantIdResponse> {
@ -15,7 +17,7 @@ export class AccountService {
url: `/api/abp/multi-tenancy/tenants/by-name/${tenantName}`,
};
return this.rest.request<null, TenantIdResponse>(request);
return this.rest.request<null, TenantIdResponse>(request, { apiName: this.apiName });
}
register(body: RegisterRequest): Observable<RegisterResponse> {
@ -25,6 +27,9 @@ export class AccountService {
body,
};
return this.rest.request<RegisterRequest, RegisterResponse>(request, { skipHandleError: true });
return this.rest.request<RegisterRequest, RegisterResponse>(request, {
skipHandleError: true,
apiName: this.apiName,
});
}
}

41
npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts

@ -1,12 +1,12 @@
import { Component, Input, OnDestroy, Type, Injector } from '@angular/core';
import { Component, OnDestroy, Type } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router, UrlSegment } from '@angular/router';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Store } from '@ngxs/store';
import snq from 'snq';
import { eLayoutType } from '../enums/common';
import { Config } from '../models/config';
import { ABP } from '../models/common';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ConfigState } from '../states/config.state';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
import { takeUntilDestroy } from '../utils/rxjs-utils';
@Component({
@ -20,24 +20,10 @@ import { takeUntilDestroy } from '../utils/rxjs-utils';
`,
})
export class DynamicLayoutComponent implements OnDestroy {
@Select(ConfigState.getOne('requirements')) requirements$: Observable<Config.Requirements>;
layout: Type<any>;
constructor(private router: Router, private route: ActivatedRoute, private store: Store) {
const {
requirements: { layouts },
routes,
} = this.store.selectSnapshot(ConfigState.getAll);
if ((this.route.snapshot.data || {}).layout) {
this.layout = layouts
.filter(l => !!l)
.find(
(l: any) =>
snq(() => l.type.toLowerCase().indexOf(this.route.snapshot.data.layout), -1) > -1,
);
}
const { routes } = this.store.selectSnapshot(ConfigState.getAll);
router.events.pipe(takeUntilDestroy(this)).subscribe(event => {
if (event instanceof NavigationEnd) {
@ -45,15 +31,24 @@ export class DynamicLayoutComponent implements OnDestroy {
{ path: router.url.replace('/', '') },
] as any);
const layout = (this.route.snapshot.data || {}).layout || findLayout(segments, routes);
const layouts = {
application: this.getComponent('Theme.ApplicationLayoutComponent'),
account: this.getComponent('Theme.AccountLayoutComponent'),
empty: this.getComponent('Theme.EmptyLayoutComponent'),
};
this.layout = layouts
.filter(l => !!l)
.find((l: any) => snq(() => l.type.toLowerCase().indexOf(layout), -1) > -1);
const expectedLayout =
(this.route.snapshot.data || {}).layout || findLayout(segments, routes);
this.layout = layouts[expectedLayout].component;
}
});
}
private getComponent(key: string): ReplaceableComponents.ReplaceableComponent {
return this.store.selectSnapshot(ReplaceableComponentsState.getComponent(key));
}
ngOnDestroy() {}
}

6
npm/ng-packs/packages/core/src/lib/models/common.ts

@ -6,7 +6,11 @@ import { Subject } from 'rxjs';
export namespace ABP {
export interface Root {
environment: Partial<Config.Environment>;
requirements: Config.Requirements;
/**
*
* @deprecated To be deleted in v3.0
*/
requirements?: Config.Requirements;
}
export type PagedResponse<T> = {

12
npm/ng-packs/packages/core/src/lib/services/application-configuration.service.ts

@ -3,12 +3,18 @@ import { Observable } from 'rxjs';
import { Rest } from '../models/rest';
import { ApplicationConfiguration } from '../models/application-configuration';
import { RestService } from './rest.service';
import { Store } from '@ngxs/store';
import { ConfigState } from '../states/config.state';
@Injectable({
providedIn: 'root',
})
export class ApplicationConfigurationService {
constructor(private rest: RestService) {}
get apiName(): string {
return this.store.selectSnapshot(ConfigState.getDeep('environment.application.name'));
}
constructor(private rest: RestService, private store: Store) {}
getConfiguration(): Observable<ApplicationConfiguration.Response> {
const request: Rest.Request<null> = {
@ -16,6 +22,8 @@ export class ApplicationConfigurationService {
url: '/api/abp/application-configuration',
};
return this.rest.request<null, ApplicationConfiguration.Response>(request);
return this.rest.request<null, ApplicationConfiguration.Response>(request, {
apiName: this.apiName,
});
}
}

18
npm/ng-packs/packages/core/src/lib/services/profile.service.ts

@ -7,6 +7,8 @@ import { Profile, Rest } from '../models';
providedIn: 'root',
})
export class ProfileService {
apiName = 'AbpIdentity';
constructor(private rest: RestService) {}
get(): Observable<Profile.Response> {
@ -15,7 +17,7 @@ export class ProfileService {
url: '/api/identity/my-profile',
};
return this.rest.request<null, Profile.Response>(request);
return this.rest.request<null, Profile.Response>(request, { apiName: this.apiName });
}
update(body: Profile.Response): Observable<Profile.Response> {
@ -25,16 +27,24 @@ export class ProfileService {
body,
};
return this.rest.request<Profile.Response, Profile.Response>(request);
return this.rest.request<Profile.Response, Profile.Response>(request, {
apiName: this.apiName,
});
}
changePassword(body: Profile.ChangePasswordRequest, skipHandleError: boolean = false): Observable<null> {
changePassword(
body: Profile.ChangePasswordRequest,
skipHandleError: boolean = false,
): Observable<null> {
const request: Rest.Request<Profile.ChangePasswordRequest> = {
method: 'POST',
url: '/api/identity/my-profile/change-password',
body,
};
return this.rest.request<Profile.ChangePasswordRequest, null>(request, { skipHandleError });
return this.rest.request<Profile.ChangePasswordRequest, null>(request, {
skipHandleError,
apiName: this.apiName,
});
}
}

4
npm/ng-packs/packages/core/src/lib/states/config.state.ts

@ -81,7 +81,7 @@ export class ConfigState {
static getApiUrl(key?: string) {
const selector = createSelector([ConfigState], (state: Config.State): string => {
return state.environment.apis[key || 'default'].url;
return (state.environment.apis[key || 'default'] || state.environment.apis.default).url;
});
return selector;
@ -301,7 +301,7 @@ export class ConfigState {
}
@Action(SetEnvironment)
setEnvironment({ patchState }: StateContext<Config.State>, { environment }:SetEnvironment) {
setEnvironment({ patchState }: StateContext<Config.State>, { environment }: SetEnvironment) {
return patchState({
environment,
});

102
npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts

@ -1,36 +1,36 @@
import { Component, NgModule } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting, SpyObject } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
import { NgxsModule, Store } from '@ngxs/store';
import { DynamicLayoutComponent, RouterOutletComponent } from '../components';
import { eLayoutType } from '../enums';
import { ABP } from '../models';
import { DynamicLayoutComponent, RouterOutletComponent } from '../components';
import { ConfigState, ReplaceableComponentsState } from '../states';
import { ApplicationConfigurationService } from '../services';
@Component({
selector: 'abp-layout-application',
template: '<router-outlet></router-outlet>',
})
class DummyApplicationLayoutComponent {
static type = eLayoutType.application;
}
class DummyApplicationLayoutComponent {}
@Component({
selector: 'abp-layout-account',
template: '<router-outlet></router-outlet>',
})
class DummyAccountLayoutComponent {
static type = eLayoutType.account;
}
class DummyAccountLayoutComponent {}
@Component({
selector: 'abp-layout-empty',
template: '<router-outlet></router-outlet>',
})
class DummyEmptyLayoutComponent {
static type = eLayoutType.empty;
}
class DummyEmptyLayoutComponent {}
const LAYOUTS = [DummyApplicationLayoutComponent, DummyAccountLayoutComponent, DummyEmptyLayoutComponent];
const LAYOUTS = [
DummyApplicationLayoutComponent,
DummyAccountLayoutComponent,
DummyEmptyLayoutComponent,
];
@NgModule({
imports: [RouterModule],
@ -47,13 +47,57 @@ class DummyComponent {
constructor(public route: ActivatedRoute) {}
}
const storeData = {
ConfigState: {
routes: [
{
path: '',
wrapper: true,
children: [
{
path: 'parentWithLayout',
layout: eLayoutType.application,
children: [
{ path: 'childWithoutLayout' },
{ path: 'childWithLayout', layout: eLayoutType.account },
],
},
],
},
{ path: 'withData', layout: eLayoutType.application },
,
] as ABP.FullRoute[],
environment: { application: {} },
},
ReplaceableComponentsState: {
replaceableComponents: [
{
key: 'Theme.ApplicationLayoutComponent',
component: DummyApplicationLayoutComponent,
},
{
key: 'Theme.AccountLayoutComponent',
component: DummyAccountLayoutComponent,
},
{
key: 'Theme.EmptyLayoutComponent',
component: DummyEmptyLayoutComponent,
},
],
},
};
describe('DynamicLayoutComponent', () => {
const createComponent = createRoutingFactory({
component: RouterOutletComponent,
stubsEnabled: false,
mocks: [Store],
declarations: [DummyComponent, DynamicLayoutComponent],
imports: [RouterModule, DummyLayoutModule],
mocks: [ApplicationConfigurationService],
imports: [
RouterModule,
DummyLayoutModule,
NgxsModule.forRoot([ConfigState, ReplaceableComponentsState]),
],
routes: [
{ path: '', component: RouterOutletComponent },
{
@ -100,33 +144,13 @@ describe('DynamicLayoutComponent', () => {
});
let spectator: SpectatorRouting<RouterOutletComponent>;
let store: SpyObject<Store>;
const mockStoreData = {
requirements: { layouts: LAYOUTS },
routes: [
{
path: '',
wrapper: true,
children: [
{
path: 'parentWithLayout',
layout: eLayoutType.application,
children: [{ path: 'childWithoutLayout' }, { path: 'childWithLayout', layout: eLayoutType.account }],
},
],
},
{ path: 'withData', layout: eLayoutType.application },
,
] as ABP.FullRoute[],
environment: { application: {} },
};
let storeSpy: jest.SpyInstance;
let store: Store;
beforeEach(async () => {
spectator = createComponent();
store = spectator.get(Store);
storeSpy = jest.spyOn(store, 'selectSnapshot');
storeSpy.mockReturnValue(mockStoreData);
store.reset(storeData);
});
it('should handle application layout from parent abp route and display it', async () => {
@ -159,7 +183,7 @@ describe('DynamicLayoutComponent', () => {
});
it('should not display any layout when layouts are empty', async () => {
storeSpy.mockReturnValue({ ...mockStoreData, requirements: { layouts: [] } });
store.reset({ ...storeData, ReplaceableComponentsState: {} });
spectator.detectChanges();

870
npm/ng-packs/packages/core/src/lib/tests/linked-list.spec.ts

File diff suppressed because it is too large

257
npm/ng-packs/packages/core/src/lib/utils/linked-list.ts

@ -27,65 +27,107 @@ export class LinkedList<T = any> {
return this.size;
}
private linkWith(
private attach(
value: T,
previousNode: ListNode<T> | undefined,
nextNode: ListNode<T> | undefined,
): ListNode<T> {
const node = new ListNode(value);
if (!previousNode) return this.addHead(value);
if (!nextNode) return this.addTail(value);
const node = new ListNode(value);
node.previous = previousNode;
previousNode.next = node;
node.next = nextNode;
nextNode.previous = node;
this.size += 1;
this.size++;
return node;
}
private attachMany(
values: T[],
previousNode: ListNode<T> | undefined,
nextNode: ListNode<T> | undefined,
): ListNode<T>[] {
if (!values.length) return [];
if (!previousNode) return this.addManyHead(values);
if (!nextNode) return this.addManyTail(values);
const list = new LinkedList<T>();
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();
}
private detach(node: ListNode<T>) {
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;
}
add(value: T) {
return {
after: (previousValue: T, compareFn = compare) => {
return this.addAfter(value, previousValue, compareFn);
},
before: (nextValue: T, compareFn = compare) => {
return this.addBefore(value, nextValue, compareFn);
},
byIndex: (position: number): ListNode<T> => {
return this.addByIndex(value, position);
},
head: (): ListNode<T> => {
return this.addHead(value);
},
tail: (): ListNode<T> => {
return this.addTail(value);
},
after: (previousValue: T, compareFn: ListComparisonFn<T> = compare) =>
this.addAfter(value, previousValue, compareFn),
before: (nextValue: T, compareFn: ListComparisonFn<T> = compare) =>
this.addBefore(value, nextValue, compareFn),
byIndex: (position: number) => this.addByIndex(value, position),
head: () => this.addHead(value),
tail: () => this.addTail(value),
};
}
addMany(values: T[]) {
return {
after: (previousValue: T, compareFn: ListComparisonFn<T> = compare) =>
this.addManyAfter(values, previousValue, compareFn),
before: (nextValue: T, compareFn: ListComparisonFn<T> = compare) =>
this.addManyBefore(values, nextValue, compareFn),
byIndex: (position: number) => this.addManyByIndex(values, position),
head: () => this.addManyHead(values),
tail: () => this.addManyTail(values),
};
}
addAfter(value: T, previousValue: T, compareFn = compare): ListNode<T> {
addAfter(value: T, previousValue: T, compareFn: ListComparisonFn<T> = compare): ListNode<T> {
const previous = this.find(node => compareFn(node.value, previousValue));
return previous ? this.linkWith(value, previous, previous.next) : this.addTail(value);
return previous ? this.attach(value, previous, previous.next) : this.addTail(value);
}
addBefore(value: T, nextValue: T, compareFn = compare): ListNode<T> {
addBefore(value: T, nextValue: T, compareFn: ListComparisonFn<T> = compare): ListNode<T> {
const next = this.find(node => compareFn(node.value, nextValue));
return next ? this.linkWith(value, next.previous, next) : this.addHead(value);
return next ? this.attach(value, next.previous, next) : this.addHead(value);
}
addByIndex(value: T, position: number): ListNode<T> {
if (position < 0) position += this.size;
else if (position >= this.size) return this.addTail(value);
if (position <= 0) return this.addHead(value);
if (position >= this.size) return this.addTail(value);
const next = this.get(position)!;
return this.linkWith(value, next.previous, next);
return this.attach(value, next.previous, next);
}
addHead(value: T): ListNode<T> {
@ -97,7 +139,7 @@ export class LinkedList<T = any> {
else this.last = node;
this.first = node;
this.size += 1;
this.size++;
return node;
}
@ -114,51 +156,92 @@ export class LinkedList<T = any> {
this.last = node;
}
this.size += 1;
this.size++;
return node;
}
addManyAfter(
values: T[],
previousValue: T,
compareFn: ListComparisonFn<T> = compare,
): ListNode<T>[] {
const previous = this.find(node => compareFn(node.value, previousValue));
return previous ? this.attachMany(values, previous, previous.next) : this.addManyTail(values);
}
addManyBefore(
values: T[],
nextValue: T,
compareFn: ListComparisonFn<T> = compare,
): ListNode<T>[] {
const next = this.find(node => compareFn(node.value, nextValue));
return next ? this.attachMany(values, next.previous, next) : this.addManyHead(values);
}
addManyByIndex(values: T[], position: number): ListNode<T>[] {
if (position < 0) position += this.size;
if (position <= 0) return this.addManyHead(values);
if (position >= this.size) return this.addManyTail(values);
const next = this.get(position)!;
return this.attachMany(values, next.previous, next);
}
addManyHead(values: T[]): ListNode<T>[] {
return values.reduceRight<ListNode<T>[]>((nodes, value) => {
nodes.unshift(this.addHead(value));
return nodes;
}, []);
}
addManyTail(values: T[]): ListNode<T>[] {
return values.map(value => this.addTail(value));
}
drop() {
return {
byIndex: (position: number) => this.dropByIndex(position),
byValue: (value: T, compareFn = compare) => this.dropByValue(value, compareFn),
byValueAll: (value: T, compareFn = compare) => this.dropByValueAll(value, compareFn),
byValue: (value: T, compareFn: ListComparisonFn<T> = compare) =>
this.dropByValue(value, compareFn),
byValueAll: (value: T, compareFn: ListComparisonFn<T> = compare) =>
this.dropByValueAll(value, compareFn),
head: () => this.dropHead(),
tail: () => this.dropTail(),
};
}
dropMany(count: number) {
return {
byIndex: (position: number) => this.dropManyByIndex(count, position),
head: () => this.dropManyHead(count),
tail: () => this.dropManyTail(count),
};
}
dropByIndex(position: number): ListNode<T> | undefined {
if (position === 0) return this.dropHead();
else if (position === this.size - 1) return this.dropTail();
if (position < 0) position += this.size;
const current = this.get(position);
if (current) {
current.previous!.next = current.next;
current.next!.previous = current.previous;
this.size -= 1;
return current;
}
return undefined;
return current ? this.detach(current) : undefined;
}
dropByValue(value: T, compareFn = compare): ListNode<T> | undefined {
dropByValue(value: T, compareFn: ListComparisonFn<T> = compare): ListNode<T> | undefined {
const position = this.findIndex(node => compareFn(node.value, value));
if (position < 0) return undefined;
return this.dropByIndex(position);
return position < 0 ? undefined : this.dropByIndex(position);
}
dropByValueAll(value: T, compareFn = compare): ListNode<T>[] {
dropByValueAll(value: T, compareFn: ListComparisonFn<T> = compare): ListNode<T>[] {
const dropped: ListNode<T>[] = [];
for (let current = this.first, position = 0; current; position += 1, current = current.next) {
for (let current = this.first, position = 0; current; position++, current = current.next) {
if (compareFn(current.value, value)) {
dropped.push(this.dropByIndex(position - dropped.length)!);
}
@ -176,7 +259,7 @@ export class LinkedList<T = any> {
if (this.first) this.first.previous = undefined;
else this.last = undefined;
this.size -= 1;
this.size--;
return head;
}
@ -193,7 +276,7 @@ export class LinkedList<T = any> {
if (this.last) this.last.next = undefined;
else this.first = undefined;
this.size -= 1;
this.size--;
return tail;
}
@ -201,24 +284,66 @@ export class LinkedList<T = any> {
return undefined;
}
find(predicate: ListIteratorFunction<T>): ListNode<T> | undefined {
for (let current = this.first, position = 0; current; position += 1, current = current.next) {
dropManyByIndex(count: number, position: number): ListNode<T>[] {
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);
const dropped: ListNode<T>[] = [];
while (count--) {
const current = this.get(position);
dropped.push(this.detach(current!)!);
}
return dropped;
}
dropManyHead(count: Exclude<number, 0>): ListNode<T>[] {
if (count <= 0) return [];
count = Math.min(count, this.size);
const dropped: ListNode<T>[] = [];
while (count--) dropped.unshift(this.dropHead()!);
return dropped;
}
dropManyTail(count: Exclude<number, 0>): ListNode<T>[] {
if (count <= 0) return [];
count = Math.min(count, this.size);
const dropped: ListNode<T>[] = [];
while (count--) dropped.push(this.dropTail()!);
return dropped;
}
find(predicate: ListIteratorFn<T>): ListNode<T> | undefined {
for (let current = this.first, position = 0; current; position++, current = current.next) {
if (predicate(current, position, this)) return current;
}
return undefined;
}
findIndex(predicate: ListIteratorFunction<T>): number {
for (let current = this.first, position = 0; current; position += 1, current = current.next) {
findIndex(predicate: ListIteratorFn<T>): number {
for (let current = this.first, position = 0; current; position++, current = current.next) {
if (predicate(current, position, this)) return position;
}
return -1;
}
forEach<R = boolean>(callback: ListIteratorFunction<T, R>) {
for (let node = this.first, position = 0; node; position += 1, node = node.next) {
forEach<R = boolean>(callback: ListIteratorFn<T, R>) {
for (let node = this.first, position = 0; node; position++, node = node.next) {
callback(node, position, this);
}
}
@ -227,7 +352,7 @@ export class LinkedList<T = any> {
return this.find((_, index) => position === index);
}
indexOf(value: T, compareFn = compare): number {
indexOf(value: T, compareFn: ListComparisonFn<T> = compare): number {
return this.findIndex(node => compareFn(node.value, value));
}
@ -239,20 +364,32 @@ export class LinkedList<T = any> {
return array;
}
toString(): string {
toNodeArray(): ListNode<T>[] {
const array = new Array(this.size);
this.forEach((node, index) => (array[index!] = node));
return array;
}
toString(mapperFn: ListMapperFn<T> = JSON.stringify): string {
return this.toArray()
.map(value => JSON.stringify(value))
.map(value => mapperFn(value))
.join(' <-> ');
}
*[Symbol.iterator]() {
for (let node = this.first, position = 0; node; position += 1, node = node.next) {
for (let node = this.first, position = 0; node; position++, node = node.next) {
yield node.value;
}
}
}
export type ListIteratorFunction<T = any, R = boolean> = (
export type ListMapperFn<T = any> = (value: T) => any;
export type ListComparisonFn<T = any> = (value1: T, value2: T) => boolean;
export type ListIteratorFn<T = any, R = boolean> = (
node: ListNode<T>,
index?: number,
list?: LinkedList,

8
npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts

@ -8,6 +8,8 @@ import { FeatureManagement } from '../models';
providedIn: 'root',
})
export class FeatureManagementService {
apiName = 'FeatureManagement';
constructor(private rest: RestService, private store: Store) {}
getFeatures(params: FeatureManagement.Provider): Observable<FeatureManagement.Features> {
@ -16,7 +18,9 @@ export class FeatureManagementService {
url: '/api/abp/features',
params,
};
return this.rest.request<FeatureManagement.Provider, FeatureManagement.Features>(request);
return this.rest.request<FeatureManagement.Provider, FeatureManagement.Features>(request, {
apiName: this.apiName,
});
}
updateFeatures({
@ -30,6 +34,6 @@ export class FeatureManagementService {
body: { features },
params: { providerKey, providerName },
};
return this.rest.request<FeatureManagement.Features, null>(request);
return this.rest.request<FeatureManagement.Features, null>(request, { apiName: this.apiName });
}
}

34
npm/ng-packs/packages/identity/src/lib/services/identity.service.ts

@ -7,6 +7,8 @@ import { Identity } from '../models/identity';
providedIn: 'root',
})
export class IdentityService {
apiName = 'AbpIdentity';
constructor(private rest: RestService) {}
getRoles(params = {} as ABP.PageQueryParams): Observable<Identity.RoleResponse> {
@ -16,7 +18,7 @@ export class IdentityService {
params,
};
return this.rest.request<null, Identity.RoleResponse>(request);
return this.rest.request<null, Identity.RoleResponse>(request, { apiName: this.apiName });
}
getAllRoles(): Observable<Identity.RoleResponse> {
@ -25,7 +27,7 @@ export class IdentityService {
url: '/api/identity/roles/all',
};
return this.rest.request<null, Identity.RoleResponse>(request);
return this.rest.request<null, Identity.RoleResponse>(request, { apiName: this.apiName });
}
getRoleById(id: string): Observable<Identity.RoleItem> {
@ -34,7 +36,7 @@ export class IdentityService {
url: `/api/identity/roles/${id}`,
};
return this.rest.request<null, Identity.RoleItem>(request);
return this.rest.request<null, Identity.RoleItem>(request, { apiName: this.apiName });
}
deleteRole(id: string): Observable<Identity.RoleItem> {
@ -43,7 +45,7 @@ export class IdentityService {
url: `/api/identity/roles/${id}`,
};
return this.rest.request<null, Identity.RoleItem>(request);
return this.rest.request<null, Identity.RoleItem>(request, { apiName: this.apiName });
}
createRole(body: Identity.RoleSaveRequest): Observable<Identity.RoleItem> {
@ -53,7 +55,9 @@ export class IdentityService {
body,
};
return this.rest.request<Identity.RoleSaveRequest, Identity.RoleItem>(request);
return this.rest.request<Identity.RoleSaveRequest, Identity.RoleItem>(request, {
apiName: this.apiName,
});
}
updateRole(body: Identity.RoleItem): Observable<Identity.RoleItem> {
@ -66,7 +70,9 @@ export class IdentityService {
body,
};
return this.rest.request<Identity.RoleItem, Identity.RoleItem>(request);
return this.rest.request<Identity.RoleItem, Identity.RoleItem>(request, {
apiName: this.apiName,
});
}
getUsers(params = {} as ABP.PageQueryParams): Observable<Identity.UserResponse> {
@ -76,7 +82,7 @@ export class IdentityService {
params,
};
return this.rest.request<null, Identity.UserResponse>(request);
return this.rest.request<null, Identity.UserResponse>(request, { apiName: this.apiName });
}
getUserById(id: string): Observable<Identity.UserItem> {
@ -85,7 +91,7 @@ export class IdentityService {
url: `/api/identity/users/${id}`,
};
return this.rest.request<null, Identity.UserItem>(request);
return this.rest.request<null, Identity.UserItem>(request, { apiName: this.apiName });
}
getUserRoles(id: string): Observable<Identity.RoleResponse> {
@ -94,7 +100,7 @@ export class IdentityService {
url: `/api/identity/users/${id}/roles`,
};
return this.rest.request<null, Identity.RoleResponse>(request);
return this.rest.request<null, Identity.RoleResponse>(request, { apiName: this.apiName });
}
deleteUser(id: string): Observable<null> {
@ -103,7 +109,7 @@ export class IdentityService {
url: `/api/identity/users/${id}`,
};
return this.rest.request<null, null>(request);
return this.rest.request<null, null>(request, { apiName: this.apiName });
}
createUser(body: Identity.UserSaveRequest): Observable<Identity.UserItem> {
@ -113,7 +119,9 @@ export class IdentityService {
body,
};
return this.rest.request<Identity.UserSaveRequest, Identity.UserItem>(request);
return this.rest.request<Identity.UserSaveRequest, Identity.UserItem>(request, {
apiName: this.apiName,
});
}
updateUser(body: Identity.UserItem): Observable<Identity.UserItem> {
@ -126,6 +134,8 @@ export class IdentityService {
body,
};
return this.rest.request<Identity.UserItem, Identity.UserItem>(request);
return this.rest.request<Identity.UserItem, Identity.UserItem>(request, {
apiName: this.apiName,
});
}
}

15
npm/ng-packs/packages/permission-management/src/lib/services/permission-management.service.ts

@ -7,16 +7,23 @@ import { PermissionManagement } from '../models/permission-management';
providedIn: 'root',
})
export class PermissionManagementService {
apiName = 'AbpPermissionManagement';
constructor(private rest: RestService) {}
getPermissions(params: PermissionManagement.GrantedProvider): Observable<PermissionManagement.Response> {
getPermissions(
params: PermissionManagement.GrantedProvider,
): Observable<PermissionManagement.Response> {
const request: Rest.Request<PermissionManagement.GrantedProvider> = {
method: 'GET',
url: '/api/abp/permissions',
params,
};
return this.rest.request<PermissionManagement.GrantedProvider, PermissionManagement.Response>(request);
return this.rest.request<PermissionManagement.GrantedProvider, PermissionManagement.Response>(
request,
{ apiName: this.apiName },
);
}
updatePermissions({
@ -31,6 +38,8 @@ export class PermissionManagementService {
params: { providerKey, providerName },
};
return this.rest.request<PermissionManagement.UpdateRequest, null>(request);
return this.rest.request<PermissionManagement.UpdateRequest, null>(request, {
apiName: this.apiName,
});
}
}

28
npm/ng-packs/packages/tenant-management/src/lib/services/tenant-management.service.ts

@ -7,6 +7,8 @@ import { TenantManagement } from '../models/tenant-management';
providedIn: 'root',
})
export class TenantManagementService {
apiName = 'AbpTenantManagement';
constructor(private rest: RestService) {}
getTenant(params = {} as ABP.PageQueryParams): Observable<TenantManagement.Response> {
@ -16,7 +18,7 @@ export class TenantManagementService {
params,
};
return this.rest.request<null, TenantManagement.Response>(request);
return this.rest.request<null, TenantManagement.Response>(request, { apiName: this.apiName });
}
getTenantById(id: string): Observable<ABP.BasicItem> {
@ -25,7 +27,7 @@ export class TenantManagementService {
url: `/api/multi-tenancy/tenants/${id}`,
};
return this.rest.request<null, ABP.BasicItem>(request);
return this.rest.request<null, ABP.BasicItem>(request, { apiName: this.apiName });
}
deleteTenant(id: string): Observable<null> {
@ -34,7 +36,7 @@ export class TenantManagementService {
url: `/api/multi-tenancy/tenants/${id}`,
};
return this.rest.request<null, null>(request);
return this.rest.request<null, null>(request, { apiName: this.apiName });
}
createTenant(body: TenantManagement.AddRequest): Observable<ABP.BasicItem> {
@ -44,7 +46,9 @@ export class TenantManagementService {
body,
};
return this.rest.request<TenantManagement.AddRequest, ABP.BasicItem>(request);
return this.rest.request<TenantManagement.AddRequest, ABP.BasicItem>(request, {
apiName: this.apiName,
});
}
updateTenant(body: TenantManagement.UpdateRequest): Observable<ABP.BasicItem> {
@ -57,7 +61,9 @@ export class TenantManagementService {
body,
};
return this.rest.request<TenantManagement.UpdateRequest, ABP.BasicItem>(request);
return this.rest.request<TenantManagement.UpdateRequest, ABP.BasicItem>(request, {
apiName: this.apiName,
});
}
getDefaultConnectionString(id: string): Observable<string> {
@ -68,7 +74,9 @@ export class TenantManagementService {
responseType: Rest.ResponseType.Text,
url,
};
return this.rest.request<TenantManagement.DefaultConnectionStringRequest, string>(request);
return this.rest.request<TenantManagement.DefaultConnectionStringRequest, string>(request, {
apiName: this.apiName,
});
}
updateDefaultConnectionString(
@ -81,7 +89,9 @@ export class TenantManagementService {
url,
params: { defaultConnectionString: payload.defaultConnectionString },
};
return this.rest.request<TenantManagement.DefaultConnectionStringRequest, any>(request);
return this.rest.request<TenantManagement.DefaultConnectionStringRequest, any>(request, {
apiName: this.apiName,
});
}
deleteDefaultConnectionString(id: string): Observable<string> {
@ -91,6 +101,8 @@ export class TenantManagementService {
method: 'DELETE',
url,
};
return this.rest.request<TenantManagement.DefaultConnectionStringRequest, any>(request);
return this.rest.request<TenantManagement.DefaultConnectionStringRequest, any>(request, {
apiName: this.apiName,
});
}
}

23
npm/ng-packs/packages/theme-basic/src/lib/services/initial.service.ts

@ -1,12 +1,29 @@
import { LazyLoadService, AddReplaceableComponent } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { LazyLoadService } from '@abp/ng.core';
import { Store } from '@ngxs/store';
import styles from '../constants/styles';
import { ApplicationLayoutComponent } from '../components/application-layout/application-layout.component';
import { AccountLayoutComponent } from '../components/account-layout/account-layout.component';
import { EmptyLayoutComponent } from '../components/empty-layout/empty-layout.component';
@Injectable({ providedIn: 'root' })
export class InitialService {
constructor(private lazyLoadService: LazyLoadService) {
constructor(private lazyLoadService: LazyLoadService, private store: Store) {
this.appendStyle().subscribe();
this.store.dispatch([
new AddReplaceableComponent({
key: 'Theme.ApplicationLayoutComponent',
component: ApplicationLayoutComponent,
}),
new AddReplaceableComponent({
key: 'Theme.AccountLayoutComponent',
component: AccountLayoutComponent,
}),
new AddReplaceableComponent({
key: 'Theme.EmptyLayoutComponent',
component: EmptyLayoutComponent,
}),
]);
}
appendStyle() {

6
templates/app/angular/src/app/app.module.ts

@ -3,7 +3,6 @@ import { CoreModule } from '@abp/ng.core';
import { IdentityConfigModule } from '@abp/ng.identity.config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management.config';
import { TenantManagementConfigModule } from '@abp/ng.tenant-management.config';
import { LAYOUTS } from '@abp/ng.theme.basic';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@ -20,10 +19,7 @@ const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })];
@NgModule({
imports: [
CoreModule.forRoot({
environment,
requirements: {
layouts: LAYOUTS
}
environment
}),
ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot({ redirectUrl: '/' }),

5
templates/module/angular/.prettierrc

@ -0,0 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}

6
templates/module/angular/src/app/app.module.ts

@ -3,7 +3,6 @@ import { CoreModule } from '@abp/ng.core';
import { IdentityConfigModule } from '@abp/ng.identity.config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management.config';
import { TenantManagementConfigModule } from '@abp/ng.tenant-management.config';
import { LAYOUTS } from '@abp/ng.theme.basic';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@ -11,11 +10,11 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
import { NgxsModule } from '@ngxs/store';
import { OAuthModule } from 'angular-oauth2-oidc';
import { MyProjectNameConfigModule } from '../../projects/my-project-name-config/src/public-api';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SharedModule } from './shared/shared.module';
import { MyProjectNameConfigModule } from '../../projects/my-project-name-config/src/public-api';
const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })];
@ -25,9 +24,6 @@ const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })];
ThemeSharedModule.forRoot(),
CoreModule.forRoot({
environment,
requirements: {
layouts: LAYOUTS,
},
}),
OAuthModule.forRoot(),
NgxsModule.forRoot([]),

Loading…
Cancel
Save