Browse Source

Merge branch 'dev' into akin/moved-samples-to-abp-samples-repository

pull/3507/head
Akın Sabri Çam 7 years ago
parent
commit
2e9177869e
  1. 26
      docs/en/Authorization.md
  2. 4
      docs/en/Background-Jobs-Hangfire.md
  3. 30
      docs/en/UI/Angular/Content-Security-Strategy.md
  4. 95
      docs/en/UI/Angular/Content-Strategy.md
  5. 27
      docs/en/UI/Angular/Cross-Origin-Strategy.md
  6. 91
      docs/en/UI/Angular/Dom-Insertion-Service.md
  7. 53
      docs/en/UI/Angular/Dom-Strategy.md
  8. 14
      docs/en/UI/Angular/Lazy-Load-Service.md
  9. 55
      docs/en/UI/Angular/Loading-Strategy.md
  10. 264
      docs/en/UI/Common/Utils/Linked-List.md
  11. 4
      docs/en/docs-nav.json
  12. 26
      docs/zh-Hans/Authorization.md
  13. 43
      docs/zh-Hans/Background-Jobs-Hangfire.md
  14. 2
      docs/zh-Hans/CLI.md
  15. 3
      docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md
  16. 113
      docs/zh-Hans/How-To/Customize-Login-Page-MVC.md
  17. 3
      docs/zh-Hans/How-To/Customize-SignIn-Manager.md
  18. 9
      docs/zh-Hans/How-To/Index.md
  19. 4
      docs/zh-Hans/docs-nav.json
  20. BIN
      docs/zh-Hans/images/angular-folder-structure.png
  21. BIN
      docs/zh-Hans/images/angular-template-structure-diagram.png
  22. BIN
      docs/zh-Hans/images/react-native-folder-structure.png
  23. BIN
      docs/zh-Hans/images/react-native-navigation-structure.png
  24. BIN
      docs/zh-Hans/images/react-native-store-folder.png
  25. 14
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs
  26. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml
  27. 38
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs
  28. 4
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs
  29. 25
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs
  30. 4
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs
  31. 9
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs
  32. 23
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs
  33. 41
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs
  34. 34
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContextExtensions.cs
  35. 26
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs
  36. 10
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs
  37. 39
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs
  38. 56
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  39. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NugetPackageToLocalReferenceConverter.cs
  40. 36
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  41. 11
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs
  42. 26
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs
  43. 1
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  44. 22
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs
  45. 5
      modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs
  46. 11
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManager.cs
  47. 1
      modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs
  48. 11
      modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionChecker_User_Tests.cs
  49. 9
      modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs
  50. 1
      modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs

26
docs/en/Authorization.md

@ -142,6 +142,20 @@ myGroup.AddPermission(
);
```
#### Enable/Disable Permissions
A permission is enabled by default. It is possible to disable a permission. A disabled permission will be prohibited for everyone. You can still check for the permission, but it will always return prohibited.
Example definition:
````csharp
myGroup.AddPermission("Author_Management", isEnabled: false);
````
You normally don't need to define a disabled permission (unless you temporary want disable a feature of your application). However, you may want to disable a permission defined in a depended module. In this way you can disable the related application functionality. See the "*Changing Permission Definitions of a Depended Module*" section below for an example usage.
> Note: Checking an undefined permission will throw an exception while a disabled permission check simply returns prohibited (false).
#### Child Permissions
A permission may have child permissions. It is especially useful when you want to create a hierarchical permission tree where a permission may have additional sub permissions which are available only if the parent permission has been granted.
@ -208,6 +222,18 @@ See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/se
A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions.
Example:
````csharp
context
.GetPermissionOrNull(IdentityPermissions.Roles.Delete)
.IsEnabled = false;
````
When you write this code inside your permission definition provider, it finds the "role deletion" permission of the [Identity Module](Modules/Identity.md) and disabled the permission, so no one can delete a role on the application.
> Tip: It is better to check the value returned by the `GetPermissionOrNull` method since it may return null if the given permission was not defined.
## IAuthorizationService
ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization.

4
docs/en/Background-Jobs-Hangfire.md

@ -39,20 +39,20 @@ public class YourModule : AbpModule
````
## Configuration
You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package).
After you have installed these NuGet packages, you need to configure your project to use Hangfire.
1.First, we change the `Module` class (example: `<YourProjectName>HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
//... other configarations
//... other configarations.
ConfigureHangfire(context, configuration);
}

30
docs/en/UI/Angular/Content-Security-Strategy.md

@ -8,12 +8,20 @@
## API
### constructor(public nonce?: string)
### constructor
```js
constructor(public nonce?: string)
```
- `nonce` enables whitelisting inline script or styles in order to avoid using `unsafe-inline` in [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) and [style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles) directives.
### applyCSP(element: HTMLScriptElement | HTMLStyleElement): void
### applyCSP
```js
applyCSP(element: HTMLScriptElement | HTMLStyleElement): void
```
This method maps the aforementioned properties to the given `element`.
@ -39,18 +47,28 @@ This method maps the aforementioned properties to the given `element`.
Predefined content security strategies are accessible via `CONTENT_SECURITY_STRATEGY` constant.
### Loose(nonce: string)
### Loose
```js
CONTENT_SECURITY_STRATEGY.Loose(nonce: string)
```
`nonce` will be set.
### None()
### None
```js
CONTENT_SECURITY_STRATEGY.None()
```
Nothing will be done.
## What's Next?
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)
- [ContentStrategy](./Content-Strategy.md)
TODO: Place new ContentStrategy link here.

95
docs/en/UI/Angular/Content-Strategy.md

@ -0,0 +1,95 @@
# ContentStrategy
`ContentStrategy` is an abstract class exposed by @abp/ng.core package. It helps you create inline scripts or styles.
## API
### constructor
```js
constructor(
public content: string,
protected domStrategy?: DomStrategy,
protected contentSecurityStrategy?: ContentSecurityStrategy
)
```
- `content` is set to `<script>` and `<style>` elements as `textContent` property.
- `domStrategy` is the `DomStrategy` that will be used when inserting the created element. (_default: AppendToHead_)
- `contentSecurityStrategy` is the `ContentSecurityStrategy` that will be used on the created element before inserting it. (_default: None_)
Please refer to [DomStrategy](./Dom-Strategy.md) and [ContentSecurityStrategy](./Content-Security-Strategy.md) documentation for their usage.
### createElement
```js
createElement(): HTMLScriptElement | HTMLStyleElement
```
This method creates and returns a `<script>` or `<style>` element with `content` set as `textContent`.
### insertElement
```js
insertElement(): void
```
This method creates and inserts a `<script>` or `<style>` element.
## ScriptContentStrategy
`ScriptContentStrategy` is a class that extends `ContentStrategy`. It lets you **insert a `<script>` element to the DOM**.
## StyleContentStrategy
`StyleContentStrategy` is a class that extends `ContentStrategy`. It lets you **insert a `<style>` element to the DOM**.
## Predefined Content Strategies
Predefined content strategies are accessible via `CONTENT_STRATEGY` constant.
### AppendScriptToBody
```js
CONTENT_STRATEGY.AppendScriptToBody(content: string)
```
Creates a `<script>` element with the given content and places it at the **end** of `<body>` tag in the document.
### AppendScriptToHead
```js
CONTENT_STRATEGY.AppendScriptToHead(content: string)
```
Creates a `<script>` element with the given content and places it at the **end** of `<head>` tag in the document.
### AppendStyleToHead
```js
CONTENT_STRATEGY.AppendStyleToHead(content: string)
```
Creates a `<style>` element with the given content and places it at the **end** of `<head>` tag in the document.
### PrependStyleToHead
```js
CONTENT_STRATEGY.PrependStyleToHead(content: string)
```
Creates a `<style>` element with the given content and places it at the **beginning** of `<head>` tag in the document.
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)

