Browse Source

Merge branch 'dev'

rel-2.4
Arkat Erol 7 years ago
parent
commit
ea426d8bf0
  1. 9
      build/common.ps1
  2. 2
      common.props
  3. 3
      docs/cs/AspNetCore/Widgets.md
  4. 4
      docs/cs/Contribution/Localization-Text-Files.md
  5. 2
      docs/cs/Getting-Started-AspNetCore-Application.md
  6. 2
      docs/cs/Getting-Started-Console-Application.md
  7. 26
      docs/en/Authorization.md
  8. 4
      docs/en/Background-Jobs-Hangfire.md
  9. 1
      docs/en/CLI.md
  10. 4
      docs/en/Contribution/Localization-Text-Files.md
  11. 2
      docs/en/Entity-Framework-Core-Migrations.md
  12. 2
      docs/en/Getting-Started-AspNetCore-Application.md
  13. 2
      docs/en/Getting-Started-Console-Application.md
  14. 2
      docs/en/UI/AspNetCore/Widgets.md
  15. 2
      docs/pt-BR/Tutorials/Angular/Part-I.md
  16. 2
      docs/pt-BR/Tutorials/Angular/Part-II.md
  17. 2
      docs/pt-BR/Tutorials/Angular/Part-III.md
  18. 2
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-I.md
  19. 2
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-II.md
  20. 2
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-III.md
  21. 26
      docs/zh-Hans/Authorization.md
  22. 43
      docs/zh-Hans/Background-Jobs-Hangfire.md
  23. 2
      docs/zh-Hans/CLI.md
  24. 4
      docs/zh-Hans/Contribution/Localization-Text-Files.md
  25. 2
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  26. 3
      docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md
  27. 113
      docs/zh-Hans/How-To/Customize-Login-Page-MVC.md
  28. 3
      docs/zh-Hans/How-To/Customize-SignIn-Manager.md
  29. 9
      docs/zh-Hans/How-To/Index.md
  30. 2
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md
  31. 4
      docs/zh-Hans/docs-nav.json
  32. BIN
      docs/zh-Hans/images/angular-folder-structure.png
  33. BIN
      docs/zh-Hans/images/angular-template-structure-diagram.png
  34. BIN
      docs/zh-Hans/images/react-native-folder-structure.png
  35. BIN
      docs/zh-Hans/images/react-native-navigation-structure.png
  36. BIN
      docs/zh-Hans/images/react-native-store-folder.png
  37. 14
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs
  38. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs
  39. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml
  40. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
  41. 38
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs
  42. 4
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs
  43. 25
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs
  44. 4
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs
  45. 9
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs
  46. 23
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs
  47. 41
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs
  48. 34
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContextExtensions.cs
  49. 26
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs
  50. 10
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs
  51. 39
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs
  52. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  53. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs
  54. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs
  55. 56
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  56. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NugetPackageToLocalReferenceConverter.cs
  57. 29
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
  58. 36
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  59. 2
      framework/src/Volo.Abp.MailKit/Volo.Abp.MailKit.csproj
  60. 2
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json
  61. 11
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs
  62. 9
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/EntityChangeWithUsername.cs
  63. 4
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs
  64. 25
      modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs
  65. 29
      modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs
  66. 225
      modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs
  67. 26
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs
  68. 2
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs
  69. 1
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  70. 22
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs
  71. 5
      modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs
  72. 11
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManager.cs
  73. 1
      modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs
  74. 11
      modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionChecker_User_Tests.cs
  75. 9
      modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs
  76. 1
      modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs
  77. 52
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  78. 46
      npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
  79. 43
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts
  80. 4
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts
  81. 21
      npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.html
  82. 13
      npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts
  83. 26
      npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html
  84. 10
      npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.scss
  85. 2
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  86. 7
      npm/ng-packs/packages/theme-shared/src/lib/tests/table.component.spec.ts
  87. 52
      npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts
  88. 1
      npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts
  89. 45
      npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts
  90. 25
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication.sln
  91. 33
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/AppModule.cs
  92. 16
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/BasicAspNetCoreApplication.csproj
  93. 26
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Error.cshtml
  94. 31
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Error.cshtml.cs
  95. 10
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Index.cshtml
  96. 25
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Index.cshtml.cs
  97. 8
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Privacy.cshtml
  98. 24
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Privacy.cshtml.cs
  99. 50
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Shared/_Layout.cshtml
  100. 2
      samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Shared/_ValidationScriptsPartial.cshtml

9
build/common.ps1

@ -21,15 +21,6 @@ $solutionPaths = (
"../modules/client-simulation",
"../templates/module/aspnet-core",
"../templates/app/aspnet-core",
"../samples/BasicAspNetCoreApplication",
"../samples/BasicConsoleApplication",
"../samples/BookStore",
"../samples/BookStore-Angular-MongoDb/aspnet-core",
"../samples/BookStore-Modular/modules/book-management",
"../samples/BookStore-Modular/application",
"../samples/DashboardDemo",
"../samples/EfCoreMigrationDemo",
"../samples/MicroserviceDemo",
"../samples/RabbitMqEventBus",
"../abp_io/AbpIoLocalization"
)

2
common.props

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

3
docs/cs/AspNetCore/Widgets.md

@ -502,4 +502,5 @@ Configure<AbpWidgetOptions>(options =>
## Podívejte se také na
* [Příklad projektu (zdrojový kód)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo).
* [Příklad projektu (zdrojový kód)](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo).

4
docs/cs/Contribution/Localization-Text-Files.md

@ -29,8 +29,8 @@ Toto je seznam lokalizačních textových souborů pro každého kdo chce přisp
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json
* https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json

2
docs/cs/Getting-Started-AspNetCore-Application.md

@ -153,5 +153,5 @@ namespace BasicAspNetCoreApplication
## Zdrojový kód
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication).
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication).

