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 #### 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. 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. 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 ## 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. 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 ## 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). 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. 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: 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 ````csharp
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
var configuration = context.Services.GetConfiguration(); var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment(); var hostingEnvironment = context.Services.GetHostingEnvironment();
//... other configarations //... other configarations.
ConfigureHangfire(context, configuration); ConfigureHangfire(context, configuration);
} }

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

@ -8,12 +8,20 @@
## API ## 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. - `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`. 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. 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. `nonce` will be set.
### None() ### None
```js
CONTENT_SECURITY_STRATEGY.None()
```
Nothing will be done. 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 ## 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). - `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). - `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`. 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. 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. `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. `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 ## API
### constructor(public target?: HTMLElement, public position?: InsertPosition) ### constructor
```js
constructor(
public target?: HTMLElement,
public position?: InsertPosition
)
```
- `target` is an HTMLElement (_default: document.head_). - `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'_). - `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. 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>`. `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>`. `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>`. `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`. `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`. `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) - [LoadingStrategy](./Loading-Strategy.md)
- [ContentStrategy](./Content-Strategy.md)
TODO: Place new InsertionStrategy link here.

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). 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. - `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_). - `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? ## 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 ## 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. - `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_) - `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. 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`. 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. 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. 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. 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. 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. 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) - [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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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", "text": "Lazy Loading Scripts & Styles",
"path": "UI/Angular/Lazy-Load-Service.md" "path": "UI/Angular/Lazy-Load-Service.md"
}, },
{
"text": "DomInsertionService",
"path": "UI/Angular/Dom-Insertion-Service.md"
},
{ {
"text": "TrackByService", "text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md" "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)定义)并更改其定义. 从 `PermissionDefinitionProvider` 派生的类(就像上面的示例一样) 可以获取现有的权限定义(由依赖[模块](Module-Development-Basics.md)定义)并更改其定义.
示例:
````csharp
context
.GetPermissionOrNull(IdentityPermissions.Roles.Delete)
.IsEnabled = false;
````
当你在权限提供程序编写了这行代码,它会找到[身份模块](Modules/Identity.md)的 "role deletion" 权限并且禁用它,因此没有人可以在应用程序中删除角色.
> 提供: 更好的方式应该检查 `GetPermissionOrNull` 返回值,如果权限未定义,它会返回null值.
## IAuthorizationService ## IAuthorizationService
ASP.NET Core 提供了 `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. * `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). 其他选项: * `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI). * `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
*
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,您会希望使用最新的版本. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,您会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`). * `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中. * `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串!
### add-package ### 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迁移", "text": "从ASP.NET Boilerplate迁移",
"path": "AspNet-Boilerplate-Migration-Guide.md" "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; private readonly ITenantResolveResultAccessor _tenantResolveResultAccessor;
public MultiTenancyMiddleware( public MultiTenancyMiddleware(
ITenantResolver tenantResolver, ITenantResolver tenantResolver,
ITenantStore tenantStore, ITenantStore tenantStore,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
ITenantResolveResultAccessor tenantResolveResultAccessor) ITenantResolveResultAccessor tenantResolveResultAccessor)
{ {
_tenantResolver = tenantResolver; _tenantResolver = tenantResolver;
@ -34,11 +34,13 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
if (resolveResult.TenantIdOrName != null) if (resolveResult.TenantIdOrName != null)
{ {
tenant = await FindTenantAsync(resolveResult.TenantIdOrName); tenant = await FindTenantAsync(resolveResult.TenantIdOrName);
if (tenant == null) if (tenant == null)
{ {
//TODO: A better exception? throw new BusinessException(
throw new AbpException( code: "Volo.AbpIo.MultiTenancy:010001",
"There is no tenant with given tenant id or name: " + resolveResult.TenantIdOrName 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"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>@pageTitle</title> <title>@pageTitle</title>
@if (ViewBag.Description!=null)
{
<meta name="description" content="@ViewBag.Description" />
}
<abp-style-bundle name="@BasicThemeBundles.Styles.Global" /> <abp-style-bundle name="@BasicThemeBundles.Styles.Global" />
@await Component.InvokeAsync(typeof(WidgetStylesViewComponent)) @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; 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() public virtual AuditLogInfo CreateAuditLogInfo()
{ {
var auditInfo = new AuditLogInfo 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 namespace Volo.Abp.Auditing
{ {
//TODO: Move ShouldSaveAudit and rename to IAuditingFactory //TODO: Move ShouldSaveAudit & IsEntityHistoryEnabled and rename to IAuditingFactory
public interface IAuditingHelper public interface IAuditingHelper
{ {
bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false); bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false);
bool IsEntityHistoryEnabled(Type entityType, bool defaultValue = false);
AuditLogInfo CreateAuditLogInfo(); AuditLogInfo CreateAuditLogInfo();
AuditLogActionInfo CreateAuditLogAction( 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.Localization;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -7,13 +8,35 @@ namespace Volo.Abp.Authorization.Permissions
public interface IPermissionDefinitionContext public interface IPermissionDefinitionContext
{ {
//TODO: Add Get methods to find and modify a permission or group. //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); PermissionGroupDefinition GetGroupOrNull(string name);
[CanBeNull]
PermissionGroupDefinition AddGroup( PermissionGroupDefinition AddGroup(
[NotNull] string name, [NotNull] string name,
ILocalizableString displayName = null, ILocalizableString displayName = null,
MultiTenancySides multiTenancySide = MultiTenancySides.Both); MultiTenancySides multiTenancySide = MultiTenancySides.Both);
void RemoveGroup(string name); 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 public interface IPermissionDefinitionProvider
{ {
void PreDefine(IPermissionDefinitionContext context);
void Define(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); 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)); Check.NotNull(name, nameof(name));
var permission = PermissionDefinitionManager.Get(name); var permission = PermissionDefinitionManager.Get(name);
if (!permission.IsEnabled)
{
return false;
}
var multiTenancySide = claimsPrincipal?.GetMultiTenancySide() var multiTenancySide = claimsPrincipal?.GetMultiTenancySide()
?? CurrentTenant.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> /// </summary>
public Dictionary<string, object> Properties { get; } 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> /// <summary>
/// Gets/sets a key-value on the <see cref="Properties"/>. /// Gets/sets a key-value on the <see cref="Properties"/>.
/// </summary> /// </summary>
@ -63,11 +76,13 @@ namespace Volo.Abp.Authorization.Permissions
protected internal PermissionDefinition( protected internal PermissionDefinition(
[NotNull] string name, [NotNull] string name,
ILocalizableString displayName = null, ILocalizableString displayName = null,
MultiTenancySides multiTenancySide = MultiTenancySides.Both) MultiTenancySides multiTenancySide = MultiTenancySides.Both,
bool isEnabled = true)
{ {
Name = Check.NotNull(name, nameof(name)); Name = Check.NotNull(name, nameof(name));
DisplayName = displayName ?? new FixedLocalizableString(name); DisplayName = displayName ?? new FixedLocalizableString(name);
MultiTenancySide = multiTenancySide; MultiTenancySide = multiTenancySide;
IsEnabled = isEnabled;
Properties = new Dictionary<string, object>(); Properties = new Dictionary<string, object>();
Providers = new List<string>(); Providers = new List<string>();
@ -77,12 +92,14 @@ namespace Volo.Abp.Authorization.Permissions
public virtual PermissionDefinition AddChild( public virtual PermissionDefinition AddChild(
[NotNull] string name, [NotNull] string name,
ILocalizableString displayName = null, ILocalizableString displayName = null,
MultiTenancySides multiTenancySide = MultiTenancySides.Both) MultiTenancySides multiTenancySide = MultiTenancySides.Both,
bool isEnabled = true)
{ {
var child = new PermissionDefinition( var child = new PermissionDefinition(
name, name,
displayName, displayName,
multiTenancySide) multiTenancySide,
isEnabled)
{ {
Parent = this 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.Localization;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -6,10 +8,13 @@ namespace Volo.Abp.Authorization.Permissions
{ {
public class PermissionDefinitionContext : IPermissionDefinitionContext public class PermissionDefinitionContext : IPermissionDefinitionContext
{ {
public IServiceProvider ServiceProvider { get; }
internal Dictionary<string, PermissionGroupDefinition> Groups { get; } internal Dictionary<string, PermissionGroupDefinition> Groups { get; }
internal PermissionDefinitionContext() internal PermissionDefinitionContext(IServiceProvider serviceProvider)
{ {
ServiceProvider = serviceProvider;
Groups = new Dictionary<string, PermissionGroupDefinition>(); Groups = new Dictionary<string, PermissionGroupDefinition>();
} }
@ -28,7 +33,20 @@ namespace Volo.Abp.Authorization.Permissions
return Groups[name] = new PermissionGroupDefinition(name, displayName, multiTenancySide); 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)); Check.NotNull(name, nameof(name));
@ -51,5 +69,22 @@ namespace Volo.Abp.Authorization.Permissions
Groups.Remove(name); 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( protected virtual void AddPermissionToDictionaryRecursively(
Dictionary<string, PermissionDefinition> permissions, Dictionary<string, PermissionDefinition> permissions,
PermissionDefinition permission) PermissionDefinition permission)
{ {
if (permissions.ContainsKey(permission.Name)) if (permissions.ContainsKey(permission.Name))
@ -101,22 +101,32 @@ namespace Volo.Abp.Authorization.Permissions
protected virtual Dictionary<string, PermissionGroupDefinition> CreatePermissionGroupDefinitions() protected virtual Dictionary<string, PermissionGroupDefinition> CreatePermissionGroupDefinitions()
{ {
var context = new PermissionDefinitionContext();
using (var scope = _serviceProvider.CreateScope()) using (var scope = _serviceProvider.CreateScope())
{ {
var context = new PermissionDefinitionContext(scope.ServiceProvider);
var providers = Options var providers = Options
.DefinitionProviders .DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider) .Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider)
.ToList(); .ToList();
foreach (var provider in providers)
{
provider.PreDefine(context);
}
foreach (var provider in providers) foreach (var provider in providers)
{ {
provider.Define(context); 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 abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider, ITransientDependency
{ {
public virtual void PreDefine(IPermissionDefinitionContext context)
{
}
public abstract void Define(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.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using JetBrains.Annotations;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -60,9 +61,15 @@ namespace Volo.Abp.Authorization.Permissions
public virtual PermissionDefinition AddPermission( public virtual PermissionDefinition AddPermission(
string name, string name,
ILocalizableString displayName = null, 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); _permissions.Add(permission);
@ -95,5 +102,33 @@ namespace Volo.Abp.Authorization.Permissions
{ {
return $"[{nameof(PermissionGroupDefinition)} {Name}]"; 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 Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -54,7 +55,7 @@ namespace Volo.Abp.Cli.ProjectModification
if (IsAngularProject(fileDirectory)) if (IsAngularProject(fileDirectory))
{ {
CreateNpmrcFile(Path.GetDirectoryName(file)); await CreateNpmrcFileAsync(Path.GetDirectoryName(file));
} }
RunYarn(fileDirectory); 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 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)) 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; return;
} }
using var fs = File.Create(fileName); 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); 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) private bool IsAngularProject(string fileDirectory)
{ {
return File.Exists(Path.Combine(fileDirectory, "angular.json")); 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") 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) if (nodes == null)
{ {
@ -66,7 +66,8 @@ namespace Volo.Abp.Cli.ProjectModification
moduleName = Directory.GetParent(Directory.GetParent(Path.GetDirectoryName(localProject)).FullName).Name; moduleName = Directory.GetParent(Directory.GetParent(Path.GetDirectoryName(localProject)).FullName).Name;
if (oldNodeIncludeValue.EndsWith(".test", StringComparison.InvariantCultureIgnoreCase) || if (oldNodeIncludeValue.EndsWith(".test", StringComparison.InvariantCultureIgnoreCase) ||
oldNodeIncludeValue.EndsWith(".tests", StringComparison.InvariantCultureIgnoreCase)) oldNodeIncludeValue.EndsWith(".tests", StringComparison.InvariantCultureIgnoreCase) ||
oldNodeIncludeValue.EndsWith(".testbase", StringComparison.InvariantCultureIgnoreCase))
{ {
tempSourceFile = "test"; tempSourceFile = "test";
} }

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

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
@ -15,7 +14,6 @@ using Volo.Abp.Json;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
using Volo.Abp.Timing; using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore.EntityHistory namespace Volo.Abp.EntityFrameworkCore.EntityHistory
{ {
@ -26,6 +24,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
protected IAuditingStore AuditingStore { get; } protected IAuditingStore AuditingStore { get; }
protected IJsonSerializer JsonSerializer { get; } protected IJsonSerializer JsonSerializer { get; }
protected AbpAuditingOptions Options { get; } protected AbpAuditingOptions Options { get; }
protected IAuditingHelper AuditingHelper { get; }
private readonly IClock _clock; private readonly IClock _clock;
@ -33,11 +32,13 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
IAuditingStore auditingStore, IAuditingStore auditingStore,
IOptions<AbpAuditingOptions> options, IOptions<AbpAuditingOptions> options,
IClock clock, IClock clock,
IJsonSerializer jsonSerializer) IJsonSerializer jsonSerializer,
IAuditingHelper auditingHelper)
{ {
_clock = clock; _clock = clock;
AuditingStore = auditingStore; AuditingStore = auditingStore;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
AuditingHelper = auditingHelper;
Options = options.Value; Options = options.Value;
Logger = NullLogger<EntityHistoryHelper>.Instance; Logger = NullLogger<EntityHistoryHelper>.Instance;
@ -203,39 +204,14 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return false; return false;
} }
if (Options.IgnoredTypes.Any(t => t.IsInstanceOfType(entityEntry.Entity))) var entityType = entityEntry.Metadata.ClrType;
{
return false;
}
var entityType = entityEntry.Entity.GetType();
if (!EntityHelper.IsEntity(entityType)) if (!EntityHelper.IsEntity(entityType))
{ {
return false; return false;
} }
if (!entityType.IsPublic) if (AuditingHelper.IsEntityHistoryEnabled(entityType))
{
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)))
{ {
return true; 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 namespace Volo.Abp.Authorization.TestServices
{ {
@ -6,14 +7,18 @@ namespace Volo.Abp.Authorization.TestServices
{ {
public override void Define(IPermissionDefinitionContext context) public override void Define(IPermissionDefinitionContext context)
{ {
PermissionGroupDefinition getGroup = context.GetGroupOrNull("TestGetGroup"); var getGroup = context.GetGroupOrNull("TestGetGroup");
if (getGroup == null) if (getGroup == null)
{ {
getGroup = context.AddGroup("TestGetGroup"); getGroup = context.AddGroup("TestGetGroup");
} }
PermissionGroupDefinition group = context.AddGroup("TestGroup");
var group = context.AddGroup("TestGroup");
group.AddPermission("MyAuthorizedService1"); group.AddPermission("MyAuthorizedService1");
group.GetPermissionOrNull("MyAuthorizedService1").ShouldNotBeNull();
context.RemoveGroup("TestGetGroup"); 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 string DefaultTitle = "Blog";
public const int MaxShortContentLength = 128; public const int MaxShortContentLength = 200;
public string GetTitle(string title = null) public string GetTitle(string title = null)
{ {
@ -30,7 +30,7 @@ namespace Volo.Blogging.Pages.Blog
return title; 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 html = RenderMarkdownToString(content);
var plainText = Regex.Replace(html, "<[^>]*>", ""); var plainText = Regex.Replace(html, "<[^>]*>", "");
@ -40,19 +40,23 @@ namespace Volo.Blogging.Pages.Blog
return ""; 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 shortContent.ToString();
{
return firsParag;
}
return firsParag.Substring(0, MaxShortContentLength) + "...";
} }
public IHtmlContent RenderMarkdownToHtml(string content) public IHtmlContent RenderMarkdownToHtml(string content)

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

@ -19,6 +19,7 @@
ViewBag.FluidLayout = true; ViewBag.FluidLayout = true;
Layout = ThemeManager.CurrentTheme.GetEmptyLayout(); Layout = ThemeManager.CurrentTheme.GetEmptyLayout();
PageLayout.Content.Title = Model.DocumentName?.Replace("-", " "); PageLayout.Content.Title = Model.DocumentName?.Replace("-", " ");
ViewBag.Description = Model.GetDescription();
} }
@section styles { @section styles {
<abp-style-bundle name="@typeof(IndexModel).FullName"> <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.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -64,9 +66,10 @@ namespace Volo.Docs.Pages.Documents.Project
public DocumentParametersDto DocumentPreferences { get; set; } public DocumentParametersDto DocumentPreferences { get; set; }
public DocumentRenderParameters UserPreferences { get; set; } = new DocumentRenderParameters(); public DocumentRenderParameters UserPreferences { get; set; } = new DocumentRenderParameters();
public bool FullSearchEnabled { get; set; } public bool FullSearchEnabled { get; set; }
private const int MaxDescriptionMetaTagLength = 200;
private readonly IDocumentAppService _documentAppService; private readonly IDocumentAppService _documentAppService;
private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory; private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory;
private readonly IProjectAppService _projectAppService; 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()) foreach (var permission in group.GetPermissionsWithChildren())
{ {
if (!permission.IsEnabled)
{
continue;
}
if (permission.Providers.Any() && !permission.Providers.Contains(providerName)) if (permission.Providers.Any() && !permission.Providers.Contains(providerName))
{ {
continue; 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); 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)) if (permission.Providers.Any() && !permission.Providers.Contains(providerName))
{ {
//TODO: BusinessException //TODO: BusinessException
@ -109,6 +115,11 @@ namespace Volo.Abp.PermissionManagement
{ {
var result = new PermissionWithGrantedProviders(permission.Name, false); var result = new PermissionWithGrantedProviders(permission.Name, false);
if (!permission.IsEnabled)
{
return result;
}
if (!permission.MultiTenancySide.HasFlag(CurrentTenant.GetMultiTenancySide())) if (!permission.MultiTenancySide.HasFlag(CurrentTenant.GetMultiTenancySide()))
{ {
return result; 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.Count.ShouldBe(1);
permissionListResultDto.Groups.ShouldContain(x => x.Name == "TestGroup"); 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 == "MyPermission1");
permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission2"); permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission2");
permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission2.ChildPermission1"); 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(); )).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] [Fact]
public async Task Should_Return_False_For_Current_User_If_Anonymous() 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( await _permissionGrantRepository.InsertAsync(
new PermissionGrant( new PermissionGrant(
_guidGenerator.Create(), _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"); var testGroup = context.AddGroup("TestGroup");
testGroup.AddPermission("MyPermission1"); testGroup.AddPermission("MyPermission1");
testGroup.AddPermission("MyDisabledPermission1", isEnabled: false);
var myPermission2 = testGroup.AddPermission("MyPermission2"); var myPermission2 = testGroup.AddPermission("MyPermission2");
myPermission2.AddChild("MyPermission2.ChildPermission1"); myPermission2.AddChild("MyPermission2.ChildPermission1");

Loading…
Cancel
Save