27
docs/en/UI/Angular/Cross-Origin-Strategy.md

@ -6,13 +6,24 @@
## API
### constructor(public crossorigin: 'anonymous' | 'use-credentials', public integrity?: string)
### constructor
```js
constructor(
public crossorigin: 'anonymous' | 'use-credentials',
public integrity?: string
)
```
- `crossorigin` is mapped to [the HTML attribute with the same name](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin).
- `integrity` is a hash for validating a remote resource. Its use is explained [here](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
### setCrossOrigin(element: HTMLElement): void
### setCrossOrigin
```js
setCrossOrigin(element: HTMLElement): void
```
This method maps the aforementioned properties to the given `element`.
@ -24,12 +35,20 @@ This method maps the aforementioned properties to the given `element`.
Predefined cross-origin strategies are accessible via `CROSS_ORIGIN_STRATEGY` constant.
### Anonymous(integrity?: string)
### Anonymous
```js
CROSS_ORIGIN_STRATEGY.Anonymous(integrity?: string)
```
`crossorigin` will be set as `"anonymous"` and `integrity` is optional.
### UseCredentials(integrity?: string)
### UseCredentials
```js
CROSS_ORIGIN_STRATEGY.UseCredentials(integrity?: string)
```
`crossorigin` will be set as `"use-credentials"` and `integrity` is optional.

91
docs/en/UI/Angular/Dom-Insertion-Service.md

@ -0,0 +1,91 @@
# How to Insert Scripts and Styles
You can use the `DomInsertionService` in @abp/ng.core package in order to insert scripts and styles in an easy and explicit way.
## Getting Started
You do not have to provide the `DomInsertionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { DomInsertionService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
}
```
## Usage
You can use the `insertContent` method of `DomInsertionService` to create a `<script>` or `<style>` element with given content in the DOM at the desired position.
### How to Insert Scripts
The first parameter of `insertContent` method expects a `ContentStrategy`. If you pass a `ScriptContentStrategy` instance, the `DomInsertionService` will create a `<script>` element with given `content` and place it in the designated DOM position.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendScriptToBody('alert()')
);
}
}
```
In the example above, `<script>alert()</script>` element will place at the **end** of `<body>`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
### How to Insert Styles
If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `<style>` element with given `content` and place it in the designated DOM position.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
}
```
In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
## API
### insertContent
```js
insertContent(strategy: ContentStrategy): void
```
`strategy` parameter is the primary focus here and is explained above.
## What's Next?
- [TrackByService](./Track-By-Service.md)

53
docs/en/UI/Angular/Dom-Strategy.md

@ -6,16 +6,26 @@
## API
### constructor(public target?: HTMLElement, public position?: InsertPosition)
### constructor
```js
constructor(
public target?: HTMLElement,
public position?: InsertPosition
)
```
- `target` is an HTMLElement (_default: document.head_).
- `position` defines where the created element will be placed. All possible values of `position` can be found [here](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement) (_default: 'beforeend'_).
### insertElement(element: HTMLElement): void
### insertElement
This method inserts given `element` to `target` based on the `position`.
```js
insertElement(element: HTMLElement): void
```
This method inserts given `element` to `target` based on the `position`.
@ -24,35 +34,56 @@ This method inserts given `element` to `target` based on the `position`.
Predefined dom strategies are accessible via `DOM_STRATEGY` constant.
### AppendToBody()
### AppendToBody
```js
DOM_STRATEGY.AppendToBody()
```
`insertElement` will place the given `element` at the end of `<body>`.
### AppendToHead()
### AppendToHead
```js
DOM_STRATEGY.AppendToHead()
```
`insertElement` will place the given `element` at the end of `<head>`.
### PrependToHead()
### PrependToHead
```js
DOM_STRATEGY.PrependToHead()
```
`insertElement` will place the given `element` at the beginning of `<head>`.
### AfterElement(target: HTMLElement)
### AfterElement
```js
DOM_STRATEGY.AfterElement(target: HTMLElement)
```
`insertElement` will place the given `element` after (as a sibling to) the `target`.
### BeforeElement(target: HTMLElement)
### BeforeElement
```js
DOM_STRATEGY.BeforeElement(target: HTMLElement)
```
`insertElement` will place the given `element` before (as a sibling to) the `target`.
## What's Next?
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)
- [LazyLoadService](./Lazy-Load-Service.md)
- [LoadingStrategy](./Loading-Strategy.md)
TODO: Place new InsertionStrategy link here.
- [ContentStrategy](./Content-Strategy.md)

14
docs/en/UI/Angular/Lazy-Load-Service.md

