Browse Source

combine

pull/5292/head
向洪林 6 years ago
parent
commit
fb166165c9
  1. 39
      .github/workflows/angular.yml
  2. 2
      .github/workflows/build-and-test.yml
  3. 1
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  4. 1
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  5. 2
      common.props
  6. 2
      common.test.props
  7. 18
      docs/en/Authorization.md
  8. 92
      docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md
  9. BIN
      docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/abp-generate-proxy-output.png
  10. 18
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md
  11. 447
      docs/en/Features.md
  12. 16
      docs/en/Index.md
  13. 2
      docs/en/Modules/Feature-Management.md
  14. 2
      docs/en/Modules/Permission-Management.md
  15. 3
      docs/en/Multi-Tenancy.md
  16. 77
      docs/en/UI/Angular/Component-Replacement.md
  17. 2
      docs/en/UI/Angular/Environment.md
  18. 3
      docs/en/UI/Angular/Features.md
  19. 12
      docs/en/UI/Angular/Permission-Management-Component-Replacement.md
  20. 2
      docs/en/UI/Angular/Service-Proxies.md
  21. 4
      docs/en/docs-nav.json
  22. BIN
      docs/en/images/features-action.png
  23. BIN
      docs/en/images/features-modal.png
  24. 9
      docs/zh-Hans/Authentication/Social-External-Logins.md
  25. 189
      docs/zh-Hans/CLI.md
  26. 28
      docs/zh-Hans/Contribution/Index.md
  27. 5
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  28. 6
      docs/zh-Hans/Entities.md
  29. 5
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  30. 5
      docs/zh-Hans/Entity-Framework-Core.md
  31. 3
      docs/zh-Hans/Getting-Started-Console-Application.md
  32. 3
      docs/zh-Hans/Global-Features.md
  33. 9
      docs/zh-Hans/How-To/Index.md
  34. 2
      docs/zh-Hans/Local-Event-Bus.md
  35. 1
      docs/zh-Hans/MailKit.md
  36. 2
      docs/zh-Hans/Module-Development-Basics.md
  37. 3
      docs/zh-Hans/Moodule-Entity-Extensions.md
  38. 32
      docs/zh-Hans/Nightly-Builds.md
  39. 1
      docs/zh-Hans/Previews.md
  40. 6
      docs/zh-Hans/Repositories.md
  41. 27
      docs/zh-Hans/Samples/Index.md
  42. 14
      docs/zh-Hans/Startup-Templates/Application.md
  43. 1
      docs/zh-Hans/UI/Angular/Environment.md
  44. 2
      docs/zh-Hans/UI/Angular/Migration-Guide-v3.md
  45. 1
      docs/zh-Hans/UI/Angular/Multi-Tenancy.md
  46. 4
      docs/zh-Hans/UI/Angular/Permission-Management.md
  47. 8
      docs/zh-Hans/UI/Angular/Toaster-Service.md
  48. 2
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  49. 1
      docs/zh-Hans/Upgrading.md
  50. 105
      docs/zh-Hans/Virtual-File-System.md
  51. 52
      docs/zh-Hans/docs-nav.json
  52. BIN
      docs/zh-Hans/images/replace-email-layout.png
  53. BIN
      docs/zh-Hans/images/solution-files-non-mvc.png
  54. 86
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs
  55. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  56. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs
  57. 46
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs
  58. 7
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs
  59. 64
      framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs
  60. 9
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs
  61. 4
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs
  62. 8
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpRemoteCallException.cs
  63. 6
      framework/src/Volo.Abp.Http/Volo/Abp/Http/RemoteServiceErrorInfo.cs
  64. 36
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs
  65. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs
  66. 46
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js
  67. 4
      framework/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj
  68. 22
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs
  69. 2
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs
  70. 10
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs
  71. 11
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs
  72. 10
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/Localization/HttpClientTestResource.cs
  73. 6
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/Localization/en.json
  74. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs
  75. 6
      global.json
  76. 4
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json
  77. 4
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json
  78. 3
      modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs
  79. 31
      modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs
  80. 59
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs
  81. 17
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml
  82. 31
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.js
  83. 56
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs
  84. 39
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml
  85. 19
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js
  86. 73
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml
  87. 72
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs
  88. 47
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js
  89. 1
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs
  90. 48
      modules/account/src/Volo.Abp.Account.Web/ProfileManagement/AccountProfileManagementPageContributor.cs
  91. 9
      modules/account/src/Volo.Abp.Account.Web/ProfileManagement/IProfileManagementPageContributor.cs
  92. 19
      modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageCreationContext.cs
  93. 39
      modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageGroup.cs
  94. 14
      modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageOptions.cs
  95. 2
      modules/cms-kit/common.props
  96. 7
      modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs
  97. 3
      modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj
  98. 4
      modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs
  99. 1
      modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj
  100. 1
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs

39
.github/workflows/angular.yml

@ -9,8 +9,41 @@ jobs:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
- uses: actions/cache@v2
with:
node-version: '12.x'
- run: yarn && yarn ci
path: 'npm/ng-packs/node_modules'
key: ${{ runner.os }}-${{ hashFiles('npm/ng-packs/yarn.lock') }}
- uses: actions/cache@v2
with:
path: 'templates/app/angular/node_modules'
key: ${{ runner.os }}-${{ hashFiles('templates/app/angular/yarn.lock') }}
- name: Install packages
run: yarn install
working-directory: npm/ng-packs
- name: Run lint
run: yarn ng lint
working-directory: npm/ng-packs
- name: Run prepare workspace
run: yarn prepare:workspace
working-directory: npm/ng-packs
- name: Run test
run: yarn ci:test
working-directory: npm/ng-packs
- name: Build dev-app
run: yarn build --prod
working-directory: npm/ng-packs
- name: Install packages of app template
run: yarn install
working-directory: templates/app/angular
- name: Build app template
run: yarn build --prod
working-directory: templates/app/angular

2
.github/workflows/build-and-test.yml

@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
with:
dotnet-version: 3.1.100
dotnet-version: 3.1.102
- name: Build All
run: .\build-all.ps1

1
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -26,6 +26,7 @@
"ContributionGuide": "Contribution Guide",
"Blog": "Blog",
"Commercial": "Commercial",
"MyAccount": "My account",
"SeeDocuments": "See Documents"
}
}

1
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -78,7 +78,6 @@
"Biography": "Biography",
"HasNoPublishedArticlesYet": "has no published articles yet",
"Author": "Author",
"MyAccount": "My account",
"LatestGithubAnnouncements": "Latest Github Announcements",
"SeeAllAnnouncements": "See All Announcements",
"LatestBlogPost": "Latest Blog Post",

2
common.props

@ -2,7 +2,7 @@
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>3.2.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>

2
common.test.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>

18
docs/en/Authorization.md