2
docs/cs/Getting-Started-Console-Application.md

@ -178,4 +178,4 @@ Stačí volat metodu `options.UseAutofac()` v možnostech `AbpApplicationFactory
## Zdrojový kód
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp/tree/master/samples/BasicConsoleApplication).
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication).

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);
}

1
docs/en/CLI.md

@ -61,6 +61,7 @@ abp new Acme.BookStore
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server!
* `--local-framework-ref --abp-path`: keeps local references to projects instead of replacing with NuGet package references.
### add-package

4
docs/en/Contribution/Localization-Text-Files.md

@ -23,8 +23,8 @@ Here, a list of localization text files for anyone wants to contribute to locali
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json

2
docs/en/Entity-Framework-Core-Migrations.md

@ -6,7 +6,7 @@ This document begins by **introducing the default structure** provided by [the a
### Source Code
You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code.
You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code.
## About the EF Core Code First Migrations

2
docs/en/Getting-Started-AspNetCore-Application.md

@ -153,5 +153,5 @@ namespace BasicAspNetCoreApplication
## Source Code
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication).
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication).

2
docs/en/Getting-Started-Console-Application.md

@ -178,4 +178,4 @@ Just called `options.UseAutofac()` method in the `AbpApplicationFactory.Create`
## Source Code
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp/tree/master/samples/BasicConsoleApplication).
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication).

2
docs/en/UI/AspNetCore/Widgets.md

@ -502,4 +502,4 @@ Configure<AbpWidgetOptions>(options =>
## See Also
* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo).
* [Example project (source code)](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo).

2
docs/pt-BR/Tutorials/Angular/Part-I.md

@ -10,7 +10,7 @@ Esta é a primeira parte da série de tutoriais angulares. Veja todas as peças:
- [Parte II: Criar, atualizar e excluir livros](Part-II)
- [Parte III: Testes de Integração](Part-III)
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) .
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) .
### Criando o projeto

2
docs/pt-BR/Tutorials/Angular/Part-II.md

@ -8,7 +8,7 @@ Esta é a segunda parte da série de tutoriais angulares. Veja todas as peças:
- **Parte II: Criar, atualizar e excluir livros (este tutorial)**
- [Parte III: Testes de Integração](Part-III.md)
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) .
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) .
### Criando um novo livro

2
docs/pt-BR/Tutorials/Angular/Part-III.md

@ -8,7 +8,7 @@ Esta é a terceira parte da série de tutoriais Angular. Veja todas as peças:
- [Parte II: Criar, atualizar e excluir livros](Part-II.md)
- **Parte III: Testes de Integração (este tutorial)**
Esta parte abrange os testes do **lado** do **servidor** . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) .
Esta parte abrange os testes do **lado** do **servidor** . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) .
### Testar projetos na solução

2
docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-I.md

@ -10,7 +10,7 @@ Esta é a primeira parte da série de tutoriais do ASP.NET Core MVC. Veja todas
- [Parte II: Criar, atualizar e excluir livros](Part-II.md)
- [Parte III: Testes de Integração](Part-III.md)
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp/tree/master/samples/BookStore) .
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore) .
> Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial.

2
docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-II.md

@ -8,7 +8,7 @@ Esta é a segunda parte da série de tutoriais do ASP.NET Core MVC. Veja todas a
- **Parte II: Criar, atualizar e excluir livros (este tutorial)**
- [Parte III: Testes de Integração](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III)
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/volosoft/abp/tree/master/samples/BookStore) .
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore) .
> Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial.

2
docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-III.md

@ -8,7 +8,7 @@ Esta é a terceira parte da série de tutoriais do ASP.NET Core MVC. Veja todas
- [Parte II: Criar, atualizar e excluir livros](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II)
- **Parte III: Testes de Integração (este tutorial)**
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/volosoft/abp/tree/master/samples/BookStore) .
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore).
> Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial.

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

@ -60,6 +60,8 @@ abp new Acme.BookStore
* `--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连接字符串!
* `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用.
### add-package

4
docs/zh-Hans/Contribution/Localization-Text-Files.md

@ -23,8 +23,8 @@
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json

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

@ -7,7 +7,7 @@
### 源码
你可以在[这里](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo)找到本文引用的示例项目的源代码; 但是为了理解示例项目的源代码,你需要阅读和理解这个文档.
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo)找到本文引用的示例项目的源代码; 但是为了理解示例项目的源代码,你需要阅读和理解这个文档.
## 关于EF Core 代码优先迁移

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)

2
docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md

@ -10,7 +10,7 @@
- [Part II: 创建,编辑,删除书籍](Part-II.md)
- [Part III: 集成测试](Part-III.md)
你可以从[GitHub存储库](https://github.com/abpframework/abp/tree/master/samples/BookStore)访问应用程序的**源代码**.
你可以从[GitHub存储库](https://github.com/abpframework/abp-samples/tree/master/BookStore)访问应用程序的**源代码**.
> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application).

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
);
}
}

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Modularity;
@ -12,5 +13,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago
{
context.Files.AddIfNotContains("/libs/timeago/jquery.timeago.js");
}
public override void ConfigureDynamicResources(BundleConfigurationContext context)
{
var cultureName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
var cultureFileName = $"/libs/timeago/locales/jquery.timeago.{cultureName}.js";
if (context.FileProvider.GetFileInfo(cultureFileName).Exists)
{
context.Files.Add(cultureFileName);
}
}
}
}

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

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js

@ -60,6 +60,8 @@
container: 'body'
});
args.$el.findWithSelf('.timeago').timeago();
enableFormFeatures(args.$el.findWithSelf('form'), true);
initializeScript(args.$el);
@ -82,6 +84,8 @@
container: 'body'
});
$('.timeago').timeago();
$('[data-auto-focus="true"]').first().findWithSelf('input,select').focus();
});

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;
}
}
}

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -199,6 +199,8 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("-o|--output-folder <output-folder> (default: current folder)");
sb.AppendLine("-v|--version <version> (default: latest version)");
sb.AppendLine("-ts|--template-source <template-source> (your local or network abp template source)");
sb.AppendLine("-csf|--create-solution-folder (default: true)");
sb.AppendLine("-cs|--connection-string <connection-string> (your database connection string)");
sb.AppendLine("--tiered (if supported by the template)");
sb.AppendLine("--no-ui (if supported by the template)");
sb.AppendLine("--separate-identity-server (if supported by the template)");
@ -217,6 +219,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp new Acme.BookStore -t module");
sb.AppendLine(" abp new Acme.BookStore -t module --no-ui");
sb.AppendLine(" abp new Acme.BookStore -ts \"D:\\localTemplate\\abp\"");
sb.AppendLine(" abp new Acme.BookStore -csf false");
sb.AppendLine(" abp new Acme.BookStore --local-framework-ref --abp-path \"D:\\github\\abp\"");
sb.AppendLine(" abp new Acme.BookStore --connection-string \"Server=myServerName\\myInstanceName;Database=myDatabase;User Id=myUsername;Password=myPassword\"");
sb.AppendLine("");

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using Volo.Abp.Cli.ProjectBuilding.Files;
@ -109,7 +110,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
var oldNodeIncludeValue = oldNode.Attributes["Include"].Value;
// ReSharper disable once PossibleNullReferenceException : Can not be null because nodes are selected with include attribute filter in previous method
if (oldNodeIncludeValue.Contains(_projectName))
if (oldNodeIncludeValue.Contains(_projectName) && _entries.Any(e=>e.Name.EndsWith($"{oldNodeIncludeValue}.csproj")))
{
continue;
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs

@ -127,11 +127,17 @@ namespace Volo.Abp.Cli.ProjectModification
{
var jestConfigPath = Path.Combine(angularProjectsPath, project, "jest.config.js");
File.Delete(jestConfigPath);
if (File.Exists(jestConfigPath))
{
File.Delete(jestConfigPath);
}
var testPath = Path.Combine(angularProjectsPath, project, "src", "test.ts");
File.Delete(testPath);
if (File.Exists(testPath))
{
File.Delete(testPath);
}
}
}

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";
}

29
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs

@ -87,24 +87,39 @@ namespace Volo.Abp.Cli.ProjectModification
await SolutionFileModifier.AddModuleToSolutionFileAsync(module, solutionFile);
await NugetPackageToLocalReferenceConverter.Convert(module, solutionFile);
await HandleAngularProject(module, solutionFile);
await HandleAngularProject(modulesFolderInSolution, solutionFile);
}
ModifyDbContext(projectFiles, module, startupProject, skipDbMigrations);
}
private async Task HandleAngularProject(ModuleWithMastersInfo module, string solutionFilePath)
private async Task HandleAngularProject(string modulesFolderInSolution, string solutionFilePath)
{
var angularPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(solutionFilePath)), "angular");
if (!Directory.Exists(angularPath))
{
DeleteAngularDirectoriesInModulesFolder(modulesFolderInSolution);
return;
}
await AngularModuleSourceCodeAdder.AddAsync(solutionFilePath, angularPath);
}
private static void DeleteAngularDirectoriesInModulesFolder(string modulesFolderInSolution)
{
var moduleFolders = Directory.GetDirectories(modulesFolderInSolution);
foreach (var moduleFolder in moduleFolders)
{
var angDir = Path.Combine(moduleFolder, "angular");
if (Directory.Exists(angDir))
{
Directory.Delete(angDir, true);
}
}
}
private async Task DownloadSourceCodesToSolutionFolder(ModuleWithMastersInfo module, string modulesFolderInSolution, string version = null)
{
var targetModuleFolder = Path.Combine(modulesFolderInSolution, module.Name);
@ -117,7 +132,7 @@ namespace Volo.Abp.Cli.ProjectModification
null
);
await DeleteAppFolderAsync(targetModuleFolder);
await DeleteAppAndDemoFolderAsync(targetModuleFolder);
if (module.MasterModuleInfos == null)
{
@ -130,13 +145,19 @@ namespace Volo.Abp.Cli.ProjectModification
}
}
private async Task DeleteAppFolderAsync(string targetModuleFolder)
private async Task DeleteAppAndDemoFolderAsync(string targetModuleFolder)
{
var appFolder = Path.Combine(targetModuleFolder, "app");
if (Directory.Exists(appFolder))
{
Directory.Delete(appFolder, true);
}
var demoFolder = Path.Combine(targetModuleFolder, "demo");
if (Directory.Exists(demoFolder))
{
Directory.Delete(demoFolder, true);
}
}
private async Task AddNugetAndNpmReferences(ModuleWithMastersInfo module, string[] projectFiles)

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;
}

2
framework/src/Volo.Abp.MailKit/Volo.Abp.MailKit.csproj

@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MailKit" Version="2.5.2" />
<PackageReference Include="MailKit" Version="2.6.0" />
</ItemGroup>
<ItemGroup>

2
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json

@ -45,7 +45,7 @@
"PagerInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
"PagerInfoEmpty": "Showing 0 to 0 of 0 entries",
"PagerInfoFiltered": "(filtered from _MAX_ total entries)",
"NoDataAvailableInDatatable": "No data available in table",
"NoDataAvailableInDatatable": "No data available",
"PagerShowMenuEntries": "Show _MENU_ entries",
"DatatableActionDropdownDefaultText": "Actions",
"ChangePassword": "Change password",

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

9
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/EntityChangeWithUsername.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.AuditLogging
{
public class EntityChangeWithUsername
{
public EntityChange EntityChange { get; set; }
public string UserName { get; set; }
}
}

4
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs

@ -69,5 +69,9 @@ namespace Volo.Abp.AuditLogging
string entityId = null,
string entityTypeFullName = null,
CancellationToken cancellationToken = default);
Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(Guid entityChangeId);
Task<List<EntityChangeWithUsername>> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName);
}
}

25
modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs

@ -182,6 +182,31 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
return totalCount;
}
public virtual async Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(Guid entityChangeId)
{
var auditLog = await DbSet.AsNoTracking().IncludeDetails()
.Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)).FirstAsync();
return new EntityChangeWithUsername()
{
EntityChange = auditLog.EntityChanges.First(x => x.Id == entityChangeId),
UserName = auditLog.UserName
};
}
public virtual async Task<List<EntityChangeWithUsername>> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName)
{
var query = DbContext.Set<EntityChange>()
.AsNoTracking()
.IncludeDetails()
.Where(x => x.EntityId == entityId && x.EntityTypeFullName == entityTypeFullName);
return await (from e in query
join auditLog in DbSet on e.AuditLogId equals auditLog.Id
select new EntityChangeWithUsername() {EntityChange = e, UserName = auditLog.UserName})
.OrderByDescending(x => x.EntityChange.ChangeTime).ToListAsync();
}
protected virtual IQueryable<EntityChange> GetEntityChangeListQuery(
Guid? auditLogId = null,
DateTime? startTime = null,

29
modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs

@ -191,6 +191,35 @@ namespace Volo.Abp.AuditLogging.MongoDB
return count;
}
public virtual async Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(Guid entityChangeId)
{
var auditLog = (await GetMongoQueryable()
.Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId))
.FirstAsync());
return new EntityChangeWithUsername()
{
EntityChange = auditLog.EntityChanges.First(x => x.Id == entityChangeId),
UserName = auditLog.UserName
};
}
public virtual async Task<List<EntityChangeWithUsername>> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName)
{
var auditLogs = await GetMongoQueryable()
.Where(x => x.EntityChanges.Any(y => y.EntityId == entityId && y.EntityTypeFullName == entityTypeFullName))
.As<IMongoQueryable<AuditLog>>()
.OrderByDescending(x => x.ExecutionTime)
.ToListAsync();
var entityChanges = auditLogs.SelectMany(x => x.EntityChanges).ToList();
entityChanges.RemoveAll(x => x.EntityId != entityId || x.EntityTypeFullName != entityTypeFullName);
return entityChanges.Select(x => new EntityChangeWithUsername()
{EntityChange = x, UserName = auditLogs.First(y => y.Id == x.AuditLogId).UserName}).ToList();
}
protected virtual IQueryable<AuditLog> GetEntityChangeListQuery(
Guid? auditLogId = null,
DateTime? startTime = null,

225
modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs

@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.VisualBasic;
using Shouldly;
using Volo.Abp.Auditing;
using Volo.Abp.Guids;
@ -761,5 +762,229 @@ namespace Volo.Abp.AuditLogging
entityChanges.ShouldNotBeNull();
entityChanges.Count.ShouldBe(1);
}
[Fact]
public async Task GetEntityChangesWithUsernameAsync()
{
// Arrange
var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06");
var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06");
var ipAddress = "153.1.7.61";
var firstComment = "first Comment";
var firstUser = "Douglas";
var secondUser = "John Doe";
var entityId = Guid.NewGuid().ToString();
var entityType = "Volo.Abp.AuditLogging.TestEntity";
var log1 = new AuditLogInfo
{
UserId = userId,
ImpersonatorUserId = Guid.NewGuid(),
ImpersonatorTenantId = Guid.NewGuid(),
ExecutionTime = DateTime.Today,
ExecutionDuration = 42,
ClientIpAddress = ipAddress,
ClientName = "MyDesktop",
BrowserInfo = "Chrome",
Comments = new List<string> { firstComment, "Second Comment" },
UserName = firstUser,
EntityChanges = {
new EntityChangeInfo
{
EntityId = entityId,
EntityTypeFullName = entityType,
ChangeType = EntityChangeType.Created,
ChangeTime = DateTime.Now,
PropertyChanges = new List<EntityPropertyChangeInfo>
{
new EntityPropertyChangeInfo
{
PropertyTypeFullName = typeof(string).FullName,
PropertyName = "Name",
NewValue = "New value",
OriginalValue = null
}
}
},
new EntityChangeInfo
{
EntityId = Guid.NewGuid().ToString(),
EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity",
ChangeType = EntityChangeType.Created,
ChangeTime = DateTime.Now,
PropertyChanges = new List<EntityPropertyChangeInfo>
{
new EntityPropertyChangeInfo
{
PropertyTypeFullName = typeof(string).FullName,
PropertyName = "Name",
NewValue = "New value",
OriginalValue = null
}
}
}
}
};
var log2 = new AuditLogInfo
{
UserId = userId2,
ImpersonatorUserId = Guid.NewGuid(),
ImpersonatorTenantId = Guid.NewGuid(),
ExecutionTime = DateTime.Today,
ExecutionDuration = 42,
ClientIpAddress = ipAddress,
ClientName = "MyDesktop",
BrowserInfo = "Chrome",
Comments = new List<string> { firstComment, "Second Comment" },
HttpStatusCode = (int?)HttpStatusCode.Accepted,
UserName = secondUser,
EntityChanges = {
new EntityChangeInfo
{
EntityId = entityId,
EntityTypeFullName = entityType,
ChangeType = EntityChangeType.Updated,
ChangeTime = DateTime.Now,
PropertyChanges = new List<EntityPropertyChangeInfo>
{
new EntityPropertyChangeInfo
{
PropertyTypeFullName = typeof(string).FullName,
PropertyName = "Name",
NewValue = "New value",
OriginalValue = null
}
}
}
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
//Assert
var entityHistory = await AuditLogRepository.GetEntityChangesWithUsernameAsync(entityId, entityType);
entityHistory.Count.ShouldBe(2);
var firstUserChange = entityHistory.First(x => x.UserName == firstUser);
firstUserChange.ShouldNotBeNull();
firstUserChange.EntityChange.ShouldNotBeNull();
firstUserChange.EntityChange.ChangeType.ShouldBe(EntityChangeType.Created);
var secondUserChange = entityHistory.First(x => x.UserName == secondUser);
secondUserChange.ShouldNotBeNull();
secondUserChange.EntityChange.ShouldNotBeNull();
secondUserChange.EntityChange.ChangeType.ShouldBe(EntityChangeType.Updated);
}
[Fact]
public async Task GetEntityChangeWithUsernameAsync()
{
// Arrange
var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06");
var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06");
var ipAddress = "153.1.7.61";
var firstComment = "first Comment";
var log1 = new AuditLogInfo
{
UserId = userId,
ImpersonatorUserId = Guid.NewGuid(),
ImpersonatorTenantId = Guid.NewGuid(),
ExecutionTime = DateTime.Today,
ExecutionDuration = 42,
ClientIpAddress = ipAddress,
ClientName = "MyDesktop",
BrowserInfo = "Chrome",
Comments = new List<string> { firstComment, "Second Comment" },
UserName = "Douglas",
EntityChanges = {
new EntityChangeInfo
{
EntityId = Guid.NewGuid().ToString(),
EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted",
ChangeType = EntityChangeType.Deleted,
ChangeTime = new DateTime(1995, 3, 27),
PropertyChanges = new List<EntityPropertyChangeInfo>
{
new EntityPropertyChangeInfo
{
PropertyTypeFullName = typeof(string).FullName,
PropertyName = "Name",
NewValue = "New value",
OriginalValue = null
}
}
},
new EntityChangeInfo
{
EntityId = Guid.NewGuid().ToString(),
EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created",
ChangeType = EntityChangeType.Created,
ChangeTime = DateTime.Now,
PropertyChanges = new List<EntityPropertyChangeInfo>
{
new EntityPropertyChangeInfo
{
PropertyTypeFullName = typeof(string).FullName,
PropertyName = "Name",
NewValue = "New value",
OriginalValue = null
}
}
}
}
};
var log2 = new AuditLogInfo
{
UserId = userId2,
ImpersonatorUserId = Guid.NewGuid(),
ImpersonatorTenantId = Guid.NewGuid(),
ExecutionTime = DateTime.Today,
ExecutionDuration = 42,
ClientIpAddress = ipAddress,
ClientName = "MyDesktop",
BrowserInfo = "Chrome",
Comments = new List<string> { firstComment, "Second Comment" },
HttpStatusCode = (int?)HttpStatusCode.BadGateway,
UserName = "John Doe",
EntityChanges = {
new EntityChangeInfo
{
EntityId = Guid.NewGuid().ToString(),
EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated",
ChangeType = EntityChangeType.Updated,
ChangeTime = DateTime.Now,
PropertyChanges = new List<EntityPropertyChangeInfo>
{
new EntityPropertyChangeInfo
{
PropertyTypeFullName = typeof(string).FullName,
PropertyName = "Name",
NewValue = "New value",
OriginalValue = null
}
}
}
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync();
var entityHistory =
await AuditLogRepository.GetEntityChangeWithUsernameAsync(entityChanges.First().Id);
entityHistory.EntityChange.ChangeTime.ShouldBe(entityChanges.First().ChangeTime);
entityHistory.UserName.ShouldNotBeNull();
}
}
}

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)

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

@ -70,7 +70,7 @@ namespace Volo.Docs.Documents
input.LanguageCode,
input.Version
);
}
}
public virtual async Task<DocumentWithDetailsDto> GetDefaultAsync(GetDefaultDocumentInput input)
{

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");

52
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts

@ -1,14 +1,14 @@
import { ChangePassword, ConfigState, ABP } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { ChangePassword } from '@abp/ng.core';
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { comparePasswords, Validation, PasswordRules, validatePassword } from '@ngx-validate/core';
import { comparePasswords, Validation } from '@ngx-validate/core';
import { Store } from '@ngxs/store';
import snq from 'snq';
import { finalize } from 'rxjs/operators';
import snq from 'snq';
import { Account } from '../../models/account';
const { minLength, required, maxLength } = Validators;
const { required } = Validators;
const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
@ -36,33 +36,7 @@ export class ChangePasswordComponent
) {}
ngOnInit(): void {
const passwordRules: ABP.Dictionary<string> = this.store.selectSnapshot(
ConfigState.getSettings('Identity.Password'),
);
const passwordRulesArr = [] as PasswordRules;
let requiredLength = 1;
if ((passwordRules['Abp.Identity.Password.RequireDigit'] || '').toLowerCase() === 'true') {
passwordRulesArr.push('number');
}
if ((passwordRules['Abp.Identity.Password.RequireLowercase'] || '').toLowerCase() === 'true') {
passwordRulesArr.push('small');
}
if ((passwordRules['Abp.Identity.Password.RequireUppercase'] || '').toLowerCase() === 'true') {
passwordRulesArr.push('capital');
}
if (
(passwordRules['Abp.Identity.Password.RequireNonAlphanumeric'] || '').toLowerCase() === 'true'
) {
passwordRulesArr.push('special');
}
if (Number.isInteger(+passwordRules['Abp.Identity.Password.RequiredLength'])) {
requiredLength = +passwordRules['Abp.Identity.Password.RequiredLength'];
}
const passwordValidations = getPasswordValidators(this.store);
this.form = this.fb.group(
{
@ -70,23 +44,13 @@ export class ChangePasswordComponent
newPassword: [
'',
{
validators: [
required,
validatePassword(passwordRulesArr),
minLength(requiredLength),
maxLength(128),
],
validators: [required, ...passwordValidations],
},
],
repeatNewPassword: [
'',
{
validators: [
required,
validatePassword(passwordRulesArr),
minLength(requiredLength),
maxLength(128),
],
validators: [required, ...passwordValidations],
},
],
},

46
npm/ng-packs/packages/account/src/lib/components/register/register.component.ts

@ -1,18 +1,15 @@
import { ConfigState, GetAppConfiguration, ABP, SessionState, AuthService } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { AuthService, ConfigState } from '@abp/ng.core';
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Navigate } from '@ngxs/router-plugin';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { from, throwError } from 'rxjs';
import { catchError, finalize, switchMap, take, tap } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { catchError, finalize, switchMap } from 'rxjs/operators';
import snq from 'snq';
import { RegisterRequest } from '../../models';
import { AccountService } from '../../services/account.service';
import { PasswordRules, validatePassword } from '@ngx-validate/core';
import { HttpHeaders } from '@angular/common/http';
const { maxLength, minLength, required, email } = Validators;
const { maxLength, required, email } = Validators;
@Component({
selector: 'abp-register',
@ -53,40 +50,9 @@ export class RegisterComponent implements OnInit {
return;
}
const passwordRules: ABP.Dictionary<string> = this.store.selectSnapshot(
ConfigState.getSettings('Identity.Password'),
);
const passwordRulesArr = [] as PasswordRules;
let requiredLength = 1;
if ((passwordRules['Abp.Identity.Password.RequireDigit'] || '').toLowerCase() === 'true') {
passwordRulesArr.push('number');
}
if ((passwordRules['Abp.Identity.Password.RequireLowercase'] || '').toLowerCase() === 'true') {
passwordRulesArr.push('small');
}
if ((passwordRules['Abp.Identity.Password.RequireUppercase'] || '').toLowerCase() === 'true') {
passwordRulesArr.push('capital');
}
if (
(passwordRules['Abp.Identity.Password.RequireNonAlphanumeric'] || '').toLowerCase() === 'true'
) {
passwordRulesArr.push('special');
}
if (Number.isInteger(+passwordRules['Abp.Identity.Password.RequiredLength'])) {
requiredLength = +passwordRules['Abp.Identity.Password.RequiredLength'];
}
this.form = this.fb.group({
username: ['', [required, maxLength(255)]],
password: [
'',
[required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)],
],
password: ['', [required, ...getPasswordValidators(this.store)]],
email: ['', [required, email]],
});
}

43
npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts

@ -1,5 +1,5 @@
import { ABP, ConfigState } from '@abp/ng.core';
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
import { ABP } from '@abp/ng.core';
import { Confirmation, ConfirmationService, getPasswordValidators } from '@abp/ng.theme.shared';
import { Component, OnInit, TemplateRef, TrackByFunction, ViewChild } from '@angular/core';
import {
AbstractControl,
@ -9,7 +9,6 @@ import {
FormGroup,
Validators,
} from '@angular/forms';
import { PasswordRules, validatePassword } from '@ngx-validate/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, pluck, switchMap, take } from 'rxjs/operators';
@ -23,8 +22,8 @@ import {
UpdateUser,
} from '../../actions/identity.actions';
import { Identity } from '../../models/identity';
import { IdentityState } from '../../states/identity.state';
import { IdentityService } from '../../services/identity.service';
import { IdentityState } from '../../states/identity.state';
@Component({
selector: 'abp-users',
templateUrl: './users.component.html',
@ -63,10 +62,6 @@ export class UsersComponent implements OnInit {
sortKey = '';
passwordRulesArr = [] as PasswordRules;
requiredPasswordLength = 1;
trackByFn: TrackByFunction<AbstractControl> = (index, item) => Object.keys(item)[0] || index;
onVisiblePermissionChange = event => {
@ -86,32 +81,6 @@ export class UsersComponent implements OnInit {
ngOnInit() {
this.get();
const passwordRules: ABP.Dictionary<string> = this.store.selectSnapshot(
ConfigState.getSettings('Identity.Password'),
);
if ((passwordRules['Abp.Identity.Password.RequireDigit'] || '').toLowerCase() === 'true') {
this.passwordRulesArr.push('number');
}
if ((passwordRules['Abp.Identity.Password.RequireLowercase'] || '').toLowerCase() === 'true') {
this.passwordRulesArr.push('small');
}
if ((passwordRules['Abp.Identity.Password.RequireUppercase'] || '').toLowerCase() === 'true') {
this.passwordRulesArr.push('capital');
}
if (
(passwordRules['Abp.Identity.Password.RequireNonAlphanumeric'] || '').toLowerCase() === 'true'
) {
this.passwordRulesArr.push('special');
}
if (Number.isInteger(+passwordRules['Abp.Identity.Password.RequiredLength'])) {
this.requiredPasswordLength = +passwordRules['Abp.Identity.Password.RequiredLength'];
}
}
onSearch(value: string) {
@ -146,11 +115,7 @@ export class UsersComponent implements OnInit {
),
});
const passwordValidators = [
validatePassword(this.passwordRulesArr),
Validators.minLength(this.requiredPasswordLength),
Validators.maxLength(128),
];
const passwordValidators = getPasswordValidators(this.store);
this.form.addControl('password', new FormControl('', [...passwordValidators]));

4
npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts

@ -1,5 +1,5 @@
import { ABP } from '@abp/ng.core';
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
import { ConfirmationService, Confirmation, getPasswordValidators } from '@abp/ng.theme.shared';
import { Component, OnInit, TemplateRef, ViewChild, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
@ -122,7 +122,7 @@ export class TenantsComponent implements OnInit {
const tenantForm = this.fb.group({
name: [this.selected.name || '', [Validators.required, Validators.maxLength(256)]],
adminEmailAddress: [null, [Validators.required, Validators.maxLength(256), Validators.email]],
adminPassword: [null, [Validators.required]],
adminPassword: [null, [Validators.required, ...getPasswordValidators(this.store)]],
});
if (this.hasSelectedTenant) {

21
npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.html

@ -1,5 +1,16 @@
<div #container id="abp-http-error-container" class="error" [style.backgroundColor]="backgroundColor">
<button *ngIf="!hideCloseIcon" id="abp-close-button" type="button" class="close mr-2" (click)="destroy()">
<div
#container
id="abp-http-error-container"
class="error"
[style.backgroundColor]="backgroundColor"
>
<button
*ngIf="!hideCloseIcon"
id="abp-close-button"
type="button"
class="close mr-2"
(click)="destroy()"
>
<span aria-hidden="true">&times;</span>
</button>
@ -11,7 +22,11 @@
{{ details | abpLocalization }}
</div>
<div class="error-actions">
<a (click)="destroy()" routerLink="/" class="btn btn-primary btn-md mt-2"
<a
*ngIf="isHomeShow"
(click)="destroy()"
routerLink="/"
class="btn btn-primary btn-md mt-2"
><span class="glyphicon glyphicon-home"></span>
{{ { key: '::Menu:Home', defaultValue: 'Home' } | abpLocalization }}
</a>

13
npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts

@ -42,6 +42,8 @@ export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnIn
backgroundColor: string;
isHomeShow = true;
@ViewChild('container', { static: false })
containerRef: ElementRef<HTMLDivElement>;
@ -51,16 +53,21 @@ export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnIn
ngOnInit() {
this.backgroundColor =
snq(() => window.getComputedStyle(document.body).getPropertyValue('background-color')) || '#fff';
snq(() => window.getComputedStyle(document.body).getPropertyValue('background-color')) ||
'#fff';
}
ngAfterViewInit() {
if (this.customComponent) {
const customComponentRef = this.cfRes.resolveComponentFactory(this.customComponent).create(this.injector);
const customComponentRef = this.cfRes
.resolveComponentFactory(this.customComponent)
.create(this.injector);
customComponentRef.instance.errorStatus = this.status;
customComponentRef.instance.destroy$ = this.destroy$;
this.appRef.attachView(customComponentRef.hostView);
this.containerRef.nativeElement.appendChild((customComponentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]);
this.containerRef.nativeElement.appendChild(
(customComponentRef.hostView as EmbeddedViewRef<any>).rootNodes[0],
);
customComponentRef.changeDetectorRef.detectChanges();
}

26
npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html

@ -62,22 +62,20 @@
</ng-template>
<ng-template #body>
<tbody class="ui-table-tbody">
<ng-container *ngIf="value && value.length; else emptyTemplate">
<ng-template
#bodyTemplateWrapper
*ngFor="let val of slicedValue; let index = index; trackBy: trackByFn"
[ngTemplateOutlet]="bodyTemplate"
[ngTemplateOutletContext]="{ $implicit: val, rowIndex: index }"
></ng-template>
</ng-container>
<tbody class="ui-table-tbody" *ngIf="value && value.length; else emptyTemplate">
<ng-template
#bodyTemplateWrapper
*ngFor="let val of slicedValue; let index = index; trackBy: trackByFn"
[ngTemplateOutlet]="bodyTemplate"
[ngTemplateOutletContext]="{ $implicit: val, rowIndex: index }"
></ng-template>
</tbody>
</ng-template>
<ng-template #emptyTemplate>
<tr class="empty-row" #emptyRow>
<div class="empty-row-content" [style.width.px]="emptyRow.offsetWidth">
{{ emptyMessage | abpLocalization }}
</div>
</tr>
<caption class="ui-table-empty">
{{
emptyMessage | abpLocalization
}}
</caption>
</ng-template>

10
npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.scss

@ -6,13 +6,11 @@
filter: brightness(90%);
}
.ui-table-tbody > tr.empty-row:hover {
filter: none;
}
.ui-table-tbody > tr.empty-row > div.empty-row-content {
padding: 10px;
.ui-table-empty {
padding: 20px 0;
text-align: center;
border: 1px solid #e0e0e0;
border-top-width: 0;
}
.ui-table-caption,

2
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -138,6 +138,8 @@ export class ErrorHandler {
key: 'AbpAccount::DefaultErrorMessage',
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
},
details: err.message,
isHomeShow: false,
});
}
break;

7
npm/ng-packs/packages/theme-shared/src/lib/tests/table.component.spec.ts

@ -1,8 +1,7 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { PaginationComponent, TableComponent } from '../components';
import { Pipe, PipeTransform } from '@angular/core';
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { PaginationComponent, TableComponent } from '../components';
@Pipe({
name: 'abpLocalization',
@ -40,7 +39,7 @@ describe('TableComponent', () => {
});
it('should display the empty message', () => {
expect(spectator.query('tr.empty-row>div')).toHaveText(
expect(spectator.query('caption.ui-table-empty')).toHaveText(
'AbpAccount::NoDataAvailableInDatatable',
);
});

52
npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts

@ -0,0 +1,52 @@
import { ConfigState } from '@abp/ng.core';
import { Component } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { NgxValidateCoreModule, validatePassword } from '@ngx-validate/core';
import { NgxsModule, Store } from '@ngxs/store';
import { HttpClient } from '@angular/common/http';
import { getPasswordValidators } from '../utils';
import { Validators } from '@angular/forms';
@Component({ template: '', selector: 'abp-dummy' })
class DummyComponent {}
describe('ValidationUtils', () => {
let spectator: Spectator<DummyComponent>;
const createComponent = createComponentFactory({
component: DummyComponent,
imports: [NgxsModule.forRoot([ConfigState]), NgxValidateCoreModule.forRoot()],
mocks: [HttpClient],
});
beforeEach(() => (spectator = createComponent()));
describe('#getPasswordValidators', () => {
it('should return password valdiators', () => {
const store = spectator.get(Store);
store.reset({
ConfigState: {
setting: {
values: {
'Abp.Identity.Password.RequiredLength': '6',
'Abp.Identity.Password.RequiredUniqueChars': '1',
'Abp.Identity.Password.RequireNonAlphanumeric': 'True',
'Abp.Identity.Password.RequireLowercase': 'True',
'Abp.Identity.Password.RequireUppercase': 'True',
'Abp.Identity.Password.RequireDigit': 'True',
},
},
},
});
const validators = getPasswordValidators(store);
const expectedValidators = [
validatePassword(['number', 'small', 'capital', 'special']),
Validators.minLength(6),
Validators.maxLength(128),
];
validators.forEach((validator, index) => {
expect(validator.toString()).toBe(expectedValidators[index].toString());
});
});
});
});

1
npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts

@ -1,2 +1,3 @@
export * from './widget-utils';
export * from './date-parser-formatter';
export * from './validation-utils';

45
npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts

@ -0,0 +1,45 @@
import { Store } from '@ngxs/store';
import { ABP, ConfigState } from '@abp/ng.core';
import { PasswordRules, validatePassword } from '@ngx-validate/core';
import { Validators, ValidatorFn } from '@angular/forms';
const { minLength, maxLength } = Validators;
export function getPasswordValidators(store: Store): ValidatorFn[] {
const getRule = getRuleFn(store);
const passwordRulesArr = [] as PasswordRules;
let requiredLength = 1;
if (getRule('RequireDigit') === 'true') {
passwordRulesArr.push('number');
}
if (getRule('RequireLowercase') === 'true') {
passwordRulesArr.push('small');
}
if (getRule('RequireUppercase') === 'true') {
passwordRulesArr.push('capital');
}
if (getRule('RequireNonAlphanumeric') === 'true') {
passwordRulesArr.push('special');
}
if (Number.isInteger(+getRule('RequiredLength'))) {
requiredLength = +getRule('RequiredLength');
}
return [validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)];
}
function getRuleFn(store: Store) {
return (key: string) => {
const passwordRules: ABP.Dictionary<string> = store.selectSnapshot(
ConfigState.getSettings('Identity.Password'),
);
return (passwordRules[`Abp.Identity.Password.${key}`] || '').toLowerCase();
};
}

25
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication.sln

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29326.143
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicAspNetCoreApplication", "BasicAspNetCoreApplication\BasicAspNetCoreApplication.csproj", "{069D79DF-2340-4101-9B6E-32FEEC6893A8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{069D79DF-2340-4101-9B6E-32FEEC6893A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{069D79DF-2340-4101-9B6E-32FEEC6893A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{069D79DF-2340-4101-9B6E-32FEEC6893A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{069D79DF-2340-4101-9B6E-32FEEC6893A8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9AC9422D-35CC-4A8E-9F5B-D695565E9B45}
EndGlobalSection
EndGlobal

33
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/AppModule.cs

@ -1,33 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
namespace BasicAspNetCoreApplication
{
[DependsOn(typeof(AbpAspNetCoreMvcModule))]
[DependsOn(typeof(AbpAutofacModule))] //Add dependency to ABP Autofac module
public class AppModule : AbpModule
{
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseMvcWithDefaultRouteAndArea();
}
}
}

16
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/BasicAspNetCoreApplication.csproj

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
</ItemGroup>
</Project>

26
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Error.cshtml

@ -1,26 +0,0 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

31
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Error.cshtml.cs

@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace BasicAspNetCoreApplication.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

10
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Index.cshtml

@ -1,10 +0,0 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

25
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Index.cshtml.cs

@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace BasicAspNetCoreApplication.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}

8
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Privacy.cshtml

@ -1,8 +0,0 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

24
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Privacy.cshtml.cs

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace BasicAspNetCoreApplication.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}

50
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Shared/_Layout.cshtml

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - BasicAspNetCoreApplication</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">BasicAspNetCoreApplication</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2019 - BasicAspNetCoreApplication - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>

2
samples/BasicAspNetCoreApplication/BasicAspNetCoreApplication/Pages/Shared/_ValidationScriptsPartial.cshtml

@ -1,2 +0,0 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

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

Loading…
Cancel
Save