@ -185,13 +185,21 @@ In this example, the second file needs the first one to be loaded beforehand. Rx
### loaded: Set<string>
### loaded
```js
loaded: Set<string>
```
All previously loaded paths are available via this property. It is a simple [JavaScript Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
### load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event>
### load
```js
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event>
```
- `strategy` parameter is the primary focus here and is explained above.
- `retryTimes` defines how many times the loading will be tried again before fail (_default: 2_).
@ -202,4 +210,4 @@ All previously loaded paths are available via this property. It is a simple [Jav
## What's Next?
- [TrackByService](./Track-By-Service.md)
- [DomInsertionService](./Dom-Insertion-Service.md)

55
docs/en/UI/Angular/Loading-Strategy.md

@ -7,8 +7,15 @@
## API
### constructor
### constructor(public path: string, protected domStrategy?: DomStrategy, protected crossOriginStrategy?: CrossOriginStrategy)
```js
constructor(
public path: string,
protected domStrategy?: DomStrategy,
protected crossOriginStrategy?: CrossOriginStrategy
)
```
- `path` is set to `<script>` elements as `src` and `<link>` elements as `href` attribute.
- `domStrategy` is the `DomStrategy` that will be used when inserting the created element. (_default: AppendToHead_)
@ -17,12 +24,20 @@
Please refer to [DomStrategy](./Dom-Strategy.md) and [CrossOriginStrategy](./Cross-Origin-Strategy.md) documentation for their usage.
### createElement(): HTMLScriptElement | HTMLLinkElement
### createElement
```js
createElement(): HTMLScriptElement | HTMLLinkElement
```
This method creates and returns a `<script>` or `<link>` element with `path` set as `src` or `href`.
### createStream(): Observable<Event>
### createStream
```js
createStream(): Observable<Event>
```
This method creates and returns an observable stream that emits on success and throws on error.
@ -45,33 +60,51 @@ This method creates and returns an observable stream that emits on success and t
Predefined content security strategies are accessible via `LOADING_STRATEGY` constant.
### AppendAnonymousScriptToHead(src: string, integrity?: string)
### AppendAnonymousScriptToHead
```js
LOADING_STRATEGY.AppendAnonymousScriptToHead(src: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<script>` element and places it at the **end** of `<head>` tag in the document.
### PrependAnonymousScriptToHead(src: string, integrity?: string)
### PrependAnonymousScriptToHead
```js
LOADING_STRATEGY.PrependAnonymousScriptToHead(src: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<script>` element and places it at the **beginning** of `<head>` tag in the document.
### AppendAnonymousScriptToBody(src: string, integrity?: string)
### AppendAnonymousScriptToBody
```js
LOADING_STRATEGY.AppendAnonymousScriptToBody(src: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<script>` element and places it at the **end** of `<body>` tag in the document.
### AppendAnonymousStyleToHead(href: string, integrity?: string)
### AppendAnonymousStyleToHead
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<style>` element and places it at the **end** of `<head>` tag in the document.
```js
LOADING_STRATEGY.AppendAnonymousStyleToHead(href: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<style>` element and places it at the **end** of `<head>` tag in the document.
### PrependAnonymousStyleToHead(href: string, integrity?: string)
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<style>` element and places it at the **beginning** of `<head>` tag in the document.
### PrependAnonymousStyleToHead
```js
LOADING_STRATEGY.PrependAnonymousStyleToHead(href: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<style>` element and places it at the **beginning** of `<head>` tag in the document.
## What's Next?
## See Also
- [LazyLoadService](./Lazy-Load-Service.md)

264
docs/en/UI/Common/Utils/Linked-List.md

@ -30,7 +30,11 @@ There are several methods to create new nodes in a linked list and all of them a
#### addHead(value: T): ListNode\<T\>
#### addHead(value)
```js
addHead(value: T): ListNode\<T\>
```
Adds a node with given value as the first node in list:
@ -50,7 +54,11 @@ list.addHead('c');
#### addManyHead(values: T\[\]): ListNode\<T\>\[\]
#### addManyHead(values)
```js
addManyHead(values: T\[\]): ListNode\<T\>\[\]
```
Adds multiple nodes with given values as the first nodes in list:
@ -66,7 +74,11 @@ list.addManyHead(['x', 'y', 'z']);
#### addTail(value: T): ListNode\<T\>
#### addTail(value)
```js
addTail(value: T): ListNode\<T\>
```
Adds a node with given value as the last node in list:
@ -86,7 +98,11 @@ list.addTail('c');
#### addManyTail(values: T\[\]): ListNode\<T\>\[\]
#### addManyTail(values)
```js
addManyTail(values: T\[\]): ListNode\<T\>\[\]
```
Adds multiple nodes with given values as the last nodes in list:
@ -102,7 +118,11 @@ list.addManyTail(['x', 'y', 'z']);
#### addAfter(value: T, previousValue: T, compareFn = compare): ListNode\<T\>
#### addAfter(value, previousValue, compareFn)
```js
addAfter(value: T, previousValue: T, compareFn = compare): ListNode\<T\>
```
Adds a node with given value after the first node that has the previous value:
@ -145,7 +165,11 @@ list.addAfter(
#### addManyAfter(values: T\[\], previousValue: T, compareFn = compare): ListNode\<T\>\[\]
#### addManyAfter(values, previousValue, compareFn)
```js
addManyAfter(values: T\[\], previousValue: T, compareFn = compare): ListNode\<T\>\[\]
```
Adds multiple nodes with given values after the first node that has the previous value:
@ -183,7 +207,11 @@ list.addManyAfter(
#### addBefore(value: T, nextValue: T, compareFn = compare): ListNode\<T\>
#### addBefore(value, nextValue, compareFn)
```js
addBefore(value: T, nextValue: T, compareFn = compare): ListNode\<T\>
```
Adds a node with given value before the first node that has the next value:
@ -226,7 +254,11 @@ list.addBefore(
#### addManyBefore(values: T\[\], nextValue: T, compareFn = compare): ListNode\<T\>\[\]
#### addManyBefore(values, nextValue, compareFn)
```js
addManyBefore(values: T\[\], nextValue: T, compareFn = compare): ListNode\<T\>\[\]
```
Adds multiple nodes with given values before the first node that has the next value:
@ -264,7 +296,11 @@ list.addManyBefore(
#### addByIndex(value: T, position: number): ListNode\<T\>
#### addByIndex(value, position)
```js
addByIndex(value: T, position: number): ListNode\<T\>
```
Adds a node with given value at the specified position in the list:
@ -298,7 +334,11 @@ list.addByIndex('x', -1);
#### addManyByIndex(values: T\[\], position: number): ListNode\<T\>\[\]
#### addManyByIndex(values, position)
```js
addManyByIndex(values: T\[\], position: number): ListNode\<T\>\[\]
```
Adds multiple nodes with given values at the specified position in the list:
@ -328,7 +368,11 @@ list.addManyByIndex(['x', 'y'], -1);
#### add(value: T).head(): ListNode\<T\>
#### add(value).head()
```js
add(value: T).head(): ListNode\<T\>
```
Adds a node with given value as the first node in list:
@ -352,7 +396,11 @@ list.add('c').head();
#### add(value: T).tail(): ListNode\<T\>
#### add(value).tail()
```js
add(value: T).tail(): ListNode\<T\>
```
Adds a node with given value as the last node in list:
@ -376,7 +424,11 @@ list.add('c').tail();
#### add(value: T).after(previousValue: T, compareFn = compare): ListNode\<T\>
#### add(value).after(previousValue, compareFn)
```js
add(value: T).after(previousValue: T, compareFn = compare): ListNode\<T\>
```
Adds a node with given value after the first node that has the previous value:
@ -419,7 +471,11 @@ list
#### add(value: T).before(nextValue: T, compareFn = compare): ListNode\<T\>
#### add(value).before(nextValue, compareFn)
```js
add(value: T).before(nextValue: T, compareFn = compare): ListNode\<T\>
```
Adds a node with given value before the first node that has the next value:
@ -462,7 +518,11 @@ list
#### add(value: T).byIndex(position: number): ListNode\<T\>
#### add(value).byIndex(position)
```js
add(value: T).byIndex(position: number): ListNode\<T\>
```
Adds a node with given value at the specified position in the list:
@ -500,7 +560,11 @@ list.add('x').byIndex(-1);
#### addMany(values: T\[\]).head(): ListNode\<T\>\[\]
#### addMany(values).head()
```js
addMany(values: T\[\]).head(): ListNode\<T\>\[\]
```
Adds multiple nodes with given values as the first nodes in list:
@ -520,7 +584,11 @@ list.addMany(['x', 'y', 'z']).head();
#### addMany(values: T\[\]).tail(): ListNode\<T\>\[\]
#### addMany(values).tail()
```js
addMany(values: T\[\]).tail(): ListNode\<T\>\[\]
```
Adds multiple nodes with given values as the last nodes in list:
@ -540,7 +608,11 @@ list.addMany(['x', 'y', 'z']).tail();
#### addMany(values: T\[\]).after(previousValue: T, compareFn = compare): ListNode\<T\>\[\]
#### addMany(values).after(previousValue, compareFn)
```js
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:
@ -578,7 +650,11 @@ list
#### addMany(values: T\[\]).before(nextValue: T, compareFn = compare): ListNode\<T\>\[\]
#### addMany(values).before(nextValue, compareFn)
```js
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:
@ -616,7 +692,11 @@ list
#### addMany(values: T\[\]).byIndex(position: number): ListNode\<T\>\[\]
#### addMany(values).byIndex(position)
```js
addMany(values: T\[\]).byIndex(position: number): ListNode\<T\>\[\]
```
Adds multiple nodes with given values at the specified position in the list:
@ -656,7 +736,11 @@ There are a few methods to remove nodes from a linked list and all of them are s
#### dropHead(): ListNode\<T\> | undefined
#### dropHead()
```js
dropHead(): ListNode\<T\> | undefined
```
Removes the first node from the list:
@ -672,7 +756,11 @@ list.dropHead();
#### dropManyHead(count: number): ListNode\<T\>\[\]
#### dropManyHead(count)
```js
dropManyHead(count: number): ListNode\<T\>\[\]
```
Removes the first nodes from the list based on given count:
@ -688,7 +776,11 @@ list.dropManyHead(2);
#### dropTail(): ListNode\<T\> | undefined
#### dropTail()
```js
dropTail(): ListNode\<T\> | undefined
```
Removes the last node from the list:
@ -704,7 +796,11 @@ list.dropTail();
#### dropManyTail(count: number): ListNode\<T\>\[\]
#### dropManyTail(count)
```js
dropManyTail(count: number): ListNode\<T\>\[\]
```
Removes the last nodes from the list based on given count:
@ -720,7 +816,11 @@ list.dropManyTail(2);
#### dropByIndex(position: number): ListNode\<T\> | undefined
#### dropByIndex(position)
```js
dropByIndex(position: number): ListNode\<T\> | undefined
```
Removes the node with the specified position from the list:
@ -750,7 +850,11 @@ list.dropByIndex(-2);
#### dropManyByIndex(count: number, position: number): ListNode\<T\>\[\]
#### dropManyByIndex(count, position)
```js
dropManyByIndex(count: number, position: number): ListNode\<T\>\[\]
```
Removes the nodes starting from the specified position from the list based on given count:
@ -780,7 +884,11 @@ list.dropManyByIndex(2, -2);
#### dropByValue(value: T, compareFn = compare): ListNode\<T\> | undefined
#### dropByValue(value, compareFn)
```js
dropByValue(value: T, compareFn = compare): ListNode\<T\> | undefined
```
Removes the first node with given value from the list:
@ -814,7 +922,11 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### dropByValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
#### dropByValueAll(value, compareFn)
```js
dropByValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
```
Removes all nodes with given value from the list:
@ -848,7 +960,11 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### drop().head(): ListNode\<T\> | undefined
#### drop().head()
```js
drop().head(): ListNode\<T\> | undefined
```
Removes the first node in list:
@ -868,7 +984,11 @@ list.drop().head();
#### drop().tail(): ListNode\<T\> | undefined
#### drop().tail()
```js
drop().tail(): ListNode\<T\> | undefined
```
Removes the last node in list:
@ -888,7 +1008,11 @@ list.drop().tail();
#### drop().byIndex(position: number): ListNode\<T\> | undefined
#### drop().byIndex(position)
```js
drop().byIndex(position: number): ListNode\<T\> | undefined
```
Removes the node with the specified position from the list:
@ -922,7 +1046,11 @@ list.drop().byIndex(-2);
#### drop().byValue(value: T, compareFn = compare): ListNode\<T\> | undefined
#### drop().byValue(value, compareFn)
```js
drop().byValue(value: T, compareFn = compare): ListNode\<T\> | undefined
```
Removes the first node with given value from the list:
@ -960,7 +1088,11 @@ list
#### drop().byValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
#### drop().byValueAll(value, compareFn)
```js
drop().byValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
```
Removes all nodes with given value from the list:
@ -998,7 +1130,11 @@ list
#### dropMany(count: number).head(): ListNode\<T\>\[\]
#### dropMany(count).head()
```js
dropMany(count: number).head(): ListNode\<T\>\[\]
```
Removes the first nodes from the list based on given count:
@ -1018,7 +1154,11 @@ list.dropMany(2).head();
#### dropMany(count: number).tail(): ListNode\<T\>\[\]
#### dropMany(count).tail()
```js
dropMany(count: number).tail(): ListNode\<T\>\[\]
```
Removes the last nodes from the list based on given count:
@ -1038,7 +1178,11 @@ list.dropMany(2).tail();
#### dropMany(count: number).byIndex(position: number): ListNode\<T\>\[\]
#### dropMany(count).byIndex(position)
```js
dropMany(count: number).byIndex(position: number): ListNode\<T\>\[\]
```
Removes the nodes starting from the specified position from the list based on given count:
@ -1078,7 +1222,11 @@ There are a few methods to find specific nodes in a linked list.
#### find(predicate: ListIteratorFunction\<T\>): ListNode\<T\> | undefined
#### find(predicate)
```js
find(predicate: ListIteratorFunction\<T\>): ListNode\<T\> | undefined
```
Finds the first node from the list that matches the given predicate:
@ -1098,7 +1246,11 @@ found.next.value === "b"
#### findIndex(predicate: ListIteratorFunction\<T\>): number
#### findIndex(predicate)
```js
findIndex(predicate: ListIteratorFunction\<T\>): number
```
Finds the position of the first node from the list that matches the given predicate:
@ -1122,7 +1274,11 @@ i3 === -1
#### get(position: number): ListNode\<T\> | undefined
#### get(position)
```js
get(position: number): ListNode\<T\> | undefined
```
Finds and returns the node with specific position in the list:
@ -1142,7 +1298,11 @@ found.next.value === "c"
#### indexOf(value: T, compareFn = compare): number
#### indexOf(value, compareFn)
```js
indexOf(value: T, compareFn = compare): number
```
Finds the position of the first node from the list that has the given value:
@ -1200,7 +1360,11 @@ There are a few ways to iterate over or display a linked list.
#### forEach(callback: ListIteratorFunction\<T\>): void
#### forEach(callback)
```js
forEach(callback: ListIteratorFunction\<T\>): void
```
Runs a callback function on all nodes in a linked list from head to tail:
@ -1238,7 +1402,11 @@ for(const node of list) {
#### toArray(): T\[\]
#### toArray()
```js
toArray(): T\[\]
```
Converts a linked list to an array of values:
@ -1256,7 +1424,11 @@ arr === ['a', 'b', 'c']
#### toNodeArray(): T\[\]
#### toNodeArray()
```js
toNodeArray(): T\[\]
```
Converts a linked list to an array of nodes:
@ -1276,7 +1448,11 @@ arr[2].value === 'a'
#### toString(): string
#### toString()
```js
toString(): string
```
Converts a linked list to a string representation of nodes and their relations:

4
docs/en/docs-nav.json

@ -349,6 +349,10 @@
"text": "Lazy Loading Scripts & Styles",
"path": "UI/Angular/Lazy-Load-Service.md"
},
{
"text": "DomInsertionService",
"path": "UI/Angular/Dom-Insertion-Service.md"
},
{
"text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md"

26
docs/zh-Hans/Authorization.md

@ -143,6 +143,20 @@ myGroup.AddPermission(
);
````
#### 启用/禁用权限
权限默认为启用. 它也可以被禁用,禁用权限所有的用户将无法使用它. 你仍然可以检查这个权限,但它总是会返回被禁止.
定义示例:
````csharp
myGroup.AddPermission("Author_Management", isEnabled: false);
````
通常你不需要定义禁用权限(除非您暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法.
> 注意:检查一个未定义的权限会抛出异常,而被禁用的权限的返回禁止(false).
#### 子权限
权限可以具有子权限,当你想要创建一个层次结构的权限树时它特别有用. 在这个树中一个权限可能含有子权限,并且子权限只有在授权父权限时才可用.
@ -209,6 +223,18 @@ public class AuthorAppService : ApplicationService, IAuthorAppService
从 `PermissionDefinitionProvider` 派生的类(就像上面的示例一样) 可以获取现有的权限定义(由依赖[模块](Module-Development-Basics.md)定义)并更改其定义.
示例:
````csharp
context
.GetPermissionOrNull(IdentityPermissions.Roles.Delete)
.IsEnabled = false;
````
当你在权限提供程序编写了这行代码,它会找到[身份模块](Modules/Identity.md)的 "role deletion" 权限并且禁用它,因此没有人可以在应用程序中删除角色.
> 提供: 更好的方式应该检查 `GetPermissionOrNull` 返回值,如果权限未定义,它会返回null值.
## IAuthorizationService
ASP.NET Core 提供了 `IAuthorizationService` 用于检查权限. 注入后使用它进行条件控制权限.

43
docs/zh-Hans/Background-Jobs-Hangfire.md

@ -40,4 +40,45 @@ public class YourModule : AbpModule
## 配置
TODO...
你可以安装任何Hangfire存储. 最常用的是SQL Server(参阅[Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer)NuGet包).
当你安装NuGet包后,你需要为你的项目配置Hangfire.
1.首先, 我们需要更改 `Module` 类 (例如: `<YourProjectName>HttpApiHostModule`) 的 `ConfigureServices` 方法去配置Hangfire存储和连接字符串:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
//... other configarations.
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
}
````
1. 我们需要在 `Module` 类的 `OnApplicationInitialization` 方法添加 `UseHangfireServer` 方法调用.
如果你想要使用Hangfire的面板,你可以使用: `UseHangfireDashboard`
````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
// ... others
app.UseHangfireServer();
app.UseHangfireDashboard();
}
````

2
docs/zh-Hans/CLI.md

@ -56,10 +56,12 @@ abp new Acme.BookStore
* `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
*
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,您会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串!
### add-package

3
docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md

@ -0,0 +1,3 @@
# 如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证
TODO...

113
docs/zh-Hans/How-To/Customize-Login-Page-MVC.md

@ -0,0 +1,113 @@
# 如何为MVC / Razor页面应用程序自定义登录页面
当你使用[应用程序启动模板](../Startup-Templates/Application.md)创建了一个新的应用程序, 登录页面的源代码并不在你的解决方案中,所以你不能直接更改. 它来自[账户模块](../Modules/Account.md),使用[NuGet包](https://www.nuget.org/packages/Volo.Abp.Account.Web)引用.
本文介绍了如何为自己的应用程序自定义登录页面.
## 创建登录 PageModel
创建一个新的类继承账户模块的[LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs).
````csharp
public class CustomLoginModel : LoginModel
{
public CustomLoginModel(
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider,
Microsoft.Extensions.Options.IOptions<Volo.Abp.Account.Web.AbpAccountOptions> accountOptions)
: base(schemeProvider, accountOptions)
{
}
}
````
> 在这里命令约定很重要. 如果你的类名不是以 `LoginModel` 结束,你需要手动在[依赖注入](../Dependency-Injection.md)系统替换 `LoginModel`.
然后你可以覆盖任何方法并添加用户界面需要的新方法和属性.
## 重写登录页面UI
在 **Pages** 目录下创建名为 **Account** 的文件夹,并在这个文件夹中创建 `Login.cshtml` ,借助[虚拟文件系统](../Virtual-File-System.md)它会自动覆盖账户模块的页面文件.
自定义页面一个很好的开始是复制它的源代码. [点击这里](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml)找到登录页面的源码. 在编写本文档时,源代码如下:
````xml
@page
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<form method="post" class="mt-4">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
<div class="form-group">
<label asp-for="LoginInput.UserNameOrEmailAddress"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginInput.Password"></label>
<input asp-for="LoginInput.Password" class="form-control" />
<span asp-validation-for="LoginInput.Password" class="text-danger"></span>
</div>
<div class="form-check">
<label asp-for="LoginInput.RememberMe" class="form-check-label">
<input asp-for="LoginInput.RememberMe" class="form-check-input" />
@Html.DisplayNameFor(m => m.LoginInput.RememberMe)
</label>
</div>
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-block btn-lg mt-3">@L["Login"]</abp-button>
</form>
</div>
<div class="card-footer text-center border-0">
<abp-button type="button" button-type="Link" name="Action" value="Cancel" class="px-2 py-0">@L["Cancel"]</abp-button> @* TODO: Only show if identity server is used *@
</div>
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="col-md-6">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
````
只需更改 `@model` 为 `Acme.BookStore.Web.Pages.Account.CustomLoginModel` 使用自定义的 `PageModel` 类. 你可以做任何应用程序需要的更改.
## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码.
## 另请参阅
* [ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南](../UI/AspNetCore/Customization-User-Interface.md).

3
docs/zh-Hans/How-To/Customize-SignIn-Manager.md

@ -0,0 +1,3 @@
# 如何为ABP应用程序定制SignIn Manager
TODO...

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

@ -0,0 +1,9 @@
# "如何" 指南
本部分包含一些常见问题的 "如何" 指南. 尽管其中是一些常见的开发任务和ABP并不直接相关,但我们认为有一些具体的示例可以直接与基于ABP的应用程序一起使用.
## Authentication
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md)
* [如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证](Azure-Active-Directory-Authentication-MVC.md)
* [如何为ABP应用程序定制SignIn Manager](Customize-SignIn-Manager.md)

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

@ -64,6 +64,10 @@
}
]
},
{
"text": "\"如何\" 指南",
"path": "How-To/Index.md"
},
{
"text": "从ASP.NET Boilerplate迁移",
"path": "AspNet-Boilerplate-Migration-Guide.md"

BIN
docs/zh-Hans/images/angular-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/zh-Hans/images/angular-template-structure-diagram.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
docs/zh-Hans/images/react-native-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/zh-Hans/images/react-native-navigation-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/zh-Hans/images/react-native-store-folder.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

14
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs

@ -14,9 +14,9 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
private readonly ITenantResolveResultAccessor _tenantResolveResultAccessor;
public MultiTenancyMiddleware(
ITenantResolver tenantResolver,
ITenantStore tenantStore,
ICurrentTenant currentTenant,
ITenantResolver tenantResolver,
ITenantStore tenantStore,
ICurrentTenant currentTenant,
ITenantResolveResultAccessor tenantResolveResultAccessor)
{
_tenantResolver = tenantResolver;
@ -34,11 +34,13 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
if (resolveResult.TenantIdOrName != null)
{
tenant = await FindTenantAsync(resolveResult.TenantIdOrName);
if (tenant == null)
{
//TODO: A better exception?
throw new AbpException(
"There is no tenant with given tenant id or name: " + resolveResult.TenantIdOrName
throw new BusinessException(
code: "Volo.AbpIo.MultiTenancy:010001",
message: "Tenant not found!",
details: "There is no tenant with the tenant id or name: " + resolveResult.TenantIdOrName
);
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml

@ -39,7 +39,10 @@
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>@pageTitle</title>
@if (ViewBag.Description!=null)
{
<meta name="description" content="@ViewBag.Description" />
}
<abp-style-bundle name="@BasicThemeBundles.Styles.Global" />
@await Component.InvokeAsync(typeof(WidgetStylesViewComponent))

38
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs

@ -86,6 +86,44 @@ namespace Volo.Abp.Auditing
return defaultValue;
}
public virtual bool IsEntityHistoryEnabled(Type entityType, bool defaultValue = false)
{
if (!entityType.IsPublic)
{
return false;
}
if (Options.IgnoredTypes.Any(t => t.IsAssignableFrom(entityType)))
{
return false;
}
if (entityType.IsDefined(typeof(AuditedAttribute), true))
{
return true;
}
foreach (var propertyInfo in entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if(propertyInfo.IsDefined(typeof(AuditedAttribute)))
{
return true;
}
}
if (entityType.IsDefined(typeof(DisableAuditingAttribute), true))
{
return false;
}
if (Options.EntityHistorySelectors.Any(selector => selector.Predicate(entityType)))
{
return true;
}
return defaultValue;
}
public virtual AuditLogInfo CreateAuditLogInfo()
{
var auditInfo = new AuditLogInfo

4
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs

@ -4,11 +4,13 @@ using System.Reflection;
namespace Volo.Abp.Auditing
{
//TODO: Move ShouldSaveAudit and rename to IAuditingFactory
//TODO: Move ShouldSaveAudit & IsEntityHistoryEnabled and rename to IAuditingFactory
public interface IAuditingHelper
{
bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false);
bool IsEntityHistoryEnabled(Type entityType, bool defaultValue = false);
AuditLogInfo CreateAuditLogInfo();
AuditLogActionInfo CreateAuditLogAction(

25
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs

@ -1,4 +1,5 @@
using JetBrains.Annotations;
using System;
using JetBrains.Annotations;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
@ -7,13 +8,35 @@ namespace Volo.Abp.Authorization.Permissions
public interface IPermissionDefinitionContext
{
//TODO: Add Get methods to find and modify a permission or group.
IServiceProvider ServiceProvider { get; }
/// <summary>
/// Gets a pre-defined permission group.
/// Throws <see cref="AbpException"/> if can not find the given group.
/// </summary>
/// <param name="name">Name of the group</param>
/// <returns></returns>
PermissionGroupDefinition GetGroup([NotNull] string name);
/// <summary>
/// Tries to get a pre-defined permission group.
/// Returns null if can not find the given group.
/// </summary>
/// <param name="name">Name of the group</param>
/// <returns></returns>
[NotNull]
PermissionGroupDefinition GetGroupOrNull(string name);
[CanBeNull]
PermissionGroupDefinition AddGroup(
[NotNull] string name,
ILocalizableString displayName = null,
MultiTenancySides multiTenancySide = MultiTenancySides.Both);
void RemoveGroup(string name);
[CanBeNull]
PermissionDefinition GetPermissionOrNull([NotNull] string name);
}
}

4
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs

@ -2,6 +2,10 @@
{
public interface IPermissionDefinitionProvider
{
void PreDefine(IPermissionDefinitionContext context);
void Define(IPermissionDefinitionContext context);
void PostDefine(IPermissionDefinitionContext context);
}
}

9
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs

@ -32,12 +32,19 @@ namespace Volo.Abp.Authorization.Permissions
return await IsGrantedAsync(PrincipalAccessor.Principal, name);
}
public virtual async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)
public virtual async Task<bool> IsGrantedAsync(
ClaimsPrincipal claimsPrincipal,
string name)
{
Check.NotNull(name, nameof(name));
var permission = PermissionDefinitionManager.Get(name);
if (!permission.IsEnabled)
{
return false;
}
var multiTenancySide = claimsPrincipal?.GetMultiTenancySide()
?? CurrentTenant.GetMultiTenancySide();

23
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs

@ -46,6 +46,19 @@ namespace Volo.Abp.Authorization.Permissions
/// </summary>
public Dictionary<string, object> Properties { get; }
/// <summary>
/// Indicates whether this permission is enabled or disabled.
/// A permission is normally enabled.
/// A disabled permission can not be granted to anyone, but it is still
/// will be available to check its value (while it will always be false).
///
/// Disabling a permission would be helpful to hide a related application
/// functionality from users/clients.
///
/// Default: true.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Gets/sets a key-value on the <see cref="Properties"/>.
/// </summary>
@ -63,11 +76,13 @@ namespace Volo.Abp.Authorization.Permissions
protected internal PermissionDefinition(
[NotNull] string name,
ILocalizableString displayName = null,
MultiTenancySides multiTenancySide = MultiTenancySides.Both)
MultiTenancySides multiTenancySide = MultiTenancySides.Both,
bool isEnabled = true)
{
Name = Check.NotNull(name, nameof(name));
DisplayName = displayName ?? new FixedLocalizableString(name);
MultiTenancySide = multiTenancySide;
IsEnabled = isEnabled;
Properties = new Dictionary<string, object>();
Providers = new List<string>();
@ -77,12 +92,14 @@ namespace Volo.Abp.Authorization.Permissions
public virtual PermissionDefinition AddChild(
[NotNull] string name,
ILocalizableString displayName = null,
MultiTenancySides multiTenancySide = MultiTenancySides.Both)
MultiTenancySides multiTenancySide = MultiTenancySides.Both,
bool isEnabled = true)
{
var child = new PermissionDefinition(
name,
displayName,
multiTenancySide)
multiTenancySide,
isEnabled)
{
Parent = this
};

41
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
@ -6,10 +8,13 @@ namespace Volo.Abp.Authorization.Permissions
{
public class PermissionDefinitionContext : IPermissionDefinitionContext
{
public IServiceProvider ServiceProvider { get; }
internal Dictionary<string, PermissionGroupDefinition> Groups { get; }
internal PermissionDefinitionContext()
internal PermissionDefinitionContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Groups = new Dictionary<string, PermissionGroupDefinition>();
}
@ -28,7 +33,20 @@ namespace Volo.Abp.Authorization.Permissions
return Groups[name] = new PermissionGroupDefinition(name, displayName, multiTenancySide);
}
public virtual PermissionGroupDefinition GetGroupOrNull(string name)
[NotNull]
public virtual PermissionGroupDefinition GetGroup([NotNull] string name)
{
var group = GetGroupOrNull(name);
if (group == null)
{
throw new AbpException($"Could not find a permission definition group with the given name: {name}");
}
return group;
}
public virtual PermissionGroupDefinition GetGroupOrNull([NotNull] string name)
{
Check.NotNull(name, nameof(name));
@ -51,5 +69,22 @@ namespace Volo.Abp.Authorization.Permissions
Groups.Remove(name);
}
public virtual PermissionDefinition GetPermissionOrNull([NotNull] string name)
{
Check.NotNull(name, nameof(name));
foreach (var groupDefinition in Groups.Values)
{
var permissionDefinition = groupDefinition.GetPermissionOrNull(name);
if (permissionDefinition != null)
{
return permissionDefinition;
}
}
return null;
}
}
}

34
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContextExtensions.cs

@ -0,0 +1,34 @@
using JetBrains.Annotations;
namespace Volo.Abp.Authorization.Permissions
{
public static class PermissionDefinitionContextExtensions
{
/// <summary>
/// Finds and disables a permission with the given <paramref name="name"/>.
/// Returns false if given permission was not found.
/// </summary>
/// <param name="context">Permission definition context</param>
/// <param name="name">Name of the permission</param>
/// <returns>
/// Returns true if given permission was found.
/// Returns false if given permission was not found.
/// </returns>
public static bool TryDisablePermission(
[NotNull] this IPermissionDefinitionContext context,
[NotNull] string name)
{
Check.NotNull(context, nameof(context));
Check.NotNull(name, nameof(name));
var permission = context.GetPermissionOrNull(name);
if (permission == null)
{
return false;
}
permission.IsEnabled = false;
return true;
}
}
}

26
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs

@ -83,7 +83,7 @@ namespace Volo.Abp.Authorization.Permissions
}
protected virtual void AddPermissionToDictionaryRecursively(
Dictionary<string, PermissionDefinition> permissions,
Dictionary<string, PermissionDefinition> permissions,
PermissionDefinition permission)
{
if (permissions.ContainsKey(permission.Name))
@ -101,22 +101,32 @@ namespace Volo.Abp.Authorization.Permissions
protected virtual Dictionary<string, PermissionGroupDefinition> CreatePermissionGroupDefinitions()
{
var context = new PermissionDefinitionContext();
using (var scope = _serviceProvider.CreateScope())
{
var context = new PermissionDefinitionContext(scope.ServiceProvider);
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider)
.ToList();
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider)
.ToList();
foreach (var provider in providers)
{
provider.PreDefine(context);
}
foreach (var provider in providers)
{
provider.Define(context);
}
}
return context.Groups;
foreach (var provider in providers)
{
provider.PostDefine(context);
}
return context.Groups;
}
}
}
}

10
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs

@ -4,6 +4,16 @@ namespace Volo.Abp.Authorization.Permissions
{
public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider, ITransientDependency
{
public virtual void PreDefine(IPermissionDefinitionContext context)
{
}
public abstract void Define(IPermissionDefinitionContext context);
public virtual void PostDefine(IPermissionDefinitionContext context)
{
}
}
}

39
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using JetBrains.Annotations;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
@ -60,9 +61,15 @@ namespace Volo.Abp.Authorization.Permissions
public virtual PermissionDefinition AddPermission(
string name,
ILocalizableString displayName = null,
MultiTenancySides multiTenancySide = MultiTenancySides.Both)
MultiTenancySides multiTenancySide = MultiTenancySides.Both,
bool isEnabled = true)
{
var permission = new PermissionDefinition(name, displayName, multiTenancySide);
var permission = new PermissionDefinition(
name,
displayName,
multiTenancySide,
isEnabled
);
_permissions.Add(permission);
@ -95,5 +102,33 @@ namespace Volo.Abp.Authorization.Permissions
{
return $"[{nameof(PermissionGroupDefinition)} {Name}]";
}
[CanBeNull]
public PermissionDefinition GetPermissionOrNull([NotNull] string name)
{
Check.NotNull(name, nameof(name));
return GetPermissionOrNullRecursively(Permissions, name);
}
private PermissionDefinition GetPermissionOrNullRecursively(
IReadOnlyList<PermissionDefinition> permissions, string name)
{
foreach (var permission in permissions)
{
if (permission.Name == name)
{
return permission;
}
var childPermission = GetPermissionOrNullRecursively(permission.Children, name);
if (childPermission != null)
{
return childPermission;
}
}
return null;
}
}
}

56
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs

@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
@ -54,7 +55,7 @@ namespace Volo.Abp.Cli.ProjectModification
if (IsAngularProject(fileDirectory))
{
CreateNpmrcFile(Path.GetDirectoryName(file));
await CreateNpmrcFileAsync(Path.GetDirectoryName(file));
}
RunYarn(fileDirectory);
@ -68,21 +69,70 @@ namespace Volo.Abp.Cli.ProjectModification
}
}
private void CreateNpmrcFile(string directoryName)
private async Task CreateNpmrcFileAsync(string directoryName)
{
var fileName = Path.Combine(directoryName, ".npmrc");
var abpRegistry = "@abp:registry:https://www.myget.org/F/abp-nightly/npm";
var voloRegistry = await GetVoloRegistryAsync();
if (File.Exists(fileName))
{
var fileContent = File.ReadAllText(fileName);
if (!fileContent.Contains(abpRegistry))
{
fileContent += Environment.NewLine + abpRegistry;
}
if (!fileContent.Contains(voloRegistry))
{
fileContent += Environment.NewLine + voloRegistry;
}
File.WriteAllText(fileName, fileContent);
return;
}
using var fs = File.Create(fileName);
var content = new UTF8Encoding(true).GetBytes("@abp:registry:https://www.myget.org/F/abp-nightly/npm");
var content = new UTF8Encoding(true)
.GetBytes(abpRegistry + Environment.NewLine + voloRegistry);
fs.Write(content, 0, content.Length);
}
private async Task<string> GetVoloRegistryAsync()
{
var apikey = await GetApiKeyAsync();
if (string.IsNullOrWhiteSpace(apikey))
{
return "";
}
return "@volo:registry=https://www.myget.org/F/abp-commercial/auth/" + apikey + "/npm/";
}
public async Task<string> GetApiKeyAsync()
{
try
{
using (var client = new CliHttpClient(TimeSpan.FromMinutes(1)))
{
var responseMessage = await client.GetAsync(
$"{CliUrls.WwwAbpIo}api/myget/apikey/"
);
return Encoding.Default.GetString(await responseMessage.Content.ReadAsByteArrayAsync());
}
}
catch (Exception)
{
return "";
}
}
private bool IsAngularProject(string fileDirectory)
{
return File.Exists(Path.Combine(fileDirectory, "angular.json"));

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NugetPackageToLocalReferenceConverter.cs

@ -43,7 +43,7 @@ namespace Volo.Abp.Cli.ProjectModification
private string ProcessReferenceNodes(string folder, XmlDocument doc, List<NugetPackageInfoWithModuleName> nugetPackageList, string localPathPrefix, string sourceFile = "src")
{
var nodes = doc.SelectNodes("/Project/ItemGroup/PackageReference[starts-with(@Include, 'Volo.Abp')]");
var nodes = doc.SelectNodes("/Project/ItemGroup/PackageReference[starts-with(@Include, 'Volo.')]");
if (nodes == null)
{
@ -66,7 +66,8 @@ namespace Volo.Abp.Cli.ProjectModification
moduleName = Directory.GetParent(Directory.GetParent(Path.GetDirectoryName(localProject)).FullName).Name;
if (oldNodeIncludeValue.EndsWith(".test", StringComparison.InvariantCultureIgnoreCase) ||
oldNodeIncludeValue.EndsWith(".tests", StringComparison.InvariantCultureIgnoreCase))
oldNodeIncludeValue.EndsWith(".tests", StringComparison.InvariantCultureIgnoreCase) ||
oldNodeIncludeValue.EndsWith(".testbase", StringComparison.InvariantCultureIgnoreCase))
{
tempSourceFile = "test";
}

36
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
@ -15,7 +14,6 @@ using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore.EntityHistory
{
@ -26,6 +24,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
protected IAuditingStore AuditingStore { get; }
protected IJsonSerializer JsonSerializer { get; }
protected AbpAuditingOptions Options { get; }
protected IAuditingHelper AuditingHelper { get; }
private readonly IClock _clock;
@ -33,11 +32,13 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
IAuditingStore auditingStore,
IOptions<AbpAuditingOptions> options,
IClock clock,
IJsonSerializer jsonSerializer)
IJsonSerializer jsonSerializer,
IAuditingHelper auditingHelper)
{
_clock = clock;
AuditingStore = auditingStore;
JsonSerializer = jsonSerializer;
AuditingHelper = auditingHelper;
Options = options.Value;
Logger = NullLogger<EntityHistoryHelper>.Instance;
@ -203,39 +204,14 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return false;
}
if (Options.IgnoredTypes.Any(t => t.IsInstanceOfType(entityEntry.Entity)))
{
return false;
}
var entityType = entityEntry.Metadata.ClrType;
var entityType = entityEntry.Entity.GetType();
if (!EntityHelper.IsEntity(entityType))
{
return false;
}
if (!entityType.IsPublic)
{
return false;
}
if (entityType.IsDefined(typeof(AuditedAttribute), true))
{
return true;
}
if (entityEntry.Metadata.GetProperties()
.Any(p => p.PropertyInfo?.IsDefined(typeof(AuditedAttribute)) ?? false))
{
return true;
}
if (entityType.IsDefined(typeof(DisableAuditingAttribute), true))
{
return false;
}
if (Options.EntityHistorySelectors.Any(selector => selector.Predicate(entityType)))
if (AuditingHelper.IsEntityHistoryEnabled(entityType))
{
return true;
}

11
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs

@ -1,4 +1,5 @@
using Volo.Abp.Authorization.Permissions;
using Shouldly;
using Volo.Abp.Authorization.Permissions;
namespace Volo.Abp.Authorization.TestServices
{
@ -6,14 +7,18 @@ namespace Volo.Abp.Authorization.TestServices
{
public override void Define(IPermissionDefinitionContext context)
{
PermissionGroupDefinition getGroup = context.GetGroupOrNull("TestGetGroup");
var getGroup = context.GetGroupOrNull("TestGetGroup");
if (getGroup == null)
{
getGroup = context.AddGroup("TestGetGroup");
}
PermissionGroupDefinition group = context.AddGroup("TestGroup");
var group = context.AddGroup("TestGroup");
group.AddPermission("MyAuthorizedService1");
group.GetPermissionOrNull("MyAuthorizedService1").ShouldNotBeNull();
context.RemoveGroup("TestGetGroup");
}
}

26
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs

@ -18,7 +18,7 @@ namespace Volo.Blogging.Pages.Blog
public const string DefaultTitle = "Blog";
public const int MaxShortContentLength = 128;
public const int MaxShortContentLength = 200;
public string GetTitle(string title = null)
{
@ -30,7 +30,7 @@ namespace Volo.Blogging.Pages.Blog
return title;
}
public string GetShortContent(string content) //TODO: This should be moved to its own place!
public string GetShortContent(string content)
{
var html = RenderMarkdownToString(content);
var plainText = Regex.Replace(html, "<[^>]*>", "");
@ -40,19 +40,23 @@ namespace Volo.Blogging.Pages.Blog
return "";
}
var firsParag = plainText.Split(Environment.NewLine).FirstOrDefault(s => !string.IsNullOrWhiteSpace(s));
var shortContent = new StringBuilder();
var lines = plainText.Split(Environment.NewLine).Where(s => !string.IsNullOrWhiteSpace(s));
if (firsParag == null)
foreach (var line in lines)
{
return plainText;
if (shortContent.Length < MaxShortContentLength)
{
shortContent.Append($" {line}");
}
if(shortContent.Length >= MaxShortContentLength)
{
return shortContent.ToString().Substring(0, MaxShortContentLength) + "...";
}
}
if (firsParag.Length <= MaxShortContentLength)
{
return firsParag;
}
return firsParag.Substring(0, MaxShortContentLength) + "...";
return shortContent.ToString();
}
public IHtmlContent RenderMarkdownToHtml(string content)

1
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml

@ -19,6 +19,7 @@
ViewBag.FluidLayout = true;
Layout = ThemeManager.CurrentTheme.GetEmptyLayout();
PageLayout.Content.Title = Model.DocumentName?.Replace("-", " ");
ViewBag.Description = Model.GetDescription();
}
@section styles {
<abp-style-bundle name="@typeof(IndexModel).FullName">

22
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

@ -2,7 +2,9 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
@ -64,9 +66,10 @@ namespace Volo.Docs.Pages.Documents.Project
public DocumentParametersDto DocumentPreferences { get; set; }
public DocumentRenderParameters UserPreferences { get; set; } = new DocumentRenderParameters();
public bool FullSearchEnabled { get; set; }
private const int MaxDescriptionMetaTagLength = 200;
private readonly IDocumentAppService _documentAppService;
private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory;
private readonly IProjectAppService _projectAppService;
@ -591,5 +594,22 @@ namespace Volo.Docs.Pages.Documents.Project
}
}
}
public string GetDescription()
{
var firstParagraph = new Regex(@"<p>(.*?)</p>", RegexOptions.IgnoreCase);
var match = firstParagraph.Match(Document.Content);
if (!match.Success)
{
return null;
}
var description = HttpUtility.HtmlDecode(match.Value);
var htmlTagReplacer = new Regex(@"<[^>]*>", RegexOptions.IgnoreCase);
description = htmlTagReplacer.Replace(description, m => string.Empty);
return description.Truncate(MaxDescriptionMetaTagLength);
}
}
}

5
modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs

@ -52,6 +52,11 @@ namespace Volo.Abp.PermissionManagement
foreach (var permission in group.GetPermissionsWithChildren())
{
if (!permission.IsEnabled)
{
continue;
}
if (permission.Providers.Any() && !permission.Providers.Contains(providerName))
{
continue;

11
modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManager.cs

@ -71,6 +71,12 @@ namespace Volo.Abp.PermissionManagement
{
var permission = PermissionDefinitionManager.Get(permissionName);
if (!permission.IsEnabled)
{
//TODO: BusinessException
throw new ApplicationException($"The permission named '{permission.Name}' is disabled!");
}
if (permission.Providers.Any() && !permission.Providers.Contains(providerName))
{
//TODO: BusinessException
@ -109,6 +115,11 @@ namespace Volo.Abp.PermissionManagement
{
var result = new PermissionWithGrantedProviders(permission.Name, false);
if (!permission.IsEnabled)
{
return result;
}
if (!permission.MultiTenancySide.HasFlag(CurrentTenant.GetMultiTenancySide()))
{
return result;

1
modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs

@ -31,7 +31,6 @@ namespace Volo.Abp.PermissionManagement.Application.Tests.Volo.Abp.PermissionMan
permissionListResultDto.Groups.Count.ShouldBe(1);
permissionListResultDto.Groups.ShouldContain(x => x.Name == "TestGroup");
permissionListResultDto.Groups.First().Permissions.Count.ShouldBe(4);
permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission1");
permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission2");
permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission2.ChildPermission1");

11
modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionChecker_User_Tests.cs

@ -35,6 +35,17 @@ namespace Volo.Abp.PermissionManagement
)).ShouldBeFalse();
}
[Fact]
public async Task Should_Return_False_For_Granted_Current_User_If_The_Permission_Is_Disabled()
{
//Disabled permissions always returns false!
(await _permissionChecker.IsGrantedAsync(
CreatePrincipal(PermissionTestDataBuilder.User1Id),
"MyDisabledPermission1"
)).ShouldBeFalse();
}
[Fact]
public async Task Should_Return_False_For_Current_User_If_Anonymous()
{

9
modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs

@ -31,6 +31,15 @@ namespace Volo.Abp.PermissionManagement
)
);
await _permissionGrantRepository.InsertAsync(
new PermissionGrant(
_guidGenerator.Create(),
"MyDisabledPermission1",
UserPermissionValueProvider.ProviderName,
User1Id.ToString()
)
);
await _permissionGrantRepository.InsertAsync(
new PermissionGrant(
_guidGenerator.Create(),

1
modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs

@ -10,6 +10,7 @@ namespace Volo.Abp.PermissionManagement
var testGroup = context.AddGroup("TestGroup");
testGroup.AddPermission("MyPermission1");
testGroup.AddPermission("MyDisabledPermission1", isEnabled: false);
var myPermission2 = testGroup.AddPermission("MyPermission2");
myPermission2.AddChild("MyPermission2.ChildPermission1");

Loading…
Cancel
Save