@ -80,6 +80,8 @@ namespace Acme.BookStore.Permissions
> ABP automatically discovers this class. No additional configuration required!
> You typically define this class inside the `Application.Contracts` project of your [application](Startup-Templates/Application.md). The startup template already comes with an empty class named *YourProjectNamePermissionDefinitionProvider* that you can start with.
In the `Define` method, you first need to add a **permission group** or get an existing group then add **permissions** to this group.
When you define a permission, it becomes usable in the ASP.NET Core authorization system as a **policy** name. It also becomes visible in the UI. See permissions dialog for a role:
@ -276,14 +278,24 @@ public async Task CreateAsync(CreateAuthorDto input)
> Tip: Prefer to use the `Authorize` attribute wherever possible, since it is declarative & simple. Use `IAuthorizationService` if you need to conditionally check a permission and run a business code based on the permission check.
### Check a Permission in JavaScript
## Check a Permission in JavaScript
You may need to check a policy/permission on the client side.
### MVC UI
You may need to check a policy/permission on the client side. For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API. Example:
For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API.
**Example: Check if a given permission has been granted for the current user**
```js
abp.auth.isGranted('MyPermissionName');
```
### Angular UI
See the [permission management document](UI/Angular/Permission-Management.md) for the Angular UI.
## Permission Management
Permission management is normally done by an admin user using the permission management modal:
@ -374,7 +386,7 @@ Configure<AbpPermissionOptions>(options =>
### Permission Store
`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. Permission management module implements it. See the [permission management module documentation](Modules/Permission-Management.md) for more information
`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. The Permission Management module implements it and pre-installed in the application startup template. See the [permission management module documentation](Modules/Permission-Management.md) for more information
### AlwaysAllowAuthorizationService

92
docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md

@ -1,8 +1,8 @@
# Introducing the Angular Service Proxy Generation
Angular Service Proxy System **generates TypeScript services and models** to consume your backend HTTP APIs developed using the ABP Framework. So, you **don't manually create** models for your server side DTOs and perform raw HTTP calls to the server.
ABP Angular Service Proxy System **generates TypeScript services and models** to consume your backend HTTP APIs developed using the ABP Framework. So, you **don't manually create** models for your server side DTOs and perform raw HTTP calls to the server.
ABP Framework has introduced the **new** Angular Service Proxy Generation system with the version 3.1. While this feature was available since the [v2.3](https://blog.abp.io/abp/ABP-Framework-v2_3_0-Has-Been-Released), it was not well covering some scenarios, like inheritance and generic types and had some known problems. **With the v3.1, we've re-written** it using the [Angular Schematics](https://angular.io/guide/schematics) system. Now, it is much more stable and feature rich.
ABP Framework has introduced the **new** Angular Service Proxy Generation system with the **version 3.1**. While this feature was available since the [v2.3](https://blog.abp.io/abp/ABP-Framework-v2_3_0-Has-Been-Released), it was not well covering some scenarios, like inheritance and generic types and had some known problems. **With the v3.1, we've re-written** it using the [Angular Schematics](https://angular.io/guide/schematics) system. Now, it is much more stable and feature rich.
This post introduces the service proxy generation system and highlights some important features.
@ -47,6 +47,15 @@ apis: {
`Acme.BookStore` should be replaced by the root namespace of your .NET project. This ensures to not create unnecessary nested folders while creating the service proxy code. This value is `AngularProxyDemo` for the example solution explained below.
* Finally, add the following paths to the `tsconfig.base.json` to have a shortcut while importing proxies:
```json
"paths": {
"@proxy": ["src/app/proxy/index.ts"],
"@proxy/*": ["src/app/proxy/*"]
}
```
## Basic Usage
### Project Creation
@ -139,21 +148,23 @@ abp generate-proxy
It should produce an output like the following:
````bash
CREATE src/app/shared/models/books/index.ts (142 bytes)
CREATE src/app/shared/services/books/book.service.ts (437 bytes)
...
CREATE src/app/proxy/books/book.service.ts (446 bytes)
CREATE src/app/proxy/books/models.ts (148 bytes)
CREATE src/app/proxy/books/index.ts (57 bytes)
CREATE src/app/proxy/index.ts (33 bytes)
````
> `generate-proxy` command can take some some optional parameters for advanced scenarios (like [modular development](https://docs.abp.io/en/abp/latest/Module-Development-Basics)). You can take a look at the [documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies).
It basically creates two files;
#### The Generated Code
src/app/shared/services/books/**book.service.ts**: This is the service that can be injected and used to get the list of books;
`src/app/proxy/books/book.service.ts`: This is the service that can be injected and used to get the list of books;
````typescript
````js
import type { BookDto } from './models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { BookDto } from '../../models/book';
@Injectable({
providedIn: 'root',
@ -170,12 +181,11 @@ export class BookService {
constructor(private restService: RestService) {}
}
````
src/app/shared/models/books/**index.ts**: This file contains the modal classes corresponding to the DTOs defined in the server side;
`src/app/proxy/books/models.ts`: This file contains the modal classes corresponding to the DTOs defined in the server side;
````typescript
````js
import type { EntityDto } from '@abp/ng.core';
export interface BookDto extends EntityDto<string> {
@ -184,7 +194,21 @@ export interface BookDto extends EntityDto<string> {
}
````
You can now inject the `BookService` into any Angular component and use the `getList()` method to get the list of books.
> There are a few more files have been generated to help you import the types easier.
#### How to Import
You can now import the `BookService` into any Angular component and use the `getList()` method to get the list of books.
````js
import { BookService, BookDto } from '../proxy/books';
````
You can also use the `@proxy` as a shortcut of the proxy folder:
````js
import { BookService, BookDto } from '@proxy/books';
````
### About the Generated Code
@ -235,27 +259,20 @@ namespace AngularProxyDemo.Books
}
```
Let's re-run the `generate-proxy` command to see the result:
````
abp generate-proxy
````
The output of this command will be like the following:
Let's re-run the `generate-proxy` command:
````bash
UPDATE src/app/shared/services/books/book.service.ts (660 bytes)
UPDATE src/app/shared/models/books/index.ts (217 bytes)
abp generate-proxy
````
It tells us two files have been updated. Let's see the changes;
This command will re-generate the proxies by updating some files. Let's see some of the changes;
**book.service.ts**
````typescript
````js
import type { BookDto, BookUpdateDto } from './models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { BookDto, BookUpdateDto } from '../../models/books';
@Injectable({
providedIn: 'root',
@ -284,7 +301,7 @@ export class BookService {
`update` function has been added to the `BookService` that gets an `id` and a `BookUpdateDto` as the parameters.
**index.ts**
**models.ts**
````typescript
import type { EntityDto } from '@abp/ng.core';
@ -358,14 +375,14 @@ namespace AngularProxyDemo.Orders
}
````
When I run the `abp generate-proxy` command again, I see two new files have been created.
When I run the `abp generate-proxy` command again, I see there are some created and updated files. Let's see some important ones;
src/app/shared/services/orders/**order.service.ts**
`src/app/proxy/orders/order.service.ts`
````typescript
````js
import type { OrderCreateDto } from './models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { OrderCreateDto } from '../../models/orders';
@Injectable({
providedIn: 'root',
@ -385,16 +402,19 @@ export class OrderService {
}
````
src/app/shared/models/orders/**index.ts**
`src/app/proxy/orders/models.ts`
````typescript
import type { GenericDetailDto } from '../../models/orders';
export interface GenericDetailDto<TCount> {
productId: string;
count: TCount;
}
export interface OrderCreateDto {
customerId: string;
creationTime: string;
details: OrderDetailDto[];
extraProperties: string | object;
extraProperties: Record<string, object>;
}
export interface OrderDetailDto extends GenericDetailDto<number> {
@ -402,4 +422,10 @@ export interface OrderDetailDto extends GenericDetailDto<number> {
}
````
NOTE: 3.1.0-rc2 was generating the code above, which is wrong. It will be fixed in the next RC versions.
## Conclusion
`abp generate-proxy` is a very handy command that creates all the necessary code to consume your ABP based backend HTTP APIs. It generates a clean code that is well aligned to the backend services and benefits from the power of TypeScript (by using generics, inheritance...).
## The Documentation
See [the documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies) for details of the Angular Service Proxy Generation.

BIN
docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/abp-generate-proxy-output.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

18
docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md

@ -24,7 +24,7 @@ Then add a string property called `Title`, as an example property.
ABP Suite needs a DTO for the target entity (user, in this case) in order to define a navigation property.
To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`.
To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`.
![create-appuserdto](create-appuserdto.jpg)
@ -40,13 +40,13 @@ CreateMap<AppUser, AppUserDto>().Ignore(x => x.ExtraProperties);
### Define the Navigation Property
Get back to ABP Suite, open the **Navigation Properties** tab of the ABP Suite, click the **Add Navigation Property** button. Browse `AppUser.cs` in `*.Domain\Users` folder. Then choose the `Name` item as display property. Browse `AppUserDto.cs` in `*.Contracts\Users` folder. Choose `Users` from Collection Names dropdown.
Get back to ABP Suite, open the **Navigation Properties** tab of the ABP Suite, click the **Add Navigation Property** button. Browse `AppUser.cs` in `*.Domain\Users` folder. Then choose the `Name` item as display property. Browse `AppUserDto.cs` in `*.Contracts\Users` folder. Choose `Users` from Collection Names dropdown.
![add-user-navigation](add-user-navigation.jpg)
### Generate the Code!
That's it! Click **Save and generate** button to create your page. You'll see the following page if everything goes well.
That's it! Click **Save and generate** button to create your page. You'll see the following page if everything goes well.
![final-page](final-page.jpg)
@ -54,7 +54,7 @@ This is the new page that has been created by the ABP Suite. It can perform the
**Picking Users from Look Up Table**
We used dropdown element to select a user from the user list. If you have a lot of users, then it's good to pick a user from a look up table. A look up table is a modal window that lets you filter data and pick one. To do this, get back to Suite and click **Edit** button of user navigation which is set as `AppUserId` name. Choose "Modal" from the "UI Pick Type" field. Then click **Save and generate** button to recreate your page.
We used dropdown element to select a user from the user list. If you have a lot of users, then it's good to pick a user from a look up table. A look up table is a modal window that lets you filter data and pick one. To do this, get back to Suite and click **Edit** button of user navigation which is set as `AppUserId` name. Choose "Modal" from the "UI Pick Type" field. Then click **Save and generate** button to recreate your page.
![ui-pick-type-modal](ui-pick-type-modal.jpg)
@ -64,18 +64,18 @@ After successful code generation, you'll see the the user can be picked from use
## About the ABP Commercial RC
This example has been implemented with **ABP Commercial 3.1.0-rc.3**. This is a RC version. If you want to install the CLI and Suite RC version follow the next steps:
This example has been implemented with **ABP Commercial 3.1.0**. If you have not installed the ABP CLI and ABP Suite, follow the next steps:
1- Uninstall the current version of the CLI and install the specific RC version:
1- Uninstall the current version of the CLI and install:
```bash
dotnet tool uninstall --global Volo.Abp.Cli && dotnet tool install --global Volo.Abp.Cli --version 3.1.0-rc.3
dotnet tool install --global Volo.Abp.Cli --version 3.1.0
```
2- Uninstall the current version of the Suite and install the specific RC version:
2- Uninstall the current version of the Suite and install:
```bash
dotnet tool uninstall --global Volo.Abp.Suite && dotnet tool install -g Volo.Abp.Suite --version 3.1.0-rc.3 --add-source https://nuget.abp.io/<YOUR-API-KEY>/v3/index.json
dotnet tool uninstall --global Volo.Abp.Suite && dotnet tool install -g Volo.Abp.Suite --version 3.1.0 --add-source https://nuget.abp.io/<YOUR-API-KEY>/v3/index.json
```
Don't forget to replace the `<YOUR-API-KEY>` with your own key!

447
docs/en/Features.md

@ -1,3 +1,448 @@
# Features
TODO
ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**.
The runtime value for a feature is generally a `boolean` value, like `true` (enabled) or `false` (disabled). However, you can get/set **any kind** of value for feature.
Feature system was originally designed to control the tenant features in a **[multi-tenant](Multi-Tenancy.md)** application. However, it is **extensible** and capable of determining the features by any condition.
> The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md).
## Checking for the Features
Before explaining to define features, let's see how to check a feature value in your application code.
### RequiresFeature Attribute
`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is `true` (enabled) or not. It is a useful shortcut for the `boolean` features.
**Example: Check if the "PDF Reporting" feature enabled**
```csharp
public class ReportingAppService : ApplicationService, IReportingAppService
{
[RequiresFeature("MyApp.PdfReporting")]
public async Task<PdfReportResultDto> GetPdfReportAsync()
{
//TODO...
}
}
```
* `RequiresFeature(...)` simply gets a feature name to check if it is enabled or not. If not enabled, an authorization [exception](Exception-Handling.md) is thrown and a proper response is returned to the client side.
* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the methods of that class require the given feature.
* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if any of the features enabled. Use `RequiresAll` option, like `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to check all of the features to be enabled.
* Multiple usage of `[RequiresFeature]` attribute is supported for a method or class. ABP check checks all of them in that case.
> Feature name can be any arbitrary string. It should be unique for a feature.
#### About the Interception
ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class (application services, controllers...) that is injected from the [dependency injection](Dependency-Injection.md).
However, there are **some rules should be followed** in order to make it working;
* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work.
* Only `async` methods (methods returning a `Task` or `Task<T>`) are intercepted.
> There is an exception for the **controller and razor page methods**. They **don't require** the following the rules above, since ABP Framework uses the action/page filters to implement the feature checking in this case.
### IFeatureChecker Service
`IFeatureChecker` allows to check a feature in your application code.
#### IsEnabledAsync
Returns `true` if the given feature is enabled. So, you can conditionally execute your business flow.
**Example: Check if the "PDF Reporting" feature enabled**
```csharp
public class ReportingAppService : ApplicationService, IReportingAppService
{
private readonly IFeatureChecker _featureChecker;
public ReportingAppService(IFeatureChecker featureChecker)
{
_featureChecker = featureChecker;
}
public async Task<PdfReportResultDto> GetPdfReportAsync()
{
if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting"))
{
//TODO...
}
else
{
//TODO...
}
}
}
```
`IsEnabledAsync` has overloads to check multiple features in one method call.
#### GetOrNullAsync
Gets the current value for a feature. This method returns a `string`, so you store any kind of value inside it, by converting to or from `string`.
**Example: Check the maximum product count allowed**
```csharp
public class ProductController : AbpController
{
private readonly IFeatureChecker _featureChecker;
public ProductController(IFeatureChecker featureChecker)
{
_featureChecker = featureChecker;
}
public async Task<IActionResult> Create(CreateProductModel model)
{
var currentProductCount = await GetCurrentProductCountFromDatabase();
//GET THE FEATURE VALUE
var maxProductCountLimit =
await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount");
if (currentProductCount >= Convert.ToInt32(maxProductCountLimit))
{
throw new BusinessException(
"MyApp:ReachToMaxProductCountLimit",
$"You can not create more than {maxProductCountLimit} products!"
);
}
//TODO: Create the product in the database...
}
private async Task<int> GetCurrentProductCountFromDatabase()
{
throw new System.NotImplementedException();
}
}
```
This example uses a numeric value as a feature limit product counts for a user/tenant in a SaaS application.
Instead of manually converting the value to `int`, you can use the generic overload of the `GetAsync` method:
```csharp
var maxProductCountLimit = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount");
```
#### Extension Methods
There are some useful extension methods for the `IFeatureChecker` interface;
* `Task<T> GetAsync<T>(string name, T defaultValue = default)`: Used to get a value of a feature with the given type `T`. Allows to specify a `defaultValue` that is returned when the feature value is `null`.
* `CheckEnabledAsync(string name)`: Checks if given feature is enabled. Throws an `AbpAuthorizationException` if the feature was not `true` (enabled).
## Defining the Features
A feature should be defined to be able to check it.
### FeatureDefinitionProvider
Create a class inheriting the `FeatureDefinitionProvider` to define permissions.
**Example: Defining permissions**
```csharp
using Volo.Abp.Features;
namespace FeaturesDemo
{
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false");
myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10");
}
}
}
```
> ABP automatically discovers this class and registers the features. No additional configuration required.
> This class is generally created in the `Application.Contracts` project of your solution.
* In the `Define` method, you first need to add a **feature group** for your application/module or get an existing group then add **features** to this group.
* First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value.
* Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value.
Default value is used if there is no other value set for the current user/tenant.
### Other Feature Properties
While these minimal definitions are enough to make the feature system working, you can specify the **optional properties** for the features;
* `DisplayName`: A localizable string that will be used to show the feature name on the user interface.
* `Description`: A longer localizable text to describe the feature.
* `ValueType`: Type of the feature value. Can be a class implementing the `IStringValueType`. Built-in types:
* `ToggleStringValueType`: Used to define `true`/`false`, `on`/`off`, `enabled`/`disabled` style features. A checkbox is shown on the UI.
* `FreeTextStringValueType`: Used to define free text values. A textbox is shown on the UI.
* `SelectionStringValueType`: Used to force the value to be selected from a list. A dropdown list is shown on the UI.
* `IsVisibleToClients` (default: `true`): Set false to hide the value of this feature from clients (browsers). Sharing the value with the clients helps them to conditionally show/hide/change the UI parts based on the feature value.
* `Properties`: A dictionary to set/get arbitrary key-value pairs related to this feature. This can be a point for customization.
So, based on these descriptions, it would be better to define these features as shown below:
```csharp
using FeaturesDemo.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace FeaturesDemo
{
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
myGroup.AddFeature(
"MyApp.PdfReporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("PdfReporting"),
valueType: new ToggleStringValueType()
);
myGroup.AddFeature(
"MyApp.MaxProductCount",
defaultValue: "10",
displayName: LocalizableString
.Create<FeaturesDemoResource>("MaxProductCount"),
valueType: new FreeTextStringValueType(
new NumericValueValidator(0, 1000000))
);
}
}
}
```
* `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system.
* First feature is set to `ToggleStringValueType`, while the second one is set to `FreeTextStringValueType` with a numeric validator that allows to the values from `0` to `1,000,000`.
Remember to define the localization the keys in your localization file:
````json
"PdfReporting": "PDF Reporting",
"MaxProductCount": "Maximum number of products"
````
See the [localization document](Localization.md) for details about the localization system.
### Feature Management Modal
The [application startup template](Startup-Templates/Application.md) comes with the [tenant management](Modules/Tenant-Management.md) and the [feature management](Modules/Feature-Management.md) modules pre-installed.
Whenever you define a new feature, it will be available on the **feature management modal**. To open this modal, navigate to the **tenant management page** and select the `Features` action for a tenant (create a new tenant if there is no tenant yet):
![features-action](images/features-action.png)
This action opens a modal to manage the feature values for the selected tenant:
![features-modal](images/features-modal.png)
So, you can enable, disable and set values for a tenant. These values will be used whenever a user of this tenant uses the application.
See the *Feature Management* section below to learn more about managing the features.
### Child Features
A feature may have child features. This is especially useful if you want to create a feature that is selectable only if another feature was enabled.
**Example: Defining child features**
```csharp
using FeaturesDemo.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace FeaturesDemo
{
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
var reportingFeature = myGroup.AddFeature(
"MyApp.Reporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("Reporting"),
valueType: new ToggleStringValueType()
);
reportingFeature.CreateChild(
"MyApp.PdfReporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("PdfReporting"),
valueType: new ToggleStringValueType()
);
reportingFeature.CreateChild(
"MyApp.ExcelReporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("ExcelReporting"),
valueType: new ToggleStringValueType()
);
}
}
}
```
The example above defines a *Reporting* feature with two children: *PDF Reporting* and *Excel Reporting*.
### Changing Features Definitions of a Depended Module
A class deriving from the `FeatureDefinitionProvider` (just like the example above) can also get the existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions.
**Example: Manipulate an existing feature definition**
```csharp
var someGroup = context.GetGroupOrNull("SomeModule");
var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature");
if (feature != null)
{
feature.Description = ...
feature.CreateChild(...);
}
```
## Check a Feature in the Client Side
A feature value is available at the client side too, unless you set `IsVisibleToClients` to `false` on the feature definition. The feature values are exposed from the [Application Configuration API](API/Application-Configuration.md) and usable via some services on the UI.
### ASP.NET Core MVC / Razor Pages UI
Use `abp.features` API to get the feature values.
**Example: Get feature values in the JavaScript code**
````js
var isEnabled = abp.features.values["MyApp.ExcelReporting"] === "true";
var count = abp.features.values["MyApp.MaxProductCount"];
````
### Angular UI
See the [features](Features.md) document for the Angular UI.
## Feature Management
Feature management is normally done by an admin user using the feature management modal:
![features-modal](images/features-modal.png)
This modal is available on the related entities, like tenants in a multi-tenant application. To open it, navigate to the **Tenant Management** page (for a multi-tenant application), click to the **Actions** button left to the Tenant and select the **Features** action.
If you need to manage features by code, inject the `IFeatureManager` service.
**Example: Enable PDF reporting for a tenant**
```csharp
public class MyService : ITransientDependency
{
private readonly IFeatureManager _featureManager;
public MyService(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task EnablePdfReporting(Guid tenantId)
{
await _featureManager.SetForTenantAsync(
tenantId,
"MyApp.PdfReporting",
true.ToString()
);
}
}
```
`IFeatureManager` is defined by the Feature Management module. It comes pre-installed with the application startup template. See the [feature management module documentation](Modules/Feature-Management.md) for more information.
## Advanced Topics
### Feature Value Providers
Feature system is extensible. Any class derived from `FeatureValueProvider` (or implements `IFeatureValueProvider`) can contribute to the feature system. A value provider is responsible to **obtain the current value** of a given feature.
Feature value providers are **executed one by one**. If one of them return a non-null value, then this feature value is used and the other providers are not executed.
There are three pre-defined value providers, executed by the given order:
* `TenantFeatureValueProvider` tries to get if the feature value is explicitly set for the **current tenant**.
* `EditionFeatureValueProvider` tries to get the feature value for the current edition. Edition Id is obtained from the current principal identity (`ICurrentPrincipalAccessor`) with the claim name `editionid` (a constant defined as`AbpClaimTypes.EditionId`). Editions are not implemented for the [tenant management](Modules/Tenant-Management.md) module. You can implement it yourself or consider to use the [SaaS module](https://commercial.abp.io/modules/Volo.Saas) of the ABP Commercial.
* `DefaultValueFeatureValueProvider` gets the default value of the feature.
You can write your own provider by inheriting the `FeatureValueProvider`.
**Example: Enable all features for a user with "SystemAdmin" as a "User_Type" claim value**
```csharp
using System.Threading.Tasks;
using Volo.Abp.Features;
using Volo.Abp.Security.Claims;
using Volo.Abp.Validation.StringValues;
namespace FeaturesDemo
{
public class SystemAdminFeatureValueProvider : FeatureValueProvider
{
public override string Name => "SA";
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public SystemAdminFeatureValueProvider(
IFeatureStore featureStore,
ICurrentPrincipalAccessor currentPrincipalAccessor)
: base(featureStore)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public override Task<string> GetOrNullAsync(FeatureDefinition feature)
{
if (feature.ValueType is ToggleStringValueType &&
_currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin")
{
return Task.FromResult("true");
}
return null;
}
}
}
```
If a provider returns `null`, then the next provider is executed.
Once a provider is defined, it should be added to the `AbpFeatureOptions` as shown below:
```csharp
Configure<AbpFeatureOptions>(options =>
{
options.ValueProviders.Add<SystemAdminFeatureValueProvider>();
});
```
Use this code inside the `ConfigureServices` of your [module](Module-Development-Basics.md) class.
### Feature Store
`IFeatureStore` is the only interface that needs to be implemented to read the value of features from a persistence source, generally a database system. The Feature Management module implements it and pre-installed in the application startup template. See the [feature management module documentation](https://docs.abp.io/en/abp/latest/Modules/Feature-Management) for more information

16
docs/en/Index.md

@ -6,9 +6,21 @@ Explore the navigation menu to deep dive in the documentation.
## Getting Started
The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) tutorial.
The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) guide.
Then you can continue with the [web application development tutorial](Tutorials/Part-1.md).
## Tutorials / Articles
### Web Application Development
[Web application development tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack application using the ABP Framework.
### ABP Community Articles
See also the [ABP Community](https://community.abp.io/) articles.
## Samples
See the [sample projects](Samples/Index.md) built with the ABP Framework.
## Source Code

2
docs/en/Modules/Feature-Management.md

@ -1,3 +1,5 @@
# Feature Management Module
> This module implements the `IFeatureStore` to store and manage feature values in a database. See the [Features System document](../Features.md) to understand the features first.
TODO

2
docs/en/Modules/Permission-Management.md

@ -1,3 +1,5 @@
# Permission Management Module
This module implements the `IPermissionStore` to store and manage feature values in a database. See the [Authorization document](../Authorization.md) to understand the authorization and permission systems first.
TODO

3
docs/en/Multi-Tenancy.md

@ -369,3 +369,6 @@ options.AddDomainTenantResolver("{0}.mydomain.com");
options.AddDomainTenantResolver("{0}.com");
````
## See Also
* [Features](Features.md)

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

@ -8,27 +8,22 @@ The reason that you **can replace** but **cannot customize** default ABP compone
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below:
Then, open the `app.component.ts` and execute the `add` method of `ReplaceableComponentsService` to replace your component with an ABP component as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
@Component(/* component metadata */)
export class AppComponent {
constructor(
private store: Store // injected Store
)
{
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
private replaceableComponents: ReplaceableComponentsService, // injected the service
) {
this.replaceableComponents.add({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
});
}
}
```
@ -59,28 +54,24 @@ Add the following code in your layout template (`my-layout.component.html`) wher
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
import { Store } from '@ngxs/store'; // imported Store
import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
@Component(/* component metadata */)
export class AppComponent {
constructor(
private store: Store, // injected Store
private replaceableComponents: ReplaceableComponentsService, // injected the service
) {
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
}),
);
this.replaceableComponents.add({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
});
}
}
```
> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the AddReplaceableComponent action as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of AddReplaceableComponent is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.
> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the `add` method of `ReplaceableComponentsService` as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of the `add` method is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.
### Layout Components
@ -123,26 +114,22 @@ export class LogoComponent {}
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: LogoComponent,
key: eThemeBasicComponents.Logo,
}),
);
});
}
}
```
@ -166,7 +153,7 @@ yarn ng generate component routes --entryComponent
Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following:
```js
import { ABP, ReplaceableComponents } from '@abp/ng.core';
import { ABP } from '@abp/ng.core';
import {
Component,
HostBinding,
@ -309,26 +296,22 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: RoutesComponent,
key: eThemeBasicComponents.Routes,
}),
);
});
}
}
```
@ -511,26 +494,22 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: NavItemsComponent,
key: eThemeBasicComponents.NavItems,
}),
);
});
}
}
```

2
docs/en/UI/Angular/Environment.md

@ -1,6 +1,6 @@
# Environment
Every application needs some ** environment ** variables. In Angular world, this is usually managed by `environment.ts`, `environment.prod.ts` and so on. It is the same for ABP as well.
Every application needs some **environment** variables. In Angular world, this is usually managed by `environment.ts`, `environment.prod.ts` and so on. It is the same for ABP as well.
Current `Environment` configuration holds sub config classes as follows:

3
docs/en/UI/Angular/Features.md

@ -0,0 +1,3 @@
# Angular UI: Features
> This document explains how to get feature values in an Angular application. See the [Features document](../../Features.md) to learn the feature system.

12
docs/en/UI/Angular/Permission-Management-Component-Replacement.md

@ -473,24 +473,20 @@ Open the generated `permission-management.component.html` in `src/app/permission
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core';
import { ReplaceableComponentsService } from '@abp/ng.core';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngxs/store';
import { PermissionManagementComponent } from './permission-management/permission-management.component';
//...
export class AppComponent implements OnInit {
constructor(private store: Store) {} // injected store
constructor(private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
// added dispatching the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: PermissionManagementComponent,
key: ePermissionManagementComponents.PermissionManagement,
})
);
});
}
}
```

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

@ -1,5 +1,7 @@
## Service Proxies
> THIS DOCUMENT IS OUTDATED. IT IS BEING UPDATED. MEANWHILE, YOU CAN [SEE THIS ARTICLE](https://github.com/abpframework/abp/blob/dev/docs/en/Blog-Posts/2020-09-07%20Angular-Service-Proxies/POST.md) TO LEARN HOW TO USE THE ABP ANGULAR SERVICE PROXIES.
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on the server side) and **model objects** (matches to [DTOs](../../Data-Transfer-Objects) in the server side).
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced:

4
docs/en/docs-nav.json

@ -170,6 +170,10 @@
"text": "Settings",
"path": "Settings.md"
},
{
"text": "Features",
"path": "Features.md"
},
{
"text": "Data Filtering",
"path": "Data-Filtering.md"

BIN
docs/en/images/features-action.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/images/features-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

9
docs/zh-Hans/Authentication/Social-External-Logins.md

@ -1,10 +1,9 @@
# 社交/外部登录
## ASP.NET Core MVC / Razor Pages UI
[帐户模块](../Modules/Account.md)已配置为开箱即用的处理社交或外部登录. 你可以按照ASP.NET Core文档向你的应用程序添加社交/外部登录提供程序.
### 示例: Facebook 认证
## 示例: Facebook 认证
按照[ASP.NET Core Facebook集成文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/facebook-logins)向你应用程序添加Facebook登录.
@ -27,4 +26,8 @@ context.Services.AddAuthentication()
});
````
> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密.
> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密.
## Angular UI
从v3.1开始,Angular UI使用授权码流程(作为最佳实践)通过重定向到MVC UI登录页面来对用户进行身份验证. 因此,即使你使用的是Angular UI,社交/外部登录集成也与上面说明的相同.并且可以开箱即用.

189
docs/zh-Hans/CLI.md

@ -16,6 +16,12 @@ dotnet tool install -g Volo.Abp.Cli
dotnet tool update -g Volo.Abp.Cli
````
## 全局选项
虽然每个命令可能都有一组选项,但有些全局选项可以与任何命令一起使用:
* `--skip-cli-version-check`: 跳过检查最新版本的ABP CLI. 如果没有指定,它会检查最新版本,如果检查到ABP CLI的新版本,会显示一条警告消息.
## Commands
这里是所有可用的命令列表:
@ -25,7 +31,9 @@ dotnet tool update -g Volo.Abp.Cli
* **`update`**:自动更新的ABP解决方案ABP相关的NuGet和NPM包.
* **`add-package`**: 添加ABP包到项目.
* **`add-module`**: 添加[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index)到解决方案.
* **`generate-proxy`**: 生成客户端代理以使用服务器上的HTTP API端点.
* **`generate-proxy`**: 生成客户端代理以使用HTTP API端点.
* **`remove-proxy`**: 移除以前生成的客户端代理.
* **`switch-to-preview`**: 切换到ABP框架的最新预览版本。
* **`switch-to-preview`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本.
* **`switch-to-stable`**: 切换解决方案所有ABP相关包为最新的稳定版本.
* **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程.
@ -90,12 +98,33 @@ abp new Acme.BookStore
* **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本.
* `--preview`: 使用最新的预发行版本 (仅在未指定 `--version` 且最新稳定版本之后至少有一个预发行版时).
* `--preview`: 使用最新的预览版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D:\local-templat``https://.../my-template-file.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 默认的数据库提供程序是 `SQL Server`. 如果你使用EF Core但需要更改DBMS,可以按[这里所述](Entity-Framework-Core-Other-DBMS.md)进行更改(创建解决方案之后).
* `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用.
### update
更新所有ABP相关的包可能会很繁琐,框架和模块都有很多包. 此命令自动将解决方案或项目中所有ABP相关的包更新到最新版本.
用法:
````bash
abp update [options]
````
* 如果你的文件夹中有.sln文件,运行命令会将解决方案中所有项目ABP相关的包更新到最新版本.
* 如果你的文件夹中有.csproj文件,运行命令会将项目中所有ABP相关的包更新到最新版本.
#### Options
* `--npm`: 仅更新NPM包
* `--nuget`: 仅更新的NuGet包
* `--solution-path``-sp`: 指定解决方案路径/目录. 默认使用当前目录
* `--solution-name``-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件.
*`--check-all`: 分别检查每个包的新版本. 默认是 `false`.
### add-package
通过以下方式将ABP包添加到项目中
@ -150,105 +179,157 @@ abp add-module Volo.Blogging
* `-sp``--startup-project`: 启动项目的项目文件夹的相对路径. 默认值是当前文件夹.
* `--with-source-code`: 添加模块的源代码,而不是NuGet/NPM软件包.
### update
### generate-proxy
更新所有ABP相关的包可能会很繁琐,框架和模块都有很多包. 此命令自动将解决方案或项目中所有ABP相关的包更新到最新版本.
为您的HTTP API生成Angular服务代理,简化从客户端使用服务的成本. 在运行此命令之前,你的host必须启动正在运行.
用法:
````bash
abp update [options]
abp generate-proxy
````
* 如果你的文件夹中有.sln文件,运行命令会将解决方案中所有项目ABP相关的包更新到最新版本.
* 如果你的文件夹中有.csproj文件,运行命令会将项目中所有ABP相关的包更新到最新版本.
#### Options
* `--include-previews``-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本.
* `--npm`: 仅更新NPM包
* `--nuget`: 仅更新的NuGet包
* `--solution-path``-sp`: 指定解决方案路径/目录. 默认使用当前目录
* `--solution-name``-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件.
*`--check-all`: 分别检查每个包的新版本. 默认是 `false`.
### 切换到每晚构建(预览)包
* `--module``-m`: 指定要为其生成代理的后端模块的名称. 默认值: `app`.
* `--api-name``-a`: 在 `/src/environments/environment.ts` 中定义的API端点名称。. 默认值: `default`.
* `--source``-s`: 指定解析根名称空间和API定义URL的Angular项目名称. 默认值: `defaultProject`
* `--target``-t`: 指定放置生成的代码的Angular项目名称. 默认值: `defaultProject`.
* `--prompt``-p`: 在命令行提示符下询问选项(未指定的选项).
想要切换到ABP框架的最新**每晚构建**预览版可以使用此命令.
> 参阅 [Angular服务代理文档](UI/Angular/Service-Proxies.md) 了解更多.
用法:
### remove-proxy
````bash
abp switch-to-nightly [options]
````
从Angular应用程序中删除以前生成的代理代码. 在运行此命令之前,你的host必须启动正在运行.
你也可以使用切换回最新稳定版本:
This can be especially useful when you generate proxies for multiple modules before and need to remove one of them later.
Usage:
````bash
abp switch-to-stable [options]
abp remove-proxy
````
#### Options
`--solution-directory``-sd`: 指定解决方案文件夹. 解决方案应该在指定文件夹或子文件夹中. 如果未指定,默认为当前目录.
* `--module``-m`: 指定要为其生成代理的后端模块的名称. 默认值: `app`.
* `--api-name``-a`: 在 `/src/environments/environment.ts` 中定义的API端点名称。. 默认值: `default`.
* `--source``-s`: 指定解析根名称空间和API定义URL的Angular项目名称. 默认值: `defaultProject`
* `--target``-t`: 指定放置生成的代码的Angular项目名称. 默认值: `defaultProject`.
* `--prompt``-p`: 在命令行提示符下询问选项(未指定的选项).
### login
> 参阅 [Angular服务代理文档](UI/Angular/Service-Proxies.md) 了解更多.
CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录
### switch-to-preview
```bash
abp login <username>
```
你可以使用此命令将项目切换到ABP框架的最新预览版本.
```bash
abp login <username> -p <password>
```
请注意,新的登录将终止先前的会话并创建一个新的会话.
### logout
用法:
通过从计算机中删除会话令牌来注销.
````bash
abp switch-to-preview [options]
````
```
abp logout
```
#### Options
### generate-proxy
* `--solution-directory``-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录.
### switch-to-nightly
为你的HTTP API生成客户端代码,简化客户端使用服务的成本. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
想要切换到ABP框架的最新[每晚构建](Nightly-Builds.md)预览版可以使用此命令.
用法:
````bash
abp generate-proxy [options]
abp switch-to-nightly [options]
````
#### Options
* `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注!
* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
`--solution-directory``-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录.
示例:
### switch-to-stable
如果你使用的是ABP框架预览包(包括每晚构建),可以使用此命令切换回最新的稳定版本.
用法:
````bash
abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
abp switch-to-stable [options]
````
#### Options
### help
`--solution-directory``-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录.
CLI的基本用法信息.
### translate
用法:
源代码控制存储库中有多个JSON[本地化](Localization.md)文件时,用于简化翻译[本地化](Localization.md)文件的过程.
* 该命令将基于参考文化创建一个统一的json文件
* 它搜索当前目录和所有子目录中的所有本地化"JSON"文件(递归). 然后创建一个包含所有需要翻译的条目的文件(默认情况下名为 "abp-translation.json").
* 翻译了此文件中的条目后,你就可以使用 `--apply` 命令将更改应用于原始本地化文件.
> 该命令的主要目的是翻译ABP框架本地化文件(因为[abp仓库](https://github.com/abpframework/abp)包括数十个要在不同目录中转换的本地化文件).
#### 创建翻译文件
第一步是创建统一的翻译文件:
````bash
abp help [命令名]
abp translate -c <culture> [options]
````
示例:
````bash
abp help # 显示常规帮助.
abp help new # 显示有关 "New" 命令的帮助.
abp translate -c de-DE
````
该命令为 `de-DE` (德语)文化创建了统一的翻译文件.
##### 附加选项
* `--reference-culture``-r`: 默认值 `en`. 指定参考文化.
* `--output``-o`: 输出文件名. 默认值 `abp-translation.json`.
* `--all-values``-all`: 包括所有要翻译的键. 默认情况下,统一翻译文件仅包含目标文化的缺失文本. 如果你可能需要修改之前已经翻译的值,请指定此参数.
#### 应用更改
翻译完统一翻译文件中的条目后,你可以使用 `--apply` 参数将更改应用于原始本地化文件:
````bash
abp translate --apply # apply all changes
abp translate -a # shortcut for --apply
````
然后,检查源代码控制系统上的更改,以确保它已更改了正确的文件. 如果你翻译了ABP框架资源, 请发送 "Pull Request". 提前感谢你的贡献.
##### 附加选项
* `--file``-f`: 默认值: `abp-translation.json`. 翻译文件(仅在之前使用过 `--output` 选项时使用).
### login
CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录
```bash
abp login <username> # Allows you to enter your password hidden
abp login <username> -p <password> # Specify the password as a parameter (password is visible)
abp login <username> --organization <organization> # If you have multiple organizations, you need set your active organization
abp login <username> -p <password> -o <organization> # You can enter both your password and organization in the same command
```
> 当使用-p参数,请注意,因为你的密码是可见的. 它对于CI / CD自动化管道很有用.
请注意,新的登录将终止先前的会话并创建一个新的会话.
### logout
通过从计算机中删除会话令牌来注销.
```
abp logout
```

28
docs/zh-Hans/Contribution/Index.md

@ -1,8 +1,12 @@
## 贡献指南
# 贡献指南
ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南旨在帮助任何想要为项目做出贡献的人.
### 贡献代码
## community.abp.io
如果你可编写文章或关于ASP框架和ASP.NET Core的 "如何" 指南,请提交你的文章到[community.abp.io](https://community.abp.io/)网站.
## 贡献代码
你可以将Pull request(拉取请求)发送到Github存储库.
@ -12,15 +16,15 @@ ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南
在进行任何更改之前,请在[Github问题](https://github.com/abpframework/abp/issues)上进行讨论. 通过这种方式, 其他开发人员将不会处理同一个问题, 你的PR将有更好的机会被接受.
#### Bug修复 & 增强功能
### Bug修复 & 增强功能
你可能希望修复已知Bug或处理计划的增强功能. 请参阅Github上的[问题列表](https://github.com/abpframework/abp/issues).
#### 功能请求
### 功能请求
如果你对框架或模块有功能的想法, 请在Github上[创建一个问题](https://github.com/abpframework/abp/issues/new)或参加现有的讨论. 如果它被社区所接受你就可以实现它.
### 文档翻译
## 文档翻译
你可能希望将完整的[文档](https://abp.io/documents/)(包括本文)翻译成你的母语. 请按照下列步骤操作:
@ -37,13 +41,13 @@ ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南
完成了这些基本的翻译后,将添加一种新的语言
### 资源本地化
## 资源本地化
ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自己的应用程序创建本地化用户界面.
除此之外,框架和预构建模块已经本地化了文本.请参阅[Volo.Abp.UI包的本地化文本](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
#### 使用 "abp translate" 命令
### 使用 "abp translate" 命令
这是推荐的方法,因为它会自动查找所有缺少的文本的特定文化,让你在一个地方翻译.
@ -54,14 +58,10 @@ ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自
* 一旦你完成了翻译,使用 `abp translate -a` 命令应用更改到相关的文件.
* 在GitHub上发送PR.
#### 手动翻译
如果你想更改特定的资源文件,你可以自己找到这个文件进行必要的更改(或为你的语言创建新文件),并在GitHub上发送PR。
### 博客文章和教程
### 手动翻译
如果你发布了一些ABP框架的教程或博客帖子, 请通知我们(通过创建[Github问题](https://github.com/abpframework/abp/issues)), 我们可能会在官方文档中添加指向你的教程或博客帖子的链接和在[推特](https://twitter.com/abpframework)上公布.
如果你想更改特定的资源文件,你可以自己找到这个文件进行必要的更改(或为你的语言创建新文件),并在GitHub上发送PR.
### Bug 报告
## Bug 报告
如果你发现任何Bug, 请[在Github存储库上创建一个问题](https://github.com/abpframework/abp/issues/new).

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

@ -43,7 +43,10 @@ return user.GetProperty<string>("Title");
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(32);
}
);
````

6
docs/zh-Hans/Entities.md

@ -226,6 +226,12 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
虽然这种聚合根并不常见(也不建议使用),但实际上可以按照与上面提到的跟实体相同的方式定义复合键.在这种情况下,要使用非泛型的`AggregateRoot`基类.
### BasicAggregateRoot类
`AggregateRoot` 类实现了 `IHasExtraProperties``IHasConcurrencyStamp` 接口,这为派生类带来了两个属性. `IHasExtraProperties` 使实体可扩展(请参见下面的 *额外的属性*部分) 和 `IHasConcurrencyStamp` 添加了由ABP框架管理的 `ConcurrencyStamp` 属性实现[乐观并发](https://docs.microsoft.com/zh-cn/ef/core/saving/concurrency). 在大多数情况下,这些是聚合根需要的功能.
但是,如果你不需要这些功能,你的聚合根可以继承 `BasicAggregateRoot<TKey>`(或`BasicAggregateRoot`).
## 基类和接口的审计属性
有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**.

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

@ -414,7 +414,10 @@ public static class MyProjectNameEntityExtensions
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(128);
}
);
});
}

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

@ -325,7 +325,10 @@ public class BookService
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(64);
}
);
````

3
docs/zh-Hans/Getting-Started-Console-Application.md

@ -1,3 +0,0 @@
## 在控制台应用中使用ABP
ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息.

3
docs/zh-Hans/Global-Features.md

@ -0,0 +1,3 @@
# Global Features
TODO...

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

@ -1,9 +0,0 @@
# "如何" 指南
本部分包含一些常见问题的 "如何" 指南. 尽管其中是一些常见的开发任务和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/Local-Event-Bus.md

@ -156,6 +156,8 @@ namespace AbpDemo
> 事件处理程序类必须注册到依赖注入(DI),示例中使用了 `ITransientDependency`. 参阅[DI文档](Dependency-Injection.md)了解更多选项.
如果您执行**数据库操作**并在事件处理程序中使用[仓储](Repositories.md),那么您可能需要创建一个[工作单元](Unit-Of-Work.md),因为一些存储库方法需要在**活动的工作单元**中工作. 确保处理方法设置为 `virtual`,并为该方法添加一个 `[UnitOfWork]` attribute. 或者手动使用 `IUnitOfWorkManager` 创建一个工作单元范围.
## 事务和异常行为
当一个事件发布,订阅的事件处理程序将立即执行.所以;

1
docs/zh-Hans/MailKit.md

@ -0,0 +1 @@
TODO...

2
docs/zh-Hans/Module-Development-Basics.md

@ -1,4 +1,4 @@
## 模块开发
## 模块
### 介绍

3
docs/zh-Hans/Moodule-Entity-Extensions.md

@ -0,0 +1,3 @@
# Module Entity Extensions
参阅 https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions (文档会在近期完成).

32
docs/zh-Hans/Nightly-Builds.md

@ -2,40 +2,18 @@
所有框架和模块包每晚都部署到MyGet. 因此你可以使用或测试最新的代码,而无需等待下一个版本.
## 在Visual Studio配置
## 安装和卸载每晚预览包
> 需要Visual Studio 2017以上
1. 在VS中打开: `工具 > 选项 > NuGet 包管理器 > 程序包源`
2. 单击绿色的`+`图标
3. 在底部输入名称(ABP Nightly)和并粘贴URL(`https://www.myget.org/F/abp-nightly/api/v3/index.json`)到源上.
![night-build-add-nuget-source](images/night-build-add-nuget-source.png)
3. 单击`更新`按钮
4. 点击`确定`按钮保存
## 安装包
现在, 你可以从**管理NuGet包** 或 **程序包管理器控制台** 将预览/夜间程序包安装到你的项目中.
![night-build-add-nuget-package](images/night-build-add-nuget-package.png)
1. 在nuget浏览中,选择"包括预发行版".
2. 将包源更改为`全部`.
3. 搜索nuget包. 你将看到包的预发布格式为`(VERSION)-preview(DATE)` (如本示例中的**v0.16.0-preview20190401**).
4. 你可以单击`安装`按钮将包添加到项目中.
## 安装和卸载预览NPM包
预览NPM包的最新版本可以通过在应用程序的根文件夹命令运行命令安装:
可以通过在应用程序的根文件夹中运行以下命令安装最新版本的夜间预览软件包:
```bash
abp switch-to-preview --npm
abp switch-to-nightly
```
如果你正在使用ABP框架预览包,你可以使用此命令切换回稳定版本:
如果你正在使用ABP框架每晚预览包,你可以使用此命令切换回稳定版本:
```bash
abp switch-to-stable --npm
abp switch-to-stable
```
参阅 [ABP CLI 文档](./CLI.md) 了解更多信息.

1
docs/zh-Hans/Previews.md

@ -0,0 +1 @@
TODO...

6
docs/zh-Hans/Repositories.md

@ -6,7 +6,11 @@
## 通用(泛型)仓储
ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** . 你可以在服务中[注入](Dependency-Injection.md) `IRepository<TEntity, TKey>` 使用标准的**CRUD**操作. 用法示例:
ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** . 你可以在服务中[注入](Dependency-Injection.md) `IRepository<TEntity, TKey>` 使用标准的**CRUD**操作.
> 数据库提供程序层应正确配置为能够使用默认的通用存储库. 如果你已经使用启动模板创建了项目,则这些配置 **已经完成**了. 如果不是,请参考数据库提供程序文档([EF Core](Entity-Framework-Core.md) / [MongoDB](MongoDB.md))进行配置.
**默认通用仓储用法示例:**
````C#
public class PersonAppService : ApplicationService

27
docs/zh-Hans/Samples/Index.md

@ -15,17 +15,14 @@
一个简单的CRUD应用程序,展示了使用ABP框架开发应用程序的基本原理. 使用不同的技术实现了相同的示例:
* **Book Store: Razor Pages UI & Entity Framework Core**
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC)
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore)
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF)
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* **Book Store: Angular UI & MongoDB**
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG)
* [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG&DB=Mongo)
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
* **Book Store: Modular application (Razor Pages UI & EF Core)**
* [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular)
如果没有Razor Pages & MongoDB 结合,但你可以检查两个文档来理解它,因为DB和UI不会互相影响.
@ -33,11 +30,14 @@
### 其他示例
* **Entity Framework 迁移**: 演示如何将应用程序拆分为多个数据库的解决方案. 每个数据库包含不同的模块.
* [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [源码](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo)
* [EF Core数据库迁移文档](../Entity-Framework-Core-Migrations.md)
* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users.
* [源码](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo)
* [SignalR 集成文档](../SignalR-Integration.md)
* **分布式架构中的实时消息** (使用 SingalR & RabbitMQ)
* [源码](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo)
* [文章](https://community.abp.io/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17)
* **Dashboard Demo**: 一个简单的应用程序,展示了如何在ASP.NET Core MVC UI中使用widget系统.
* [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [Widget 文档](../UI/AspNetCore/Widgets.md)
@ -50,15 +50,20 @@
* [文本模板文档](../Text-Templating.md)
* **存储过程 Demo**: 演示如何以最佳实践使用存储过程,数据库视图和函数.
* [源码](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo)
* **无密码认证**: 演示如何添加自定义令牌提供者使用链接验证用户身份,而不是输入密码.
* [源码](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication)
* [文章](https://community.abp.io/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj)
* **自定义认证**: 如何为ASP.NET Core MVC / Razor Pages应用程序自定义身份验证的解决方案.
* [源码](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)
* 相关 "[How To](../How-To/Index.md)" 文档:
* [Azure Active Directory 认证](../How-To/Azure-Active-Directory-Authentication-MVC.md)
* [自定义登录页面](../How-To/Customize-Login-Page-MVC.md)
* [自定义 SignIn Manager](../How-To/Customize-SignIn-Manager.md)
* 相关文章:
* [Azure Active Directory 认证](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf)
* [自定义登录页面](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd)
* [自定义 SignIn Manager](https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753)
* **空的ASP.NET Core应用程序**: 从基本的ASP.NET Core应用程序使用ABP框架.
* [源码](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)
* [文档](../Getting-Started-AspNetCore-Application.md)
* **GRPC Demo**: 演示如何将gRPC服务添加到基于ABP框架的Web应用程序以及如何从控制台应用程序使用它.
* [源码](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)
* **空的控制台应用程序**: 从基本的控制台应用程序安装ABP框架.
* [源码](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication)
* [文档](../Getting-Started-Console-Application.md)

14
docs/zh-Hans/Startup-Templates/Application.md

@ -59,6 +59,20 @@ abp new Acme.BookStore -d mongodb
### 指定移动应用程序框架
此模板支持以下移动应用程序框架:
- `react-native`: React Native
使用 `-m` (或 `--mobile`) 选项指定移动应用程序框架:
````bash
abp new Acme.BookStore -m react-native
````
如果未指定,不会创建移动应用程序.
### 指定移动应用程序框架
该模板支持以下移动应用程序框架:
- `react-native`: React Native

1
docs/zh-Hans/UI/Angular/Environment.md

@ -0,0 +1 @@
TODO...

2
docs/zh-Hans/UI/Angular/Migration-Guide-v3.md

@ -450,4 +450,4 @@ export function switchLogos(store: Store) {
## 下一步是什么?
* [服务代理](Service-Proxies.md)
* [环境](./Environment.md)

1
docs/zh-Hans/UI/Angular/Multi-Tenancy.md

@ -0,0 +1 @@
TODO...

4
docs/zh-Hans/UI/Angular/Permission-Management.md

@ -4,7 +4,7 @@
你可以使用 `ConfigState``getGrantedPolicy` 选择器获取经过身份验证的用户的权限.
你可以从Store中获取权限的布尔值:
你可以获取权限的布尔值:
```js
import { Store } from '@ngxs/store';
@ -78,4 +78,4 @@ const routes: Routes = [
## 下一步是什么?
* [确认弹层](./Confirmation-Service.md)
* [多租户](./Multi-Tenancy.md)

8
docs/zh-Hans/UI/Angular/Toaster-Service.md

@ -24,7 +24,7 @@ class DemoComponent {
### 如何显示一个Toast Overlay
```js
this.toast.success('Message', 'Title');
this.toaster.success('Message', 'Title');
```
- `ToasterService` 方法接收三个参数,分别是 `message`, `title`, 和 `options`.
@ -70,9 +70,9 @@ const options: Partial<Toaster.ToastOptions> = {
已打开的toast overlay可以通过手动调用 `remove` 方法传递指定的 toast `id`删除.
```js
const toastId = this.toast.success('Message', 'Title')
const toastId = this.toaster.success('Message', 'Title')
this.toast.remove(toastId);
this.toaster.remove(toastId);
```
### 如何删除所有的Toasts
@ -80,7 +80,7 @@ this.toast.remove(toastId);
可以手动调用 `clear` 方法删除所有的已打开的toasts.
```js
this.toast.clear();
this.toaster.clear();
```
## API

2
docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md

@ -157,7 +157,7 @@ public class MyLoginModel : LoginModel
## 重写静态资源
重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,虚拟文件系统会自动处理它.
重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,[虚拟文件系统](../../Virtual-File-System.md)会自动处理它.
## 操作捆绑

1
docs/zh-Hans/Upgrading.md

@ -0,0 +1 @@
TODO...

105
docs/zh-Hans/Virtual-File-System.md

@ -74,53 +74,12 @@ Configure<AbpVirtualFileSystemOptions>(options =>
* 你的项目有一个名为 `MyFiles` 的目录.
* 你只想添加 `MyFiles` 目录到虚拟文件系统.
#### 在开发过程中处理嵌入式文件
将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难.
假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的.
我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能.
下面的示例展示了应用程序依赖于包含嵌入文件的模块("MyModule"), 并且应用程序可以在开发过程中直接使用模块的源代码.
````C#
[DependsOn(typeof(MyModule))]
public class MyWebAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(
Path.Combine(
hostingEnvironment.ContentRootPath,
string.Format(
"..{0}MyModuleProject",
Path.DirectorySeparatorChar
)
)
);
});
}
}
}
````
上面的代码假设`MyWebAppModule`和`MyModule`是Visual Studio解决方案中的两个不同的项目, `MyWebAppModule`依赖于`MyModule`.
> [应用程序启动模板]已经为本地化文件应用这个方法,所以当你更改一个本地化文件时,它会自动检测到更改.
## IVirtualFileProvider
将文件嵌入到程序集中并注册到虚拟文件系统后,可以使用 `IVirtualFileProvider` 接口来获取文件或目录内容:
````C#
public class MyService
public class MyService : ITransientDependency
{
private readonly IVirtualFileProvider _virtualFileProvider;
@ -129,7 +88,7 @@ public class MyService
_virtualFileProvider = virtualFileProvider;
}
public void Foo()
public void Test()
{
//Getting a single file
var file = _virtualFileProvider
@ -154,7 +113,9 @@ public class MyService
#### 虚拟文件中间件
虚拟文件中间件用于向客户端/浏览器提供嵌入式(js, css, image ...)文件, 就像 **wwwroot** 文件夹中的物理(静态)文件一样. 在静态文件中间件之后添加它, 如下所示:
虚拟文件中间件用于向客户端/浏览器提供嵌入式(js, css, image ...)文件, 就像 **wwwroot** 文件夹中的物理(静态)文件一样. 它同时涵盖了物理文件.
在你ASP.NET Core中间件配置中替换 `app.UseStaticFiles()``app.UseVirtualFiles()`:
````C#
app.UseVirtualFiles();
@ -170,6 +131,62 @@ app.UseVirtualFiles();
* Pages
* Views
* Components
* Themes
这允许你可以在 `.cshtml` 文件附近添加 `.js`, `.css`... 文件,更易于开发和维护你的项目.
#### 在开发过程中处理嵌入式文件
将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难.
假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的.
我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能.
下面的示例展示了应用程序依赖于包含嵌入文件的模块("MyModule"), 并且应用程序可以在开发过程中直接使用模块的源代码.
````C#
[DependsOn(typeof(MyModule))]
public class MyWebAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(
Path.Combine(
hostingEnvironment.ContentRootPath,
string.Format(
"..{0}MyModuleProject",
Path.DirectorySeparatorChar
)
)
);
});
}
}
}
````
上面的代码假设`MyWebAppModule`和`MyModule`是Visual Studio解决方案中的两个不同的项目, `MyWebAppModule`依赖于`MyModule`.
> [应用程序启动模板]已经为本地化文件应用这个方法,所以当你更改一个本地化文件时,它会自动检测到更改.
## 替换/重写虚拟文件
虚拟文件系统在运行时创建一个统一的文件系统,其中实际的文件在开发时被分配到不同的模块中.
如果两个模块将文件添加到相同的虚拟路径(如`my-path/my-file.css`),之后添加的模块将替换/替换前一个([模块依赖](Module-Development-Basics.md)顺序决定了添加文件的顺序).
此功能允许你的应用程序可以覆盖/替换定义应用程序所使用的模块的任何虚拟文件. 这是ABP框架的基本可扩展性功能之一.
因此,如果需要替换模块的文件,只需在模块/应用程序中完全相同的路径中创建该文件.
### 物理文件
物理文件总是覆盖虚拟文件. 这意味着如果你把一个文件放在 `/wwwroot/my-folder/my-file.css`,它将覆盖虚拟文件系统相同位置的文件.因此你需要知道在模块中定义的文件路径来覆盖它们.

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

@ -4,21 +4,16 @@
"text": "入门",
"items": [
{
"text": "从启动模板开始",
"text": "Web应用程序",
"path": "Getting-Started.md"
},
{
"text": "从空项目开始",
"items": [
{
"text": "使用ASP.NET Core Web Application",
"path": "Getting-Started-AspNetCore-Application.md"
},
{
"text": "使用Console Application",
"path": "Getting-Started-Console-Application.md"
}
]
"text": "控制台应用程序",
"path": "Startup-Templates/Console.md"
},
{
"text": "空Web应用程序t",
"path": "Getting-Started-AspNetCore-Application.md"
}
]
},
@ -66,10 +61,6 @@
}
]
},
{
"text": "\"如何\" 指南",
"path": "How-To/Index.md"
},
{
"text": "从ASP.NET Boilerplate迁移",
"path": "AspNet-Boilerplate-Migration-Guide.md"
@ -201,6 +192,19 @@
"path": "Object-To-Object-Mapping.md"
},
{
"text": "邮件发送",
"items": [
{
"text": "邮件发送系统",
"path": "Emailing.md"
},
{
"text": "MailKit集成",
"path": "MailKit.md"
}
]
},
{
"text": "BLOB存储",
"items": [
@ -397,6 +401,10 @@
"text": "v2.x 到 v3 迁移指南",
"path": "UI/Angular/Migration-Guide-v3.md"
},
{
"text": "环境",
"path": "UI/Angular/Environment.md"
},
{
"text": "服务代理",
"path": "UI/Angular/Service-Proxies.md"
@ -413,6 +421,10 @@
"text": "权限管理",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "多租户",
"path": "UI/Angular/Multi-Tenancy.md"
},
{
"text": "确认弹层",
"path": "UI/Angular/Confirmation-Service.md"
@ -635,6 +647,10 @@
"text": "微服务架构",
"path": "Microservice-Architecture.md"
},
{
"text": "预览版本",
"path": "Previews.md"
},
{
"text": "每日构建",
"path": "Nightly-Builds.md"
@ -643,6 +659,10 @@
"text": "路线图",
"path": "Road-Map.md"
},
{
"text": "升级",
"path": "Upgrading.md"
},
{
"text": "贡献指南",
"path": "Contribution/Index.md"

BIN
docs/zh-Hans/images/replace-email-layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/zh-Hans/images/solution-files-non-mvc.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

86
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers;
using Volo.Abp.Guids;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.TagHelpers
@ -17,10 +18,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S
public string Title { get; set; }
private readonly IVirtualFileProvider _virtualFileProvider;
private readonly IGuidGenerator _guidGenerator;
public AbpComponentDemoSectionTagHelper(IVirtualFileProvider virtualFileProvider)
public AbpComponentDemoSectionTagHelper(
IVirtualFileProvider virtualFileProvider,
IGuidGenerator guidGenerator)
{
_virtualFileProvider = virtualFileProvider;
_guidGenerator = guidGenerator;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -29,34 +34,69 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S
var content = await output.GetChildContentAsync();
output.PreContent.AppendHtml("<div class=\"abp-component-demo-section\">");
output.PreContent.AppendHtml($"<h2>{Title}</h2>");
output.PreContent.AppendHtml("<div class=\"abp-component-demo-section-body\">");
var previewId = _guidGenerator.Create();
var codeBlockId = _guidGenerator.Create();
var codeBlockTabId = _guidGenerator.Create();
var tagHelperCodeBlockId = _guidGenerator.Create();
var bootstrapCodeBlockId = _guidGenerator.Create();
output.PreContent.AppendHtml("<div class=\"card\">");
output.PreContent.AppendHtml("<div class=\"card-header\">");
output.PreContent.AppendHtml("<div class=\"row\">");
output.PreContent.AppendHtml($"<div class=\"col\"><h4 class=\"card-title my-1\">{Title}</h4></div>");
output.PreContent.AppendHtml("<div class=\"col-auto\">");
output.PreContent.AppendHtml("<nav>");
output.PreContent.AppendHtml("<div class=\"nav nav-pills\" role=\"tablist\">");
output.PreContent.AppendHtml($"<a class=\"nav-item nav-link active\" id=\"nav-preview-tab-{previewId}\" data-toggle=\"tab\" href=\"#nav-preview-{previewId}\" role=\"tab\" aria-controls=\"nav-preview-{previewId}\" aria-selected=\"true\">Preview</a>");
output.PreContent.AppendHtml($"<a class=\"nav-item nav-link\" id=\"nav-code-tab-{codeBlockId}\" data-toggle=\"tab\" href=\"#nav-code-{codeBlockId}\" role=\"tab\" aria-controls=\"nav-code-{codeBlockId}\" aria-selected=\"false\">Code</a>");
output.PreContent.AppendHtml("</div>");
output.PreContent.AppendHtml("</nav>");
output.PreContent.AppendHtml("</div>"); // col-auto
output.PreContent.AppendHtml("</div>"); // row
output.PreContent.AppendHtml("</div>"); // card-header
output.PreContent.AppendHtml("<div class=\"card-body\">");
output.PreContent.AppendHtml($"<div class=\"tab-content\" id=\"nav-preview-tabContent-{previewId}\">");
output.PreContent.AppendHtml($"<div class=\"tab-pane fade show active\" id=\"nav-preview-{previewId}\" role=\"tabpanel\" aria-labelledby=\"nav-preview-tab-{previewId}\">");
/* component rendering here */
output.PostContent.AppendHtml("</div>"); //abp-component-demo-section-body
AppendRawSource(output);
AppendBootstrapSource(output, content);
output.PostContent.AppendHtml("</div>"); //abp-component-demo-section
}
private static void AppendBootstrapSource(TagHelperOutput output, TagHelperContent content)
{
output.PostContent.AppendHtml("<div class=\"abp-component-demo-section-bs-source\">");
output.PostContent.AppendHtml("<h3>Bootstrap</h3>");
output.PostContent.AppendHtml("<pre>");
output.PostContent.Append(content.GetContent());
output.PostContent.AppendHtml("</div>"); // tab-pane
output.PostContent.AppendHtml($"<div class=\"tab-pane fade\" id=\"nav-code-{codeBlockId}\" role=\"tabpanel\" aria-labelledby=\"nav-code-tab-{codeBlockId}\">");
/* CodeBlock tabs */
output.PostContent.AppendHtml($"<ul class=\"nav nav-tabs\" id=\"code-block-tab-{codeBlockTabId}\" role=\"tablist\">");
output.PostContent.AppendHtml("<li class=\"nav-item\">");
output.PostContent.AppendHtml($"<a class=\"nav-link active\" id=\"tag-helper-tab-{tagHelperCodeBlockId}\" data-toggle=\"pill\" href=\"#tag-helper-{tagHelperCodeBlockId}\" role=\"tab\" aria-controls=\"tag-helper-{tagHelperCodeBlockId}\" aria-selected=\"true\">Abp Tag Helper</a>");
output.PostContent.AppendHtml("</li>");
output.PostContent.AppendHtml("<li class=\"nav-item\">");
output.PostContent.AppendHtml($"<a class=\"nav-link\" id=\"bootstrap-tab-{bootstrapCodeBlockId}\" data-toggle=\"pill\" href=\"#bootstrap-{bootstrapCodeBlockId}\" role=\"tab\" aria-controls=\"bootstrap-{tagHelperCodeBlockId}\" aria-selected=\"true\">Bootstrap</a>");
output.PostContent.AppendHtml("</li>");
output.PostContent.AppendHtml("</ul>");
output.PostContent.AppendHtml($"<div class=\"tab-content\" id=\"code-block-tabContent-{codeBlockTabId}\">");
output.PostContent.AppendHtml($"<div class=\"tab-pane fade show active\" id=\"tag-helper-{tagHelperCodeBlockId}\" role=\"tabpanel\" aria-labelledby=\"tag-helper-tab-{tagHelperCodeBlockId}\">");
output.PostContent.AppendHtml("<pre class=\"p-4\">");
output.PostContent.AppendHtml("<code>");
output.PostContent.Append(GetRawDemoSource());
output.PostContent.AppendHtml("</code>");
output.PostContent.AppendHtml("</pre>");
output.PostContent.AppendHtml("</div>");
}
private void AppendRawSource(TagHelperOutput output)
{
output.PostContent.AppendHtml("<div class=\"abp-component-demo-section-raw-source\">");
output.PostContent.AppendHtml("<h3>ABP Tag Helpers</h3>");
output.PostContent.AppendHtml("<pre>");
output.PostContent.Append(GetRawDemoSource());
output.PostContent.AppendHtml($"<div class=\"tab-pane fade\" id=\"bootstrap-{bootstrapCodeBlockId}\" role=\"tabpanel\" aria-labelledby=\"bootstrap-tab-{bootstrapCodeBlockId}\">");
output.PostContent.AppendHtml("<pre class=\"p-4\">");
output.PostContent.AppendHtml("<code>");
output.PostContent.Append(content.GetContent());
output.PostContent.AppendHtml("</code>");
output.PostContent.AppendHtml("</pre>");
output.PostContent.AppendHtml("</div>");
output.PostContent.AppendHtml("</div>"); // tab-content
output.PostContent.AppendHtml("</div>"); // tab-pane
output.PostContent.AppendHtml("</div>"); // tab-content
output.PostContent.AppendHtml("</div>"); // card-body
output.PostContent.AppendHtml("</div>"); // card
}
private string GetRawDemoSource()

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js

@ -231,7 +231,7 @@
(function () {
datatables.createAjax = function (serverMethod, inputAction) {
return function (requestData, callback, settings) {
var input = inputAction ? inputAction() : {};
var input = inputAction ? inputAction(requestData, settings) : {};
//Paging
if (settings.oInit.paging) {

9
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs

@ -27,8 +27,13 @@ namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations
{
return new DynamicMaxLengthAttributeAdapter((DynamicMaxLengthAttribute) attribute, stringLocalizer);
}
if (type == typeof(DynamicRangeAttribute))
{
return new DynamicRangeAttributeAdapter((DynamicRangeAttribute) attribute, stringLocalizer);
}
return _defaultAdapter.GetAttributeAdapter(attribute, stringLocalizer);
}
}
}
}

46
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs

@ -0,0 +1,46 @@
using System;
using System.Globalization;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations
{
public class DynamicRangeAttributeAdapter : AttributeAdapterBase<DynamicRangeAttribute>
{
private readonly string _max;
private readonly string _min;
public DynamicRangeAttributeAdapter(
DynamicRangeAttribute attribute,
IStringLocalizer stringLocalizer)
: base(attribute, stringLocalizer)
{
_min = Convert.ToString(Attribute.Minimum,CultureInfo.InvariantCulture);
_max = Convert.ToString(Attribute.Maximum,CultureInfo.InvariantCulture);
}
public override void AddValidation(ClientModelValidationContext context)
{
Check.NotNull(context, nameof(context));
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-range", GetErrorMessage(context));
MergeAttribute(context.Attributes, "data-val-range-min", _min);
MergeAttribute(context.Attributes, "data-val-range-max", _max);
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
Check.NotNull(validationContext, nameof(validationContext));
return GetErrorMessage(
validationContext.ModelMetadata,
validationContext.ModelMetadata.GetDisplayName(),
Attribute.Minimum,
Attribute.Maximum
);
}
}
}

7
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs

@ -72,7 +72,7 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
}
var errorInfo = new RemoteServiceErrorInfo();
if (exception is IUserFriendlyException)
{
errorInfo.Message = exception.Message;
@ -101,6 +101,8 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
errorInfo.Message = L["InternalServerErrorMessage"];
}
errorInfo.Data = exception.Data;
return errorInfo;
}
@ -190,7 +192,6 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
return exception;
}
protected virtual RemoteServiceErrorInfo CreateDetailedErrorInfoFromException(Exception exception)
{
var detailBuilder = new StringBuilder();
@ -213,7 +214,7 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
detailBuilder.AppendLine(exception.GetType().Name + ": " + exception.Message);
//Additional info for UserFriendlyException
if (exception is IUserFriendlyException &&
if (exception is IUserFriendlyException &&
exception is IHasErrorDetails)
{
var details = ((IHasErrorDetails) exception).Details;

64
framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs

@ -0,0 +1,64 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Reflection;
using JetBrains.Annotations;
namespace Volo.Abp.Validation
{
public class DynamicRangeAttribute : RangeAttribute
{
private static readonly FieldInfo MaximumField;
private static readonly FieldInfo MinimumField;
static DynamicRangeAttribute()
{
MaximumField = typeof(RangeAttribute).GetField(
"<Maximum>k__BackingField",
BindingFlags.Instance | BindingFlags.NonPublic
);
Debug.Assert(MaximumField != null, nameof(MaximumField) + " != null");
MinimumField = typeof(RangeAttribute).GetField(
"<Minimum>k__BackingField",
BindingFlags.Instance | BindingFlags.NonPublic
);
Debug.Assert(MinimumField != null, nameof(MinimumField) + " != null");
}
/// <param name="sourceType">A type to get the values of the properties</param>
/// <param name="operandType">The type of the range parameters. Must implement IComparable. <see cref="RangeAttribute.OperandType"/></param>
/// <param name="minimumPropertyName">The name of the public static property for the <see cref="RangeAttribute.Minimum"/></param>
/// <param name="maximumPropertyName">The name of the public static property for the <see cref="RangeAttribute.Maximum"/></param>
public DynamicRangeAttribute(
[NotNull] Type sourceType,
[NotNull] Type operandType,
[CanBeNull] string minimumPropertyName,
[CanBeNull] string maximumPropertyName
)
: base(operandType, string.Empty, string.Empty)
{
Check.NotNull(sourceType, nameof(sourceType));
if (minimumPropertyName != null)
{
var minimumProperty = sourceType.GetProperty(
minimumPropertyName,
BindingFlags.Static | BindingFlags.Public
);
Debug.Assert(minimumProperty != null, nameof(minimumProperty) + " != null");
MinimumField.SetValue(this, minimumProperty.GetValue(null));
}
if (maximumPropertyName != null)
{
var maximumProperty = sourceType.GetProperty(
maximumPropertyName,
BindingFlags.Static | BindingFlags.Public
);
Debug.Assert(maximumProperty != null, nameof(maximumProperty) + " != null");
MaximumField.SetValue(this, maximumProperty.GetValue(null));
}
}
}
}

9
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs

@ -62,6 +62,11 @@ namespace Volo.Abp.Features
return FeatureDefinitions.GetOrDefault(name);
}
public IReadOnlyList<FeatureGroupDefinition> GetGroups()
{
return FeatureGroupDefinitions.Values.ToImmutableList();
}
protected virtual Dictionary<string, FeatureDefinition> CreateFeatureDefinitions()
{
var features = new Dictionary<string, FeatureDefinition>();
@ -78,7 +83,7 @@ namespace Volo.Abp.Features
}
protected virtual void AddFeatureToDictionaryRecursively(
Dictionary<string, FeatureDefinition> features,
Dictionary<string, FeatureDefinition> features,
FeatureDefinition feature)
{
if (features.ContainsKey(feature.Name))
@ -114,4 +119,4 @@ namespace Volo.Abp.Features
return context.Groups;
}
}
}
}

4
framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs

@ -11,5 +11,7 @@ namespace Volo.Abp.Features
IReadOnlyList<FeatureDefinition> GetAll();
FeatureDefinition GetOrNull(string name);
IReadOnlyList<FeatureGroupDefinition> GetGroups();
}
}
}

8
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpRemoteCallException.cs

@ -28,6 +28,14 @@ namespace Volo.Abp.Http.Client
: base(error.Message)
{
Error = error;
if (error.Data != null)
{
foreach (var dataKey in error.Data.Keys)
{
Data[dataKey] = error.Data[dataKey];
}
}
}
}
}

6
framework/src/Volo.Abp.Http/Volo/Abp/Http/RemoteServiceErrorInfo.cs

@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Volo.Abp.Http
{
@ -23,6 +25,8 @@ namespace Volo.Abp.Http
/// </summary>
public string Details { get; set; }
public IDictionary Data { get; set; }
/// <summary>
/// Validation errors if exists.
/// </summary>
@ -49,4 +53,4 @@ namespace Volo.Abp.Http
Code = code;
}
}
}
}

36
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
@ -32,7 +33,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
{
return Content("ModelState.IsValid: " + ModelState.IsValid.ToString().ToLowerInvariant());
}
[HttpGet]
[Route("object-result-action-dynamic-length")]
public Task<string> ObjectResultActionDynamicLength(ValidationDynamicTestModel model)
@ -47,7 +48,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
[StringLength(5, MinimumLength = 2)]
public string Value1 { get; set; }
}
public class ValidationDynamicTestModel
{
[DynamicStringLength(typeof(Consts), nameof(Consts.MaxValue1Length), nameof(Consts.MinValue1Length))]
@ -55,18 +56,41 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
[DynamicMaxLength(typeof(Consts), nameof(Consts.MaxValue2Length))]
public string Value2 { get; set; }
[DynamicMaxLength(typeof(Consts), nameof(Consts.MaxValue3Length))]
public int[] Value3 { get; set; }
[DynamicRange(typeof(Consts), typeof(int), nameof(Consts.MinValue1), nameof(Consts.MaxValue1))]
public int Value4 { get; set; }
[DynamicRange(typeof(Consts), typeof(double), nameof(Consts.MinValue2), nameof(Consts.MaxValue2))]
public double Value5 { get; set; }
[DynamicRange(typeof(Consts), typeof(DateTime), nameof(Consts.MinValue3), nameof(Consts.MaxValue3))]
public DateTime Value6 { get; set; }
public static class Consts
{
public static int MinValue1Length { get; set; } = 2;
public static int MaxValue1Length { get; set; } = 7;
public static int MaxValue2Length { get; set; } = 4;
public static int MaxValue3Length { get; set; } = 2;
public static int MinValue1 { get; set; } = 1;
public static int MaxValue1 { get; set; } = 5;
public static double MinValue2 { get; set; } = 1.2;
public static double MaxValue2 { get; set; } = 7.2;
public static string MinValue3 { get; set; } = "1/2/2004";
public static string MaxValue3 { get; set; } = "3/4/2004";
}
}
@ -83,4 +107,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
}
}
}
}
}

19
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs

@ -61,9 +61,9 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
[Fact]
public async Task Should_Validate_Dynamic_Length_Object_Result_Success()
{
var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action-dynamic-length?value1=hello&value3[0]=53&value3[1]=54");
var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action-dynamic-length?value1=hello&value3[0]=53&value3[1]=54&value4=4&value5=1.5&value6=2004-02-04");
result.ShouldBe("hello");
}
[Fact]
@ -71,15 +71,24 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
{
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=a", HttpStatusCode.BadRequest); //value1 has min string length of 2 chars.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=12345678", HttpStatusCode.BadRequest); //value1 has max string length of 7 chars.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=123458&value2=12345", HttpStatusCode.BadRequest); //value2 has max length of 5 chars.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value3[2]=55&value3[3]=56", HttpStatusCode.BadRequest); //value3 has max length of 2.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value[4]=10", HttpStatusCode.BadRequest); //value4 has max num of 5.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value4=2&value5=1.1", HttpStatusCode.BadRequest); //value4 has min num of 1.2.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value4=2&value5=1.2&value6=2004-05-04", HttpStatusCode.BadRequest); //value4 has max date of 3/4/2004.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
}
}
}

46
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js

@ -0,0 +1,46 @@
var abp = abp || {};
(function () {
if (!luxon) {
throw "abp/luxon library requires the luxon library included to the page!";
}
/* TIMING *************************************************/
abp.timing = abp.timing || {};
var setObjectValue = function (obj, property, value) {
if (typeof property === "string") {
property = property.split('.');
}
if (property.length > 1) {
var p = property.shift();
setObjectValue(obj[p], property, value);
} else {
obj[property[0]] = value;
}
}
var getObjectValue = function (obj, property) {
return property.split('.').reduce((a, v) => a[v], obj)
}
abp.timing.convertFieldsToIsoDate = function (form, fields) {
for (var field of fields) {
var dateTime = luxon.DateTime
.fromFormat(
getObjectValue(form, field),
abp.localization.currentCulture.dateTimeFormat.shortDatePattern,
{locale: abp.localization.currentCulture.cultureName}
);
if (!dateTime.invalid) {
setObjectValue(form, field, dateTime.toFormat("yyyy-MM-dd HH:mm:ss"))
}
}
return form;
}
})(jQuery);

4
framework/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj

@ -14,4 +14,8 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\Http\Localization\en.json" />
</ItemGroup>
</Project>

22
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs

@ -2,8 +2,12 @@
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.DynamicProxying;
using Volo.Abp.Http.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.TestApp;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Http
{
@ -22,6 +26,24 @@ namespace Volo.Abp.Http
{
options.RemoteServices.Default = new RemoteServiceConfiguration("/");
});
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpHttpClientTestModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<HttpClientTestResource>("en")
.AddVirtualJson("/Volo/Abp/Http/Localization");
});
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("Volo.Abp.Http.DynamicProxying", typeof(HttpClientTestResource));
});
}
}
}

2
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs

@ -9,6 +9,8 @@ namespace Volo.Abp.Http.DynamicProxying
Task GetException1Async();
Task GetException2Async();
Task<DateTime> GetWithDateTimeParameterAsync(DateTime dateTime1);
Task<string> PostValueWithHeaderAndQueryStringAsync(string headerValue, string qsValue);

10
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs

@ -27,6 +27,14 @@ namespace Volo.Abp.Http.DynamicProxying
throw new UserFriendlyException("This is an error message!");
}
[HttpGet]
[Route("get-exception2")]
public Task GetException2Async()
{
throw new BusinessException("Volo.Abp.Http.DynamicProxying:10001")
.WithData("0","TEST");
}
[HttpGet]
[Route("get-with-datetime-parameter")]
public Task<DateTime> GetWithDateTimeParameterAsync(DateTime dateTime1)
@ -133,4 +141,4 @@ namespace Volo.Abp.Http.DynamicProxying
[FromQuery]
public DateTime FirstReleaseDate { get; set; }
}
}
}

11
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs

@ -1,8 +1,10 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Shouldly;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Localization;
using Volo.Abp.Localization;
using Xunit;
@ -32,6 +34,13 @@ namespace Volo.Abp.Http.DynamicProxying
exception.Error.Message.ShouldBe("This is an error message!");
}
[Fact]
public async Task GetException2Async()
{
var exception = await Assert.ThrowsAsync<AbpRemoteCallException>(async () => await _controller.GetException2Async());
exception.Error.Message.ShouldBe("Business exception with data: TEST");
}
[Fact]
public async Task GetWithDateTimeParameterAsync()
{
@ -151,4 +160,4 @@ namespace Volo.Abp.Http.DynamicProxying
}
}
}
}

10
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/Localization/HttpClientTestResource.cs

@ -0,0 +1,10 @@
using Volo.Abp.Localization;
namespace Volo.Abp.Http.Localization
{
[LocalizationResourceName("HttpClientTest")]
public class HttpClientTestResource
{
}
}

6
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/Localization/en.json

@ -0,0 +1,6 @@
{
"culture": "en",
"texts": {
"Volo.Abp.Http.DynamicProxying:10001": "Business exception with data: {0}"
}
}

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs

@ -2,9 +2,11 @@
using System.Collections.Generic;
using System.Text;
using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.DataFiltering
{
[Collection(MongoTestCollection.Name)]
public class HardDelete_Tests : HardDelete_Tests<AbpMongoDbTestModule>
{
}

6
global.json

@ -0,0 +1,6 @@
{
"sdk": {
"version": "3.1.102",
"rollForward": "latestFeature"
}
}

4
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json

@ -53,6 +53,8 @@
"ResetPassword_Information": "Please enter your new password.",
"YourPasswordIsSuccessfullyReset": "Your password is successfully reset.",
"GoToTheApplication": "Go to the application",
"BackToLogin": "Back to login"
"BackToLogin": "Back to login",
"ProfileTab:Password": "Change password",
"ProfileTab:PersonalInfo": "Personal info"
}
}

4
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json

@ -52,6 +52,8 @@
"ResetPassword_Information": "Lütfen yeni şifrenizi belirleyin.",
"YourPasswordIsSuccessfullyReset": "Şifreniz başarıyla sıfırlandı.",
"GoToTheApplication": "Uygulamaya git",
"BackToLogin": "Girişe dön"
"BackToLogin": "Girişe dön",
"ProfileTab:Password": "Şifre değiştir",
"ProfileTab:PersonalInfo": "Kişisel bilgiler"
}
}

3
modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs

@ -1,6 +1,7 @@
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Identity;
using AutoMapper;
using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo;
namespace Volo.Abp.Account.Web
{
@ -8,7 +9,7 @@ namespace Volo.Abp.Account.Web
{
public AbpAccountWebAutoMapperProfile()
{
CreateMap<ProfileDto, PersonalSettingsInfoModel>();
CreateMap<ProfileDto, AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel>();
}
}
}

31
modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs

@ -1,7 +1,10 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Account.Localization;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Account.Web.ProfileManagement;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars;
using Volo.Abp.AutoMapper;
@ -50,16 +53,38 @@ namespace Volo.Abp.Account.Web
options.Contributors.Add(new AccountModuleToolbarContributor());
});
ConfigureProfileManagementPage();
context.Services.AddAutoMapperObjectMapper<AbpAccountWebModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<AbpAccountWebAutoMapperProfile>(validate: true);
});
}
private void ConfigureProfileManagementPage()
{
Configure<RazorPagesOptions>(options =>
{
options.Conventions.AuthorizePage("/Account/Manage");
});
context.Services.AddAutoMapperObjectMapper<AbpAccountWebModule>();
Configure<AbpAutoMapperOptions>(options =>
Configure<ProfileManagementPageOptions>(options =>
{
options.AddProfile<AbpAccountWebAutoMapperProfile>(validate: true);
options.Contributors.Add(new AccountProfileManagementPageContributor());
});
Configure<AbpBundlingOptions>(options =>
{
options.ScriptBundles
.Configure(typeof(ManageModel).FullName,
configuration =>
{
configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/Password/Default.js");
configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js");
});
});
}
}
}

59
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs

@ -0,0 +1,59 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
using Volo.Abp.Validation;
namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password
{
public class AccountProfilePasswordManagementGroupViewComponent : AbpViewComponent
{
private readonly IProfileAppService _profileAppService;
public AccountProfilePasswordManagementGroupViewComponent(
IProfileAppService profileAppService)
{
_profileAppService = profileAppService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var user = await _profileAppService.GetAsync();
var model = new ChangePasswordInfoModel
{
HideOldPasswordInput = !user.HasPassword
};
return View("~/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml", model);
}
public class ChangePasswordInfoModel
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:CurrentPassword")]
[DataType(DataType.Password)]
[DisableAuditing]
public string CurrentPassword { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:NewPassword")]
[DataType(DataType.Password)]
[DisableAuditing]
public string NewPassword { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:NewPasswordConfirm")]
[DataType(DataType.Password)]
[DisableAuditing]
public string NewPasswordConfirm { get; set; }
public bool HideOldPasswordInput { get; set; }
}
}
}

17
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml

@ -0,0 +1,17 @@
@using Volo.Abp.Account.Localization
@using Volo.Abp.Users
@using Microsoft.AspNetCore.Mvc.Localization
@inject IHtmlLocalizer<AccountResource> L
@inject ICurrentUser CurrentUser
@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password.AccountProfilePasswordManagementGroupViewComponent.ChangePasswordInfoModel
<h4>@L["ChangePassword"]</h4><hr/>
<form id="ChangePasswordForm">
@if (!Model.HideOldPasswordInput)
{
<abp-input asp-for="CurrentPassword"/>
}
<abp-input asp-for="NewPassword"/>
<abp-input asp-for="NewPasswordConfirm"/>
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value"/>
</form>

31
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.js

@ -0,0 +1,31 @@
(function ($) {
$(function () {
var l = abp.localization.getResource("AbpAccount");
$('#ChangePasswordForm').submit(function (e) {
e.preventDefault();
if (!$('#ChangePasswordForm').valid()) {
return false;
}
var input = $('#ChangePasswordForm').serializeFormToObject();
if (
input.newPassword != input.newPasswordConfirm ||
input.newPassword == ''
) {
abp.message.error(l('NewPasswordConfirmFailed'));
return;
}
if (input.currentPassword && input.currentPassword == ''){
return;
}
volo.abp.identity.profile.changePassword(input).then(function (result) {
abp.message.success(l('PasswordChanged'));
});
});
});
})(jQuery);

56
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs

@ -0,0 +1,56 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Identity;
using Volo.Abp.Validation;
namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo
{
public class AccountProfilePersonalInfoManagementGroupViewComponent : AbpViewComponent
{
private readonly IProfileAppService _profileAppService;
public AccountProfilePersonalInfoManagementGroupViewComponent(
IProfileAppService profileAppService)
{
_profileAppService = profileAppService;
ObjectMapperContext = typeof(AbpAccountWebModule);
}
public async Task<IViewComponentResult> InvokeAsync()
{
var user = await _profileAppService.GetAsync();
var model = ObjectMapper.Map<ProfileDto, PersonalInfoModel>(user);
return View("~/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml", model);
}
public class PersonalInfoModel
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
[Display(Name = "DisplayName:UserName")]
public string UserName { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
[Display(Name = "DisplayName:Email")]
public string Email { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))]
[Display(Name = "DisplayName:Name")]
public string Name { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))]
[Display(Name = "DisplayName:Surname")]
public string Surname { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "DisplayName:PhoneNumber")]
public string PhoneNumber { get; set; }
}
}
}

39
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml

@ -0,0 +1,39 @@
@using Volo.Abp.Account.Localization
@using Volo.Abp.Users
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@using Volo.Abp.Identity.Settings
@using Volo.Abp.Settings
@inject IHtmlLocalizer<AccountResource> L
@inject ICurrentUser CurrentUser
@inject ISettingProvider SettingManager
@inject IThemeManager ThemeManager
@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo.AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel
@{
var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true",
StringComparison.OrdinalIgnoreCase);
var isEmailUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsEmailUpdateEnabled), "true",
StringComparison.OrdinalIgnoreCase);
}
<h4>@L["PersonalSettings"]</h4><hr/>
<form method="post" id="PersonalSettingsForm">
<abp-input asp-for="UserName" readonly="!isUserNameUpdateEnabled"/>
<abp-row>
<abp-column size-md="_6">
<abp-input asp-for="Name"/>
</abp-column>
<abp-column size-md="_6">
<abp-input asp-for="Surname"/>
</abp-column>
</abp-row>
<abp-input asp-for="Email" readonly="!isEmailUpdateEnabled"/>
<abp-input asp-for="PhoneNumber"/>
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value"/>
</form>

19
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js

@ -0,0 +1,19 @@
(function ($) {
$(function () {
var l = abp.localization.getResource("AbpAccount");
$('#PersonalSettingsForm').submit(function (e) {
e.preventDefault();
if (!$('#PersonalSettingsForm').valid()) {
return false;
}
var input = $('#PersonalSettingsForm').serializeFormToObject();
volo.abp.identity.profile.update(input).then(function (result) {
abp.notify.success(l('PersonalSettingsSaved'));
});
});
});
})(jQuery);

73
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml

@ -2,66 +2,33 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Web.Pages.Account
@using Volo.Abp.Identity.Settings
@using Volo.Abp.Settings
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@inject ISettingProvider SettingManager
@inject IThemeManager ThemeManager
@inject IHtmlLocalizer<AccountResource> L
@model ManageModel
@{
Layout = ThemeManager.CurrentTheme.GetApplicationLayout();
var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true",
StringComparison.OrdinalIgnoreCase);
var isEmailUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsEmailUpdateEnabled), "true",
StringComparison.OrdinalIgnoreCase);
}
@section scripts {
<abp-script-bundle name="@typeof(ManageModel).FullName">
<abp-script src="/Pages/Account/Manage.js" />
</abp-script-bundle>
<abp-script-bundle name="@typeof(ManageModel).FullName"/>
}
<abp-card>
<abp-card-body>
<abp-tabs tab-style="PillVertical">
@if (!Model.DisablePasswordChange)
{
<abp-tab title="@L["ChangePassword"].Value">
<h4>@L["ChangePassword"].Value</h4><hr/>
<form id="ChangePasswordForm">
@if (!Model.HideOldPasswordInput)
{
<abp-input asp-for="ChangePasswordInfoModel.CurrentPassword"/>
}
<abp-input asp-for="ChangePasswordInfoModel.NewPassword"/>
<abp-input asp-for="ChangePasswordInfoModel.NewPasswordConfirm"/>
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value"/>
</form>
</abp-tab>
}
<abp-tab title="@L["PersonalSettings"].Value">
<h4>@L["PersonalSettings"].Value</h4><hr/>
<form method="post" id="PersonalSettingsForm">
<abp-input asp-for="PersonalSettingsInfoModel.UserName" readonly="!isUserNameUpdateEnabled"/>
<abp-row>
<abp-column size-md="_6">
<abp-input asp-for="PersonalSettingsInfoModel.Name"/>
</abp-column>
<abp-column size-md="_6">
<abp-input asp-for="PersonalSettingsInfoModel.Surname"/>
</abp-column>
</abp-row>
<abp-input asp-for="PersonalSettingsInfoModel.Email" readonly="!isEmailUpdateEnabled"/>
<abp-input asp-for="PersonalSettingsInfoModel.PhoneNumber"/>
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value"/>
</form>
</abp-tab>
</abp-tabs>
</abp-card-body>
</abp-card>
<div id="ProfileManagementWrapper">
<abp-card>
<abp-card-body>
<abp-tabs tab-style="PillVertical" vertical-header-size="_3">
@foreach (var group in Model.ProfileManagementPageCreationContext.Groups)
{
<abp-tab title="@group.DisplayName">
<h2>@group.DisplayName</h2>
<hr class="my-4" />
@await Component.InvokeAsync(group.ComponentType, new
{
parameter = group.Parameter
})
</abp-tab>
}
</abp-tabs>
</abp-card-body>
</abp-card>
</div>

72
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs

@ -2,35 +2,31 @@
using System.Threading.Tasks;
using Volo.Abp.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.Account.Web.ProfileManagement;
using Volo.Abp.Validation;
namespace Volo.Abp.Account.Web.Pages.Account
{
public class ManageModel : AccountPageModel
{
public ChangePasswordInfoModel ChangePasswordInfoModel { get; set; }
public ProfileManagementPageCreationContext ProfileManagementPageCreationContext { get; private set; }
public PersonalSettingsInfoModel PersonalSettingsInfoModel { get; set; }
protected ProfileManagementPageOptions Options { get; }
public bool DisablePasswordChange { get; set; }
public bool HideOldPasswordInput { get; set; }
protected IProfileAppService ProfileAppService { get; }
public ManageModel(IProfileAppService profileAppService)
public ManageModel(IOptions<ProfileManagementPageOptions> options)
{
ProfileAppService = profileAppService;
Options = options.Value;
}
public virtual async Task<IActionResult> OnGetAsync()
{
var user = await ProfileAppService.GetAsync();
PersonalSettingsInfoModel = ObjectMapper.Map<ProfileDto, PersonalSettingsInfoModel>(user);
ProfileManagementPageCreationContext = new ProfileManagementPageCreationContext(ServiceProvider);
DisablePasswordChange = user.IsExternal;
HideOldPasswordInput = !user.HasPassword;
foreach (var contributor in Options.Contributors)
{
await contributor.ConfigureAsync(ProfileManagementPageCreationContext);
}
return Page();
}
@ -40,50 +36,4 @@ namespace Volo.Abp.Account.Web.Pages.Account
return Task.FromResult<IActionResult>(Page());
}
}
public class ChangePasswordInfoModel
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:CurrentPassword")]
[DataType(DataType.Password)]
public string CurrentPassword { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:NewPassword")]
[DataType(DataType.Password)]
public string NewPassword { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:NewPasswordConfirm")]
[DataType(DataType.Password)]
public string NewPasswordConfirm { get; set; }
}
public class PersonalSettingsInfoModel
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
[Display(Name = "DisplayName:UserName")]
public string UserName { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
[Display(Name = "DisplayName:Email")]
public string Email { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))]
[Display(Name = "DisplayName:Name")]
public string Name { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))]
[Display(Name = "DisplayName:Surname")]
public string Surname { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "DisplayName:PhoneNumber")]
public string PhoneNumber { get; set; }
}
}

47
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js

@ -1,47 +0,0 @@
(function ($) {
var l = abp.localization.getResource('AbpAccount');
var _profileService = volo.abp.identity.profile;
$('#ChangePasswordForm').submit(function (e) {
e.preventDefault();
if (!$('#ChangePasswordForm').valid()) {
return false;
}
var input = $('#ChangePasswordForm').serializeFormToObject()
.changePasswordInfoModel;
if (
input.newPassword != input.newPasswordConfirm ||
input.newPassword == ''
) {
abp.message.error(l('NewPasswordConfirmFailed'));
return;
}
if (input.currentPassword && input.currentPassword == ''){
return;
}
_profileService.changePassword(input).then(function (result) {
abp.message.success(l('PasswordChanged'));
});
});
$('#PersonalSettingsForm').submit(function (e) {
e.preventDefault();
if (!$('#PersonalSettingsForm').valid()) {
return false;
}
var input = $('#PersonalSettingsForm').serializeFormToObject()
.personalSettingsInfoModel;
_profileService.update(input).then(function (result) {
abp.notify.success(l('PersonalSettingsSaved'));
});
});
})(jQuery);

1
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs

@ -18,7 +18,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
{
public class RegisterModel : AccountPageModel
{
protected IAccountAppService AccountAppService { get; }
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; }

48
modules/account/src/Volo.Abp.Account.Web/ProfileManagement/AccountProfileManagementPageContributor.cs

@ -0,0 +1,48 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.Account.Localization;
using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password;
using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo;
using Volo.Abp.Identity;
using Volo.Abp.Users;
namespace Volo.Abp.Account.Web.ProfileManagement
{
public class AccountProfileManagementPageContributor : IProfileManagementPageContributor
{
public async Task ConfigureAsync(ProfileManagementPageCreationContext context)
{
var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<AccountResource>>();
if (await IsPasswordChangeEnabled(context))
{
context.Groups.Add(
new ProfileManagementPageGroup(
"Volo.Abp.Account.Password",
l["ProfileTab:Password"],
typeof(AccountProfilePasswordManagementGroupViewComponent)
)
);
}
context.Groups.Add(
new ProfileManagementPageGroup(
"Volo.Abp.Account.PersonalInfo",
l["ProfileTab:PersonalInfo"],
typeof(AccountProfilePersonalInfoManagementGroupViewComponent)
)
);
}
protected virtual async Task<bool> IsPasswordChangeEnabled(ProfileManagementPageCreationContext context)
{
var userManager = context.ServiceProvider.GetRequiredService<IdentityUserManager>();
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
var user = await userManager.GetByIdAsync(currentUser.GetId());
return !user.IsExternal;
}
}
}

9
modules/account/src/Volo.Abp.Account.Web/ProfileManagement/IProfileManagementPageContributor.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.Account.Web.ProfileManagement
{
public interface IProfileManagementPageContributor
{
Task ConfigureAsync(ProfileManagementPageCreationContext context);
}
}

19
modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageCreationContext.cs

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.Account.Web.ProfileManagement
{
public class ProfileManagementPageCreationContext
{
public IServiceProvider ServiceProvider { get; }
public List<ProfileManagementPageGroup> Groups { get; }
public ProfileManagementPageCreationContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Groups = new List<ProfileManagementPageGroup>();
}
}
}

39
modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageGroup.cs

@ -0,0 +1,39 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.Account.Web.ProfileManagement
{
public class ProfileManagementPageGroup
{
public string Id
{
get => _id;
set => _id = Check.NotNullOrWhiteSpace(value, nameof(Id));
}
private string _id;
public string DisplayName
{
get => _displayName;
set => _displayName = Check.NotNullOrWhiteSpace(value, nameof(DisplayName));
}
private string _displayName;
public Type ComponentType
{
get => _componentType;
set => _componentType = Check.NotNull(value, nameof(ComponentType));
}
private Type _componentType;
public object Parameter { get; set; }
public ProfileManagementPageGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object parameter = null)
{
Id = id;
DisplayName = displayName;
ComponentType = componentType;
Parameter = parameter;
}
}
}

14
modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageOptions.cs

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Volo.Abp.Account.Web.ProfileManagement
{
public class ProfileManagementPageOptions
{
public List<IProfileManagementPageContributor> Contributors { get; }
public ProfileManagementPageOptions()
{
Contributors = new List<IProfileManagementPageContributor>();
}
}
}

2
modules/cms-kit/common.props

@ -2,7 +2,7 @@
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>0.1.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<AbpProjectType>module</AbpProjectType>
</PropertyGroup>

7
modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Security.Claims;
using IdentityModel;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
@ -108,12 +109,12 @@ namespace Volo.CmsKit
AbpClaimTypes.Role = JwtClaimTypes.Role;
AbpClaimTypes.Email = JwtClaimTypes.Email;
context.Services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "CmsKit";
options.Audience = "CmsKit";
});
Configure<AbpDistributedCacheOptions>(options =>

3
modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj

@ -13,7 +13,8 @@
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="IdentityModel" Version="4.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.6" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="3.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6" />
<PackageReference Include="Volo.Abp.Autofac" Version="3.0.3" />

4
modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs

@ -123,11 +123,11 @@ namespace Volo.CmsKit
});
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = configuration["AuthServer:ApiName"];
options.Audience = configuration["AuthServer:ApiName"];
});
Configure<AbpDistributedCacheOptions>(options =>

1
modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj

@ -11,7 +11,6 @@
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="3.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6" />
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="3.0.3" />

1
modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs

@ -1,4 +1,5 @@
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

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

Loading…
Cancel
Save