Browse Source

Merge branch 'master' of https://github.com/abpframework/abp into docs/bookstore-tutorial

pull/3734/head
mehmet-erim 7 years ago
parent
commit
d8e19fa8b6
  1. 30
      .github/workflows/main.yml
  2. 13
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ru.json
  3. 14
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 90
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ru.json
  5. 2
      common.props
  6. 3
      docs/en/AutoMapper-Integration.md
  7. 26
      docs/en/Best-Practices/Application-Services.md
  8. 1
      docs/en/Best-Practices/Data-Transfer-Objects.md
  9. 6
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  10. 102
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  11. 2
      docs/en/Entity-Framework-Core-Migrations.md
  12. 4
      docs/en/Exception-Handling.md
  13. 128
      docs/en/Getting-Started-Angular-Template.md
  14. 106
      docs/en/Getting-Started-AspNetCore-MVC-Template.md
  15. 6
      docs/en/Getting-Started-With-Startup-Templates.md
  16. 407
      docs/en/Getting-Started.md
  17. 4
      docs/en/How-To/Customize-SignIn-Manager.md
  18. 6
      docs/en/Multi-Tenancy.md
  19. 15
      docs/en/Nightly-Builds.md
  20. 364
      docs/en/Object-Extensions.md
  21. 17
      docs/en/Object-To-Object-Mapping.md
  22. 2
      docs/en/Tutorials/Angular/Part-I.md
  23. 2
      docs/en/Tutorials/Angular/Part-II.md
  24. 2
      docs/en/Tutorials/Angular/Part-III.md
  25. 2
      docs/en/Tutorials/AspNetCore-Mvc/Part-I.md
  26. 2
      docs/en/Tutorials/AspNetCore-Mvc/Part-II.md
  27. 2
      docs/en/Tutorials/AspNetCore-Mvc/Part-III.md
  28. 10
      docs/en/Tutorials/Part-1.md
  29. 6
      docs/en/Tutorials/Part-2.md
  30. 30
      docs/en/UI/Angular/Component-Replacement.md
  31. 163
      docs/en/UI/Angular/Confirmation-Service.md
  32. 101
      docs/en/UI/Angular/Container-Strategy.md
  33. 78
      docs/en/UI/Angular/Content-Projection-Service.md
  34. 117
      docs/en/UI/Angular/Context-Strategy.md
  35. 71
      docs/en/UI/Angular/Dom-Insertion-Service.md
  36. 1
      docs/en/UI/Angular/Dom-Strategy.md
  37. 2
      docs/en/UI/Angular/HTTP-Requests.md
  38. 2
      docs/en/UI/Angular/Lazy-Load-Service.md
  39. 2
      docs/en/UI/Angular/Loading-Strategy.md
  40. 2
      docs/en/UI/Angular/Permission-Management.md
  41. 200
      docs/en/UI/Angular/Projection-Strategy.md
  42. 157
      docs/en/UI/Angular/Toaster-Service.md
  43. BIN
      docs/en/UI/Angular/images/confirmation.png
  44. BIN
      docs/en/UI/Angular/images/toast.png
  45. 278
      docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md
  46. 261
      docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md
  47. 14
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  48. 2
      docs/en/UI/AspNetCore/Tag-Helpers/List-Groups.md
  49. 4
      docs/en/UI/AspNetCore/Tag-Helpers/Progress-Bars.md
  50. 296
      docs/en/UI/Common/Utils/Linked-List.md
  51. 49
      docs/en/docs-nav.json
  52. BIN
      docs/en/images/bookstore-home.png
  53. BIN
      docs/en/images/bookstore-login.png
  54. BIN
      docs/en/images/db-migrator-output.png
  55. BIN
      docs/en/images/package-manager-console-update-database.png
  56. BIN
      docs/en/images/rn-environment-local-ip.png
  57. BIN
      docs/en/images/rn-expo-interface.png
  58. BIN
      docs/en/images/rn-host-local-ip.png
  59. BIN
      docs/en/images/rn-login-iphone.png
  60. BIN
      docs/en/images/rn-tiered-local-ip.png
  61. BIN
      docs/en/images/solution-files-mvc.png
  62. BIN
      docs/en/images/solution-files-non-mvc.png
  63. BIN
      docs/en/images/swagger-ui.png
  64. BIN
      docs/en/images/vs-app-solution-structure-mongodb.png
  65. BIN
      docs/en/images/vs-app-solution-structure-tiered.png
  66. BIN
      docs/en/images/vs-app-solution-structure.png
  67. BIN
      docs/en/images/vs-spa-app-backend-structure-mongodb.png
  68. BIN
      docs/en/images/vs-spa-app-backend-structure.png
  69. 2
      docs/pt-BR/Tutorials/Angular/Part-I.md
  70. 4
      docs/zh-Hans/API/Auto-API-Controllers.md
  71. 2
      docs/zh-Hans/Application-Services.md
  72. 2
      docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md
  73. 32
      docs/zh-Hans/Audit-Logging.md
  74. 2
      docs/zh-Hans/Authorization.md
  75. 3
      docs/zh-Hans/AutoMapper-Integration.md
  76. 4
      docs/zh-Hans/Background-Workers.md
  77. 18
      docs/zh-Hans/Best-Practices/Application-Services.md
  78. 1
      docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md
  79. 14
      docs/zh-Hans/Blog-Posts/2019-02-22/Post.md
  80. 12
      docs/zh-Hans/Blog-Posts/2019-06-19 v0_18_Release/Post.md
  81. 4
      docs/zh-Hans/Blog-Posts/2019-08-16 v0_19_Release/Post.md
  82. 4
      docs/zh-Hans/Blog-Posts/2019-09-25 v0_21_Release/Post.md
  83. 16
      docs/zh-Hans/Blog-Posts/2020-03-19 v2_3_Release/Post.md
  84. 14
      docs/zh-Hans/CLI.md
  85. 4
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  86. 104
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  87. 10
      docs/zh-Hans/Entities.md
  88. 14
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  89. 6
      docs/zh-Hans/Entity-Framework-Core-MySQL.md
  90. 2
      docs/zh-Hans/Entity-Framework-Core-Other-DBMS.md
  91. 4
      docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md
  92. 4
      docs/zh-Hans/Entity-Framework-Core-SQLite.md
  93. 10
      docs/zh-Hans/Entity-Framework-Core.md
  94. 4
      docs/zh-Hans/Exception-Handling.md
  95. 4
      docs/zh-Hans/Getting-Started-AspNetCore-Application.md
  96. 2
      docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md
  97. 2
      docs/zh-Hans/Getting-Started-Console-Application.md
  98. 197
      docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md
  99. 100
      docs/zh-Hans/How-To/Customize-SignIn-Manager.md
  100. 2
      docs/zh-Hans/Index.md

30
.github/workflows/main.yml

@ -0,0 +1,30 @@
name: "Main"
on:
pull_request:
paths:
- "framework/**"
- "modules/**"
- "templates/**"
push:
paths:
- "framework/**"
- "modules/**"
- "templates/**"
jobs:
build-test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
with:
dotnet-version: 3.1.100
- name: Build All
run: .\build-all.ps1
working-directory: .\build
shell: powershell
- name: Test All
run: .\test-all.ps1
working-directory: .\build
shell: powershell

13
abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ru.json

@ -0,0 +1,13 @@
{
"culture": "ru",
"texts": {
"Account": "Аккаунт",
"Welcome": "Добро пожаловать",
"UseOneOfTheFollowingLinksToContinue": "Для продолжения используйте одну из следующих ссылок",
"FrameworkHomePage": "Главная страница фреймворка",
"FrameworkDocumentation": "Документация фреймворка",
"OfficialBlog": "Официальный блог",
"CommercialHomePage": "Главная страница коммерческой версии",
"CommercialSupportWebSite": "Сайт коммерческой поддержки"
}
}

14
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -14,6 +14,7 @@
"Permission:Delete": "Delete", "Permission:Delete": "Delete",
"Permission:Create": "Create", "Permission:Create": "Create",
"Menu:Organizations": "Organizations", "Menu:Organizations": "Organizations",
"Menu:Accounting": "Accounting",
"Menu:Packages": "Packages", "Menu:Packages": "Packages",
"NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?", "NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?",
"NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?", "NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?",
@ -75,7 +76,8 @@
"AddDeveloper": "Add developer", "AddDeveloper": "Add developer",
"Create": "Create", "Create": "Create",
"UserNotFound": "User not found", "UserNotFound": "User not found",
"{0}WillBeRemovedFromMembers": "{0} Will be removed from members", "{0}WillBeRemovedFromDevelopers": "{0} Will be removed from developers, do you confirm?",
"{0}WillBeRemovedFromOwners": "{0} Will be removed from owners, do you confirm?",
"Computers": "Computers", "Computers": "Computers",
"UniqueComputerId": "Unique computer id", "UniqueComputerId": "Unique computer id",
"LastSeenDate": "Last seen date", "LastSeenDate": "Last seen date",
@ -91,6 +93,14 @@
"OrganizationNamePlaceholder": "Organization name...", "OrganizationNamePlaceholder": "Organization name...",
"UsernameOrEmail": "Username or email", "UsernameOrEmail": "Username or email",
"UsernameOrEmailPlaceholder": "Username or email...", "UsernameOrEmailPlaceholder": "Username or email...",
"Member": "Member" "Member": "Member",
"QuotationPurchasedOrderNo": "Quotation Purchased Order No",
"QuotationTime": "Quotation Time",
"CompanyName": "Company Name",
"CompanyAddress": "Company Address",
"Price": "Price",
"ExtraText": "Extra Text",
"ExtraAmount": "Extra Amount",
"DownloadQuotation": "Download Quotation"
} }
} }

90
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ru.json

@ -0,0 +1,90 @@
{
"culture": "ru",
"texts": {
"Permission:Organizations": "Организации",
"Permission:Manage": "Управление организациями",
"Permission:NpmPackages": "Пакеты NPM",
"Permission:NugetPackages": "Пакеты NuGet",
"Permission:Maintenance": "Обслуживание",
"Permission:Maintain": "Обслуживать",
"Permission:ClearCaches": "Очистить кэш",
"Permission:Modules": "Модули",
"Permission:Packages": "Пакеты",
"Permission:Edit": "Редактировать",
"Permission:Delete": "Удалить",
"Permission:Create": "Создать",
"Menu:Organizations": "Организации",
"Menu:Packages": "Пакеты",
"NpmPackageDeletionWarningMessage": "Этот пакет NPM будет удален. Вы подтверждаете это?",
"NugetPackageDeletionWarningMessage": "Этот пакет NuGet будет удален. Вы подтверждаете это?",
"ModuleDeletionWarningMessage": "Этот модуль будет удален. Вы подтверждаете это?",
"Name": "Имя",
"DisplayName": "Отображаемое имя",
"ShortDescription": "Краткое описание",
"NameFilter": "Имя",
"CreationTime": "Время создания",
"IsPro": "Is pro",
"EfCoreConfigureMethodName": "Настроить имя метода",
"IsProFilter": "Is pro",
"ApplicationType": "Тип приложения",
"Target": "Цель",
"TargetFilter": "Цель",
"ModuleClass": "Класс модуля",
"NugetPackageTarget.DomainShared": "Domain Shared",
"NugetPackageTarget.Domain": "Domain",
"NugetPackageTarget.Application": "Application",
"NugetPackageTarget.ApplicationContracts": "Application Contracts",
"NugetPackageTarget.HttpApi": "Http Api",
"NugetPackageTarget.HttpApiClient": "Http Api Client",
"NugetPackageTarget.Web": "Web",
"NugetPackageTarget.EntityFrameworkCore": "DeleteAllEntityFramework Core",
"NugetPackageTarget.MongoDB": "MongoDB",
"Edit": "Редактировать",
"Delete": "Удалить",
"Refresh": "Обновить",
"NpmPackages": "NPM пакеты",
"NugetPackages": "NuGet пакеты",
"NpmPackageCount": "Количество пакетов NPM",
"NugetPackageCount": "Количество пакетов NuGet",
"Module": "Модули",
"ModuleInfo": "Информация о модуле",
"CreateANpmPackage": "Создать пакет NPM",
"CreateAModule": "Создать модуль",
"CreateANugetPackage": "Создать пакет NuGet",
"AddNew": "Добавить новый",
"PackageAlreadyExist{0}": "\"{0}\" пакет уже существует.",
"ModuleAlreadyExist{0}": "\"{0}\" модуль уже добавлен.",
"ClearCache": "Очистить кэш",
"SuccessfullyCleared": "Успешно очищено",
"Menu:NpmPackages": "Пакеты NPM",
"Menu:Modules": "Модули",
"Menu:Maintenance": "Поддержка",
"Menu:NugetPackages": "Пакеты NuGet",
"CreateAnOrganization": "Создать организацию",
"Organizations": "Организации",
"LongName": "Полное название",
"LicenseType": "Тип лицензии",
"LicenseStartTime": "Время начала действия лицензии",
"LicenseEndTime": "Время окончания действия лицензии",
"AllowedDeveloperCount": "Разрешенное количество разработчиков",
"UserNameOrEmailAddress": "Имя пользователя или адрес электронной почты",
"AddOwner": "Добавить владельца",
"UserName": "Имя пользователя",
"Email": "Электронная почта",
"Developers": "Разработчики",
"AddDeveloper": "Добавить разработчика",
"Create": "Создать",
"UserNotFound": "Пользователь не обнаружен",
"{0}WillBeRemovedFromMembers": "{0} будет удален из членов",
"Computers": "Компьютеры",
"UniqueComputerId": "Уникальный id компьютера",
"LastSeenDate": "Дата последнего визита",
"{0}Computer{1}WillBeRemovedFromRecords": "Компьютер {0} ({1}) будет удален из записей",
"OrganizationDeletionWarningMessage": "Организация будет удалена",
"This{0}AlreadyExistInThisOrganization": "{0} уже существует в данной организации",
"AreYouSureYouWantToDeleteAllComputers": "Вы уверены, что хотите удалить все компьютеры?",
"DeleteAll": "Удалить все",
"DoYouWantToCreateNewUser": "Вы хотите создать нового пользователя?",
"MasterModules": "Мастер модулей"
}
}

2
common.props

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

3
docs/en/AutoMapper-Integration.md

@ -1,3 +0,0 @@
## AutoMapper Integration
TODO

26
docs/en/Best-Practices/Application-Services.md

@ -17,17 +17,18 @@
##### Basic DTO ##### Basic DTO
**Do** define a **basic** DTO for an entity. **Do** define a **basic** DTO for an aggregate root.
- Include all the **primitive properties** directly on the entity. - Include all the **primitive properties** directly on the aggregate root.
- Exception: Can **exclude** properties for **security** reasons (like User.Password). - Exception: Can **exclude** properties for **security** reasons (like `User.Password`).
- Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**. - Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**.
- Inherit from one of the **extensible entity DTO** classes for aggregate roots (and entities implement the `IHasExtraProperties`).
Example: Example:
```c# ```c#
[Serializable] [Serializable]
public class IssueDto : FullAuditedEntityDto<Guid> public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -57,7 +58,7 @@ Example:
````C# ````C#
[Serializable] [Serializable]
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid> public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -66,14 +67,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
} }
[Serializable] [Serializable]
public class MilestoneDto : EntityDto<Guid> public class MilestoneDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public bool IsClosed { get; set; } public bool IsClosed { get; set; }
} }
[Serializable] [Serializable]
public class LabelDto : EntityDto<Guid> public class LabelDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public string Color { get; set; } public string Color { get; set; }
@ -120,6 +121,7 @@ Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
* **Do** use the `CreateAsync` **method name**. * **Do** use the `CreateAsync` **method name**.
* **Do** get a **specialized input** DTO to create the entity. * **Do** get a **specialized input** DTO to create the entity.
* **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed.
* **Do** use **data annotations** for input validation. * **Do** use **data annotations** for input validation.
* Share constants between domain wherever possible (via constants defined in the **domain shared** package). * Share constants between domain wherever possible (via constants defined in the **domain shared** package).
* **Do** return **the detailed** DTO for new created entity. * **Do** return **the detailed** DTO for new created entity.
@ -135,10 +137,11 @@ The related **DTO**:
````C# ````C#
[Serializable] [Serializable]
public class CreateQuestionDto public class CreateQuestionDto : ExtensibleObject
{ {
[Required] [Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)] [StringLength(QuestionConsts.MaxTitleLength,
MinimumLength = QuestionConsts.MinTitleLength)]
public string Title { get; set; } public string Title { get; set; }
[StringLength(QuestionConsts.MaxTextLength)] [StringLength(QuestionConsts.MaxTextLength)]
@ -152,6 +155,7 @@ public class CreateQuestionDto
- **Do** use the `UpdateAsync` **method name**. - **Do** use the `UpdateAsync` **method name**.
- **Do** get a **specialized input** DTO to update the entity. - **Do** get a **specialized input** DTO to update the entity.
- **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed.
- **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO. - **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO.
- **Do** use **data annotations** for input validation. - **Do** use **data annotations** for input validation.
- Share constants between domain wherever possible (via constants defined in the **domain shared** package). - Share constants between domain wherever possible (via constants defined in the **domain shared** package).
@ -200,6 +204,10 @@ This method votes a question and returns the current score of the question.
* **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source. * **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source.
#### Extra Properties
* **Do** use either `MapExtraPropertiesTo` extension method ([see](Object-Extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services.
#### Manipulating / Deleting Entities #### Manipulating / Deleting Entities
* **Do** always get all the related entities from repositories to perform the operations on them. * **Do** always get all the related entities from repositories to perform the operations on them.

1
docs/en/Best-Practices/Data-Transfer-Objects.md

@ -2,6 +2,7 @@
* **Do** define DTOs in the **application contracts** package. * **Do** define DTOs in the **application contracts** package.
* **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` and so on). * **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` and so on).
* **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto<TKey>`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way.
* **Do** define DTO members with **public getter and setter**. * **Do** define DTO members with **public getter and setter**.
* **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service. * **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service.
* **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary. * **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary.

6
docs/en/Customizing-Application-Modules-Extending-Entities.md

@ -50,7 +50,7 @@ ObjectExtensionManager.Instance
* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table). * You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table).
* You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties). * You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties).
> This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEntityExtensions`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself. > This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEfCoreEntityExtensionMappings`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself.
Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database. Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database.
@ -58,8 +58,6 @@ You can then use the same extra properties system defined in the previous sectio
## Creating a New Entity Maps to the Same Database Table/Collection ## Creating a New Entity Maps to the Same Database Table/Collection
While using the extra properties approach is **easy to use** and suitable for some scenarios, it has some drawbacks described in the [entities document](Entities.md).
Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database). Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database).
`AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage **EF Core database migrations** in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems. `AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage **EF Core database migrations** in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems.
@ -176,4 +174,4 @@ public class MyDistributedIdentityUserCreatedEventHandler :
## See Also ## See Also
* [Migration System for the EF Core](Entity-Framework-Core-Migrations.md) * [Migration System for the EF Core](Entity-Framework-Core-Migrations.md)
* [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md) * [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md)

102
docs/en/Customizing-Application-Modules-Overriding-Services.md

@ -60,6 +60,7 @@ In most cases, you will want to change one or a few methods of the current imple
````csharp ````csharp
[Dependency(ReplaceServices = true)] [Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))]
public class MyIdentityUserAppService : IdentityUserAppService public class MyIdentityUserAppService : IdentityUserAppService
{ {
//... //...
@ -161,6 +162,105 @@ Check the [localization system](Localization.md) to learn how to localize the er
Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above. Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above.
## Extending Data Transfer Objects
**Extending [entities](Entities.md)** is possible as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md). In this way, you can add **custom properties** to entities and perform **additional business logic** by overriding the related services as described above.
It is also possible to extend Data Transfer Objects (**DTOs**) used by the application services. In this way, you can get extra properties from the UI (or client) and return extra properties from the service.
### Example
Assuming that you've already added a `SocialSecurityNumber` as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md) and want to include this information while getting the list of users from the `GetListAsync` method of the `IdentityUserAppService`.
You can use the [object extension system](Object-Extensions.md) to add the property to the `IdentityUserDto`. Write this code inside the `YourProjectNameDtoExtensions` class comes with the application startup template:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserDto, string>(
"SocialSecurityNumber"
);
````
This code defines a `SocialSecurityNumber` to the `IdentityUserDto` class as a `string` type. That's all. Now, if you call the `/api/identity/users` HTTP API (which uses the `IdentityUserAppService` internally) from a REST API client, you will see the `SocialSecurityNumber` value in the `extraProperties` section.
````json
{
"totalCount": 1,
"items": [{
"tenantId": null,
"userName": "admin",
"name": "admin",
"surname": null,
"email": "admin@abp.io",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"twoFactorEnabled": false,
"lockoutEnabled": true,
"lockoutEnd": null,
"concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c",
"isDeleted": false,
"deleterId": null,
"deletionTime": null,
"lastModificationTime": "2020-04-09T21:25:47.0740706",
"lastModifierId": null,
"creationTime": "2020-04-09T21:25:46.8308744",
"creatorId": null,
"id": "8edecb8f-1894-a9b1-833b-39f4725db2a3",
"extraProperties": {
"SocialSecurityNumber": "123456789"
}
}]
}
````
Manually added the `123456789` value to the database for now.
All pre-built modules support extra properties in their DTOs, so you can configure easily.
### Definition Check
When you [define](Customizing-Application-Modules-Extending-Entities.md) an extra property for an entity, it doesn't automatically appear in all the related DTOs, because of the security. The extra property may contain a sensitive data and you may not want to expose it to the clients by default.
So, you need to explicitly define the same property for the corresponding DTO if you want to make it available for the DTO (as just done above). If you want to allow to set it on user creation, you also need to define it for the `IdentityUserCreateDto`.
If the property is not so secure, this can be tedious. Object extension system allows you to ignore this definition check for a desired property. See the example below:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
options.CheckPairDefinitionOnMapping = false;
}
);
````
This is another approach to define a property for an entity (`ObjectExtensionManager` has more, see [its document](Object-Extensions.md)). This time, we set `CheckPairDefinitionOnMapping` to false to skip definition check while mapping entities to DTOs and vice verse.
If you don't like this approach but want to add a single property to multiple objects (DTOs) easier, `AddOrUpdateProperty` can get an array of types to add the extra property:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
### About the User Interface
This system allows you to add extra properties to entities and DTOs and execute custom business code, however it does nothing related to the User Interface.
See [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md) guide for the UI part.
## How to Find the Services? ## How to Find the Services?
[Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services. [Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services.

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

@ -882,4 +882,4 @@ This document explains how to split your databases and manage your database migr
## Source Code ## Source Code
You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code. You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code.

4
docs/en/Exception-Handling.md

@ -315,8 +315,8 @@ The `context` object contains necessary information about the exception occurred
Some exception types are automatically thrown by the framework: Some exception types are automatically thrown by the framework:
- `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See authorization document (TODO: link) for more. - `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See [authorization](Authorization.md) for more.
- `AbpValidationException` is thrown if the input of the current request is not valid. See validation document (TODO: link) for more. - `AbpValidationException` is thrown if the input of the current request is not valid. See [validation](Validation.md) for more.
- `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](Repositories.md). - `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](Repositories.md).
You can also throw these type of exceptions in your code (although it's rarely needed). You can also throw these type of exceptions in your code (although it's rarely needed).

128
docs/en/Getting-Started-Angular-Template.md

@ -1,126 +1,8 @@
## Getting Started With the Angular Application Template # Getting Started with the Startup Templates
This tutorial explains how to create a new Angular application using the startup template, configure and run it. See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates:
### Creating a New Project * [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Getting Started with the Angular UI](Getting-Started?UI=NG&DB=EF&Tiered=No)
This tutorial uses **ABP CLI** to create a new project. See the [Get Started](https://abp.io/get-started) page for other options. <!-- TODO: this document has been moved, it should be deleted in the future. -->
Install the ABP CLI using a command line window, if you've not installed before:
````bash
dotnet tool install -g Volo.Abp.Cli
````
Use `abp new` command in an empty folder to create your project:
````bash
abp new Acme.BookStore -u angular
````
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
`-u angular` option specifies the UI framework to be Angular. Default database provider is EF Core. See the [CLI documentation](CLI.md) for all available options.
#### Pre Requirements
The created solution requires;
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
### The Solution Structure
Open the solution in **Visual Studio**:
![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-for-spa.png)
The solution has a layered structure (based on [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects properly configured to work with **EF Core** & **SQLite in-memory** database.
> See the [Application Template Document](Startup-Templates/Application.md) to understand the solution structure in details.
### Database Connection String
Check the **connection string** in the `appsettings.json` file under the `.HttpApi.Host` project:
````json
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server**. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use another DBMS if you want. Change the connection string if you need.
### Create Database & Apply Database Migrations
You have two options to create the database.
#### Using the DbMigrator Application
The solution contains a console application (named `Acme.BookStore.DbMigrator` in this sample) that can create database, apply migrations and seed initial data. It is useful on development as well as on production environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**:
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![set-as-startup-project](images/db-migrator-app.png)
#### Using EF Core Update-Database Command
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations. Right click to the `.HttpApi.Host` project and select **Set as StartUp Project**:
![set-as-startup-project](images/set-as-startup-project.png)
Open the **Package Manager Console**, select `.EntityFrameworkCore.DbMigrations` project as the **Default Project** and run the `Update-Database` command:
![pcm-update-database](images/pcm-update-database-v2.png)
This will create a new database based on the configured connection string.
> Using the `.DbMigrator` tool is the suggested way, because it also seeds the initial data to be able to properly run the web application.
### Running the Application
#### Run the API Host (Server Side)
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
![bookstore-homepage](images/bookstore-swagger-ui-host.png)
You can see the application APIs and test them here. Get [more info](https://swagger.io/tools/swagger-ui/) about the Swagger UI.
##### Authorization for the Swagger UI
Most of the application APIs require authentication & authorization. If you want to test authorized APIs, manually go to the `/Account/Login` page, enter `admin` as the username and `1q2w3E*` as the password to login to the application. Then you will be able to execute authorized APIs too.
#### Run the Angular Application (Client Side)
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest the [yarn](https://yarnpkg.com) package manager while `npm install` will also work in most cases)
````bash
yarn
````
Once all node modules are loaded, execute `yarn start` or `npm start` command:
````bash
yarn start
````
Open your favorite browser and go to `localhost:4200` URL. Initial username is `admin` and password is `1q2w3E*`.
The startup template includes the **identity management** and **tenant management** modules. Once you login, the Administration menu will be available where you can manage **tenants**, **roles**, **users** and their **permissions**.
> We recommend [Visual Studio Code](https://code.visualstudio.com/) as the editor for the Angular project, but you are free to use your favorite editor.
### What's Next?
* [Application development tutorial](Tutorials/Part-1)

106
docs/en/Getting-Started-AspNetCore-MVC-Template.md

@ -1,104 +1,8 @@
## Getting Started With the ASP.NET Core MVC Template # Getting Started with the Startup Templates
This tutorial explains how to create a new ASP.NET Core MVC web application using the startup template, configure and run it. See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates:
### Creating a New Project * [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Getting Started with the Angular UI](Getting-Started?UI=NG&DB=EF&Tiered=No)
This tutorial uses **ABP CLI** to create a new project. See the [Get Started](https://abp.io/get-started) page for other options. <!-- TODO: this document has been moved, it should be deleted in the future. -->
Install the ABP CLI using a command line window, if you've not installed before:
````bash
dotnet tool install -g Volo.Abp.Cli
````
Use `abp new` command in an empty folder to create your project:
````bash
abp new Acme.BookStore
````
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
`new` command creates a **layered MVC application** with **Entity Framework Core** as the database provider. However, it has additional options. See the [CLI documentation](CLI.md) for all available options.
#### Pre Requirements
The created solution requires;
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
### The Solution Structure
Open the solution in **Visual Studio**:
![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png)
The solution has a layered structure (based on [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects properly configured to work with **EF Core** & **SQLite in-memory** database.
> See [Application template document](Startup-Templates/Application.md) to understand the solution structure in details.
### Database Connection String
Check the **connection string** in the `appsettings.json` file under the `.Web` project:
````json
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server**. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use another DBMS if you want. Change the connection string if you need.
### Create Database & Apply Database Migrations
You have two options to create the database.
#### Using the DbMigrator Application
The solution contains a console application (named `Acme.BookStore.DbMigrator` in this sample) that can create database, apply migrations and seed initial data. It is useful on development as well as on production environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**:
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![set-as-startup-project](images/db-migrator-app.png)
#### Using EF Core Update-Database Command
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations. Right click to the `.Web` project and select **Set as StartUp Project**:
![set-as-startup-project](images/set-as-startup-project.png)
Open the **Package Manager Console**, select `.EntityFrameworkCore.DbMigrations` project as the **Default Project** and run the `Update-Database` command:
![pcm-update-database](images/pcm-update-database-v2.png)
This will create a new database based on the configured connection string.
> Using the `.Migrator` tool is the suggested way, because it also seeds the initial data to be able to properly run the web application.
### Running the Application
Ensure that the `.Web` project is the startup project. Run the application which will open the **home** page in your browser:
![bookstore-homepage](images/bookstore-homepage.png)
Click the **Login** button, enter `admin` as the username and `1q2w3E*` as the password to login to the application.
The startup template includes the **identity management** and **tenant management** modules. Once you login, the Administration menu will be available where you can manage **tenants**, **roles**, **users** and their **permissions**. User management page is shown below:
![bookstore-user-management](images/bookstore-user-management-v2.png)
### What's Next?
* [Application development tutorial](Tutorials/Part-1.md)

6
docs/en/Getting-Started-With-Startup-Templates.md

@ -2,5 +2,7 @@
See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates: See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates:
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md) * [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Getting Started with the Angular UI](Getting-Started-Angular-Template.md) * [Getting Started with the Angular UI](Getting-Started?UI=NG&DB=EF&Tiered=No)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

407
docs/en/Getting-Started.md

@ -0,0 +1,407 @@
# Getting started
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
This tutorial explains how to create a new {{if UI == "MVC"}} ASP.NET Core MVC web {{else if UI == "NG"}} Angular {{end}} application using the startup template, configure and run it.
## Setup your development environment
First things first! Let's setup your development environment before creating the first project.
### Pre-requirements
The following tools should be installed on your development machine:
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
> You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core.
### Install the ABP CLI
[ABP CLI](./CLI.md) is a command line interface that is used to authenticate and automate some tasks for ABP based applications.
> ABP CLI is a free & open source tool for [the ABP framework](https://abp.io/).
First, you need to install the ABP CLI using the following command:
````shell
dotnet tool install -g Volo.Abp.Cli
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update -g Volo.Abp.Cli
````
## Create a new project
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
### Using the ABP CLI to create a new project
Use the `new` command of the ABP CLI to create a new project:
````shell
abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}}
````
* `-t` argument specifies the [startup template](Startup-Templates/Application.md) name. `app` is the startup template that contains the essential [ABP Modules](Modules/Index.md) pre-installed and configured for you.
{{ if UI == "NG" }}
* `-u` argument specifies the UI framework, `angular` in this case.
{{ if Tiered == "Yes" }}
* `--separate-identity-server` argument is used to separate the identity server application from the API host application. If not specified, you will have a single endpoint.
{{ end }}
{{ end }}
{{ if DB == "Mongo" }}
* `-d` argument specifies the database provider, `mongodb` in this case.
{{ end }}
{{ if Tiered == "Yes" && UI != "NG" }}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ end }}
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
#### ABP CLI commands & options
[ABP CLI document](./CLI.md) covers all of the available commands and options for the ABP CLI. See the [ABP Startup Templates](Startup-Templates/Index.md) document for other templates.
## The solution structure
{{ if UI == "MVC" }}
After creating your project, you will have the following solution folders & files:
![](images/solution-files-mvc.png)
You will see the following solution structure when you open the `.sln` file in the Visual Studio:
{{if DB == "Mongo"}}
![vs-default-app-solution-structure](images/vs-app-solution-structure-mongodb.png)
{{else}}
![vs-default-app-solution-structure](images/vs-app-solution-structure{{if Tiered == "Yes"}}-tiered{{end}}.png)
{{end}}
{{ else if UI == "NG" }}
There are three folders in the created solution:
![](images/solution-files-non-mvc.png)
* `angular` folder contains the Angular UI application.
* `aspnet-core` folder contains the backend solution.
* `react-native` folder contains the React Native UI application.
Open the `.sln` (Visual Studio solution) file under the `aspnet-core` folder:
![vs-angular-app-backend-solution-structure](images/vs-spa-app-backend-structure{{if DB == "Mongo"}}-mongodb{{end}}.png)
{{ end }}
> ###### About the projects in your solution
>
> Your solution may have slightly different structure based on your **UI**, **database** and other preferences.
The solution has a layered structure (based on [Domain Driven Design](./Domain-Driven-Design.md)) and also contains unit & integration test projects.
{{ if DB == "EF" }}
Integration tests projects are properly configured to work with **EF Core** & **SQLite in-memory** database.
{{ else if DB == "Mongo" }}
Integration tests projects are properly configured to work with in-memory **MongoDB** database created per test (used [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) library).
{{ end }}
> See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
## Create the database
### Database connection string
Check the **connection string** in the `appsettings.json` file under the {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` and `.HttpApi.Host` projects{{else}}`.Web` project{{end}}{{else if UI == "NG" }}`.HttpApi.Host` project{{end}}:
{{ if DB == "EF" }}
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server**. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use any supported DBMS. See [the Entity Framework integration document](https://docs.abp.io/en/abp/latest/Entity-Framework-Core) to learn how to switch to another DBMS.
### Apply the migrations
The solution uses the [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). So, you need to apply migrations to create the database. There are two ways of applying the database migrations.
#### Apply migrations using the DbMigrator
The solution comes with a `.DbMigrator` console application which applies migrations and also seed the initial data. It is useful on development as well as on production environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial seed data creates the `admin` user in the database which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
#### Using EF Core Update-Database command
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations.
{{ if UI == "MVC" }}
Right click to the {{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}} project and select **Set as StartUp project**:
{{ else if UI != "MVC" }}
Right click to the `.HttpApi.Host` project and select **Set as StartUp Project**:
{{ end }}
![set-as-startup-project](images/set-as-startup-project.png)
Open the **Package Manager Console**, select `.EntityFrameworkCore.DbMigrations` project as the **Default Project** and run the `Update-Database` command:
![package-manager-console-update-database](images/package-manager-console-update-database.png)
This will create a new database based on the configured connection string.
> Using the `.Migrator` tool is the suggested way, because it also seeds the initial data to be able to properly run the web application.
{{ else if DB == "Mongo" }}
````json
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/BookStore"
}
````
The solution is configured to use **MongoDB** in your local computer, so you need to have a MongoDB server instance up and running or change the connection string to another MongoDB server.
### Seed initial data
The solution comes with a `.DbMigrator` console application which seeds the initial data. It is useful on development as well as on production environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial seed data creates the `admin` user in the database which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
{{ end }}
## Run the application
{{ if UI == "MVC" }}
{{ if Tiered == "Yes" }}
Ensure that the `.IdentityServer` project is the startup project. Run the application which will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is just the authentication server.
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser.
![swagger-ui](images/swagger-ui.png)
This is the API application that is used by the web application.
Lastly, ensure that the `.Web` project is the startup project and run the application which will open a **welcome** page in your browser
![mvc-tiered-app-home](images/bookstore-home.png)
Click to the **login** button which will redirect you to the `Identity Server` to login to the application:
![bookstore-login](images/bookstore-login.png)
{{ else }}
Ensure that the `.Web` project is the startup project. Run the application which will open the **login** page in your browser:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
![bookstore-login](images/bookstore-login.png)
{{ end }}
{{ else if UI != "MVC" }}
#### Running the HTTP API Host (server-side)
{{ if Tiered == "Yes" }}
Ensure that the `.IdentityServer` project is the startup project. Run the application which will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is just the authentication server.
{{ end }}
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
{{ if Tiered == "No" }}
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
{{ end }}
![swagger-ui](images/swagger-ui.png)
You can see the application APIs and test them here. Get [more info](https://swagger.io/tools/swagger-ui/) about the Swagger UI.
> ##### Authorization for the Swagger UI
>
> Most of the HTTP APIs require authentication & authorization. If you want to test authorized APIs, manually go to the `/Account/Login` page, enter `admin` as the username and `1q2w3E*` as the password to login to the application. Then you will be able to execute authorized APIs too.
{{ end }}
{{ if UI == "NG" }}
#### Running the Angular application (client-side)
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com/) package manager while `npm install` will also work in most cases)
```bash
yarn
```
Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
```bash
yarn start
```
Wait `Angular CLI` to launch `Webpack` dev-server with `BrowserSync`.
This will take care of compiling your `TypeScript` code, and automatically reloading your browser.
After it finishes, `Angular Live Development Server` will be listening on localhost:4200,
open your web browser and navigate to [localhost:4200](http://localhost:4200/)
![bookstore-login](images/bookstore-login.png)
{{ end }}
Enter **admin** as the username and **1q2w3E*** as the password to login to the application:
![bookstore-home](images/bookstore-home.png)
The application is up and running. You can start developing your application based on this startup template.
#### Mobile Development
ABP platform provide [React Native](https://reactnative.dev/) template to develop mobile applications.
>The solution includes the React Native application in the `react-native` folder as default. If you don't plan to develop a mobile application with React Native, you can ignore this step and delete the `react-native` folder.
The React Native application running on an Android emulator or a physical phone cannot connect to the backend on `localhost`. To fix this problem, it is necessary to run backend on the local IP.
{{ if Tiered == "No"}}
![React Native host project local IP entry](images/rn-host-local-ip.png)
* Open the `appsettings.json` in the `.HttpApi.Host` folder. Replace the `localhost` address on the `SelfUrl` and `Authority` properties with your local IP address.
* Open the `launchSettings.json` in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
{{ else if Tiered == "Yes" }}
![React Native tiered project local IP entry](images/rn-tiered-local-ip.png)
* Open the `appsettings.json` in the `.IdentityServer` folder. Replace the `localhost` address on the `SelfUrl` property with your local IP address.
* Open the `launchSettings.json` in the `.IdentityServer/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
* Open the `appsettings.json` in the `.HttpApi.Host` folder. Replace the `localhost` address on the `Authority` property with your local IP address.
* Open the `launchSettings.json` in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address.
{{ end }}
Run the backend as described in the [**Running the HTTP API Host (server-side)**](#running-the-http-api-host-server-side) section.
> React Native application does not trust the auto-generated .NET HTTPS certificate, you should use the HTTP during development.
Go to the `react-native` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com/) package manager while `npm install` will also work in most cases):
```bash
yarn
```
* Open the `Environment.js` in the `react-native` folder and replace the `localhost` address on the `apiUrl` and `issuer` properties with your local IP address as shown below:
![react native environment local IP](images/rn-environment-local-ip.png)
{{ if Tiered == "Yes" }}
> Make sure that `issuer` matches the running address of the `.IdentityServer` project, `apiUrl` matches the running address of the `.HttpApi.Host` project.
{{else}}
> Make sure that `issuer` and `apiUrl` matches the running address of the `.HttpApi.Host` project.
{{ end }}
Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
```bash
yarn start
```
Wait Expo CLI to start. Expo CLI opens the management interface on the `http://localhost:19002/` address.
![expo-interface](images/rn-expo-interface.png)
In the above management interface, you can start the application with an Android emulator, an iOS simulator or a physical phone by the scan the QR code with the [Expo Client](https://expo.io/tools#client).
> See the [Android Studio Emulator](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/), [iOS Simulator](https://docs.expo.io/versions/v36.0.0/workflow/ios-simulator/) documents on expo.io.
![React Native login screen on iPhone 11](images/rn-login-iphone.png)
Enter **admin** as the username and **1q2w3E*** as the password to login to the application.
The application is up and running. You can continue to develop your application based on this startup template.
> The [application startup template](startup-templates/application/index.md) includes the TenantManagement and Identity modules.
## What's next?
[Application development tutorial](tutorials/book-store/part-1.md)

4
docs/en/How-To/Customize-SignIn-Manager.md

@ -42,14 +42,14 @@ public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetE
{ {
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme); var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items; var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey")) if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{ {
return null; return null;
} }
if (expectedXsrf != null) if (expectedXsrf != null)
{ {
if (!items.ContainsKey("XsrfKey")) if (!items.ContainsKey(XsrfKey))
{ {
return null; return null;
} }

6
docs/en/Multi-Tenancy.md

@ -172,11 +172,11 @@ namespace MyCompany.MyProject
{ {
options.Tenants = new[] options.Tenants = new[]
{ {
new TenantInformation( new TenantConfiguration(
Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"), //Id Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"), //Id
"tenant1" //Name "tenant1" //Name
), ),
new TenantInformation( new TenantConfiguration(
Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"), //Id Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"), //Id
"tenant2" //Name "tenant2" //Name
) )
@ -252,7 +252,7 @@ TODO: This package implements ITenantStore using a real database...
#### Tenant Information #### Tenant Information
ITenantStore works with **TenantInformation** class that has several properties for a tenant: ITenantStore works with **TenantConfiguration** class that has several properties for a tenant:
* **Id**: Unique Id of the tenant. * **Id**: Unique Id of the tenant.
* **Name**: Unique name of the tenant. * **Name**: Unique name of the tenant.

15
docs/en/Nightly-Builds.md

@ -24,3 +24,18 @@ Now, you can install preview / nightly packages to your project from Nuget Brows
3. Search a package. You will see prereleases of the package formatted as `(VERSION)-preview(DATE)` (like *v0.16.0-preview20190401* in this sample). 3. Search a package. You will see prereleases of the package formatted as `(VERSION)-preview(DATE)` (like *v0.16.0-preview20190401* in this sample).
4. You can click to the `Install` button to add package to your project. 4. You can click to the `Install` button to add package to your project.
## Install & Uninstall Preview NPM Packages
The latest version of preview NPM packages can be installed by the running below command in the root folder of application:
```bash
abp switch-to-preview
```
If you're using the ABP Framework preview packages, you can switch back to stable version using this command:
```bash
abp switch-to-stable
```
See the [ABP CLI documentation](./CLI.md) for more information.

364
docs/en/Object-Extensions.md

@ -1,3 +1,365 @@
# Object Extensions # Object Extensions
TODO ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module.
> Object extension system is not normally not needed for your own objects since you can easily add regular properties to your own classes.
## IHasExtraProperties Interface
This is the interface to make a class extensible. It simply defines a `Dictionary` property:
````csharp
Dictionary<string, object> ExtraProperties { get; }
````
Then you can add or get extra properties using this dictionary.
### Base Classes
`IHasExtraProperties` interface is implemented by several base classes by default:
* Implemented by the `AggregateRoot` class (see [entities](Entities.md)).
* Implemented by `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... base [DTO](Data-Transfer-Objects.md) classes.
* Implemented by the `ExtensibleObject`, which is a simple base class can be inherited for any type of object.
So, if you inherit from these classes, your class will also be extensible. If not, you can always implement it manually.
### Fundamental Extension Methods
While you can directly use the `ExtraProperties` property of a class, it is suggested to use the following extension methods while working with the extra properties.
#### SetProperty
Used to set the value of an extra property:
````csharp
user.SetProperty("Title", "My Title");
user.SetProperty("IsSuperUser", true);
````
`SetProperty` returns the same object, so you can chain it:
````csharp
user.SetProperty("Title", "My Title")
.SetProperty("IsSuperUser", true);
````
#### GetProperty
Used to read the value of an extra property:
````csharp
var title = user.GetProperty<string>("Title");
if (user.GetProperty<bool>("IsSuperUser"))
{
//...
}
````
* `GetProperty` is a generic method and takes the object type as the generic parameter.
* Returns the default value if given property was not set before (default value is `0` for `int`, `false` for `bool`... etc).
##### Non Primitive Property Types
If your property type is not a primitive (int, bool, enum, string... etc) type, then you need to use non-generic version of the `GetProperty` which returns an `object`.
#### HasProperty
Used to check if the object has a property set before.
#### RemoveProperty
Used to remove a property from the object. Use this methods instead of setting a `null` value for the property.
### Some Best Practices
Using magic strings for the property names is dangerous since you can easily type the property name wrong - it is not type safe. Instead;
* Define a constant for your extra property names
* Create extension methods to easily set your extra properties.
Example:
````csharp
public static class IdentityUserExtensions
{
private const string TitlePropertyName = "Title";
public static void SetTitle(this IdentityUser user, string title)
{
user.SetProperty(TitlePropertyName, title);
}
public static string GetTitle(this IdentityUser user)
{
return user.GetProperty<string>(TitlePropertyName);
}
}
````
Then you can easily set or get the `Title` property:
````csharp
user.SetTitle("My Title");
var title = user.GetTitle();
````
## Object Extension Manager
While you can set arbitrary properties to an extensible object (which implements the `IHasExtraProperties` interface), `ObjectExtensionManager` is used to explicitly define extra properties for extensible classes.
Explicitly defining an extra property has some use cases:
* Allows to control how the extra property is handled on object to object mapping (see the section below).
* Allows to define metadata for the property. For example, you can map an extra property to a table field in the database while using the [EF Core](Entity-Framework-Core.md).
> `ObjectExtensionManager` implements the singleton pattern (`ObjectExtensionManager.Instance`) and you should define object extensions before your application startup. The [application startup template](Startup-Templates/Application.md) has some pre-defined static classes to safely define object extensions inside.
### AddOrUpdate
`AddOrUpdate` is the main method to define a extra properties or update extra properties for an object.
Example: Define extra properties for the `IdentityUser` entity:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUser>(options =>
{
options.AddOrUpdateProperty<string>("SocialSecurityNumber");
options.AddOrUpdateProperty<bool>("IsSuperUser");
}
);
````
### AddOrUpdateProperty
While `AddOrUpdateProperty` can be used on the `options` as shown before, if you want to define a single extra property, you can use the shortcut extension method too:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber");
````
Sometimes it would be practical to define a single extra property to multiple types. Instead of defining one by one, you can use the following code:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
### Property Configuration
`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
//Configure options...
});
````
> `options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document.
The following sections explain the fundamental property configuration options.
#### CheckPairDefinitionOnMapping
Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better.
## Validation
You may want to add some **validation rules** for the extra properties you've defined. `AddOrUpdateProperty` method options allows two ways of performing validation:
1. You can add **data annotation attributes** for a property.
2. You can write an action (code block) to perform a **custom validation**.
Validation works when you use the object in a method that is **automatically validated** (e.g. controller actions, page handler methods, application service methods...). So, all extra properties are validated whenever the extended object is being validated.
### Data Annotation Attributes
All of the standard data annotation attributes are valid for extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.ValidationAttributes.Add(new RequiredAttribute());
options.ValidationAttributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
````
With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided.
### Custom Validation
If you need, you can add a custom action that is executed to validate the extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Validators.Add(context =>
{
var socialSecurityNumber = context.Value as string;
if (socialSecurityNumber == null ||
socialSecurityNumber.StartsWith("X"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Invalid social security number: " + socialSecurityNumber,
new[] { "SocialSecurityNumber" }
)
);
}
});
});
````
`context.ServiceProvider` can be used to resolve a service dependency for advanced scenarios.
In addition to add custom validation logic for a single property, you can add a custom validation logic that is executed in object level. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUserCreateDto>(objConfig =>
{
//Define two properties with their own validation rules
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties
objConfig.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Please repeat the same password!",
new[] { "Password", "PasswordRepeat" }
)
);
}
});
});
````
## Object to Object Mapping
Assume that you've added an extra property to an extensible entity object and used auto [object to object mapping](Object-To-Object-Mapping.md) to map this entity to an extensible DTO class. You need to be careful in such a case, because the extra property may contain a **sensitive data** that should not be available to clients.
This section offers some **good practices** to control your extra properties on object mapping.
### MapExtraPropertiesTo
`MapExtraPropertiesTo` is an extension method provided by the ABP Framework to copy extra properties from an object to another in a controlled manner. Example usage:
````csharp
identityUser.MapExtraPropertiesTo(identityUserDto);
````
`MapExtraPropertiesTo` **requires to define properties** (as described above) in **both sides** (`IdentityUser` and `IdentityUserDto` in this case) in order to copy the value to the target object. Otherwise, it doesn't copy the value even if it does exists in the source object (`identityUser` in this example). There are some ways to overload this restriction.
#### MappingPropertyDefinitionChecks
`MapExtraPropertiesTo` gets an additional parameter to control the definition check for a single mapping operation:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
MappingPropertyDefinitionChecks.None
);
````
> Be careful since `MappingPropertyDefinitionChecks.None` copies all extra properties without any check. `MappingPropertyDefinitionChecks` enum has other members too.
If you want to completely disable definition check for a property, you can do it while defining the extra property (or update an existing definition) as shown below:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
});
````
#### Ignored Properties
You may want to ignore some properties on a specific mapping operation:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
ignoredProperties: new[] {"MySensitiveProp"}
);
````
Ignored properties are not copied to the target object.
#### AutoMapper Integration
If you're using the [AutoMapper](https://automapper.org/) library, the ABP Framework also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above.
You can use the `MapExtraProperties()` method inside your mapping profile.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties();
}
}
````
It has the same parameters with the `MapExtraPropertiesTo` method.
## Entity Framework Core Database Mapping
If you're using the EF Core, you can map an extra property to a table field in the database. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
}
);
````
See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more.

17
docs/en/Object-To-Object-Mapping.md

@ -145,6 +145,23 @@ options.AddProfile<MyProfile>(validate: true);
> If you have multiple profiles and need to enable validation only for a few of them, first use `AddMaps` without validation, then use `AddProfile` for each profile you want to validate. > If you have multiple profiles and need to enable validation only for a few of them, first use `AddMaps` without validation, then use `AddProfile` for each profile you want to validate.
### Mapping the Object Extensions
[Object extension system](Object-Extensions.md) allows to define extra properties for existing classes. ABP Framework provides a mapping definition extension to properly map extra properties of two objects.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<User, UserDto>()
.MapExtraProperties();
}
}
````
It is suggested to use the `MapExtraProperties()` method if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](Object-Extensions.md) for more.
## Advanced Topics ## Advanced Topics
### IObjectMapper<TContext> Interface ### IObjectMapper<TContext> Interface

2
docs/en/Tutorials/Angular/Part-I.md

@ -4,3 +4,5 @@
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) * [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG) * [With Angular UI](../Part-1?UI=NG)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

2
docs/en/Tutorials/Angular/Part-II.md

@ -4,3 +4,5 @@
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) * [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG) * [With Angular UI](../Part-1?UI=NG)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

2
docs/en/Tutorials/Angular/Part-III.md

@ -4,3 +4,5 @@
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) * [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG) * [With Angular UI](../Part-1?UI=NG)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

2
docs/en/Tutorials/AspNetCore-Mvc/Part-I.md

@ -4,3 +4,5 @@
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) * [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG) * [With Angular UI](../Part-1?UI=NG)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

2
docs/en/Tutorials/AspNetCore-Mvc/Part-II.md

@ -4,3 +4,5 @@
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) * [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG) * [With Angular UI](../Part-1?UI=NG)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

2
docs/en/Tutorials/AspNetCore-Mvc/Part-III.md

@ -4,3 +4,5 @@
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) * [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG) * [With Angular UI](../Part-1?UI=NG)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

10
docs/en/Tutorials/Part-1.md

@ -22,7 +22,7 @@ end
### About this tutorial: ### About this tutorial:
In this tutorial series, you will build an ABP Commercial application named `Acme.BookStore`. In this sample project, we will manage a list of books and authors. **{{DB_Text}}** will be used as the ORM provider. And on the front-end side {{UI_Value}} and JavaScript will be used. In this tutorial series, you will build an ABP application named `Acme.BookStore`. In this sample project, we will manage a list of books and authors. **{{DB_Text}}** will be used as the ORM provider. And on the front-end side {{UI_Value}} and JavaScript will be used.
The ASP.NET Core {{UI_Value}} tutorial series consists of 3 parts: The ASP.NET Core {{UI_Value}} tutorial series consists of 3 parts:
@ -34,14 +34,14 @@ The ASP.NET Core {{UI_Value}} tutorial series consists of 3 parts:
### Creating the project ### Creating the project
Create a new project named `Acme.BookStore` where `Acme` is the company name and `BookStore` is the project name. You can check out [creating a new project](../Getting-Started-{{if UI == 'NG'}}Angular{{else}}AspNetCore-MVC{{end}}-Template#creating-a-new-project) document to see how you can create a new project. We will create the project with ABP CLI. But first of all, we need to login to the ABP Platform to create a commercial project. Create a new project named `Acme.BookStore` where `Acme` is the company name and `BookStore` is the project name. You can check out [creating a new project](../Getting-Started-{{if UI == 'NG'}}Angular{{else}}AspNetCore-MVC{{end}}-Template#creating-a-new-project) document to see how you can create a new project. We will create the project with ABP CLI.
#### Create the project #### Create the project
By running the below command, it creates a new ABP Commercial project with the database provider `{{DB_Text}}` and UI option `MVC`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document. By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `MVC`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document.
```bash ```bash
abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none
``` ```
![Creating project](./images/bookstore-create-project-{{UI_Text}}.png) ![Creating project](./images/bookstore-create-project-{{UI_Text}}.png)
@ -1001,11 +1001,13 @@ import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books'; import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service'; import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({ @State<Books.State>({
name: 'BooksState', name: 'BooksState',
defaults: { books: {} } as Books.State, defaults: { books: {} } as Books.State,
}) })
@Injectable()
export class BooksState { export class BooksState {
@Selector() @Selector()
static getBooks(state: Books.State) { static getBooks(state: Books.State) {

6
docs/en/Tutorials/Part-2.md

@ -574,11 +574,13 @@ import { GetBooks, CreateUpdateBook } from '../actions/books.actions'; //<== add
import { Books } from '../models/books'; import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service'; import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({ @State<Books.State>({
name: 'BooksState', name: 'BooksState',
defaults: { books: {} } as Books.State, defaults: { books: {} } as Books.State,
}) })
@Injectable()
export class BooksState { export class BooksState {
@Selector() @Selector()
static getBooks(state: Books.State) { static getBooks(state: Books.State) {
@ -688,7 +690,7 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
* `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits. * `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
* We added `New book` button to the `AbpContentToolbar`. * We added `New book` button to the `AbpContentToolbar`.
Open `book-list.component.` file in `books\book-list` folder and replace the content as below: Open `book-list.component.ts` file in `books\book-list` folder and replace the content as below:
```js ```js
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
@ -1330,11 +1332,13 @@ import { GetBooks, CreateUpdateBook, DeleteBook } from '../actions/books.actions
import { Books } from '../models/books'; import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service'; import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({ @State<Books.State>({
name: 'BooksState', name: 'BooksState',
defaults: { books: {} } as Books.State, defaults: { books: {} } as Books.State,
}) })
@Injectable()
export class BooksState { export class BooksState {
@Selector() @Selector()
static getBooks(state: Books.State) { static getBooks(state: Books.State) {

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

@ -11,15 +11,18 @@ Create a new component that you want to use instead of an ABP component. Add tha
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 dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below:
```js ```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent { export class AppComponent {
constructor(..., private store: Store) {} constructor(..., private store: Store) {} // injected Store
ngOnInit() { ngOnInit() {
this.store.dispatch( this.store.dispatch(
new AddReplaceableComponent({ new AddReplaceableComponent({
component: YourNewRoleComponent, component: YourNewRoleComponent,
key: 'Identity.RolesComponent', key: eIdentityComponents.Roles,
}), }),
); );
//... //...
@ -56,6 +59,7 @@ Open the `app.component.ts` and add the below content:
```js ```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store import { Store } from '@ngxs/store'; // imported Store
//... //...
@ -67,7 +71,7 @@ export class AppComponent {
this.store.dispatch( this.store.dispatch(
new AddReplaceableComponent({ new AddReplaceableComponent({
component: MyApplicationLayoutComponent, component: MyApplicationLayoutComponent,
key: 'Theme.ApplicationLayoutComponent', key: eThemeBasicComponents.ApplicationLayout,
}), }),
); );
@ -76,24 +80,6 @@ export class AppComponent {
} }
``` ```
### Available Replaceable Components
| Component key | Description |
| -------------------------------------------------- | --------------------------------------------- |
| Account.LoginComponent | Login page |
| Account.RegisterComponent | Register page |
| Account.ManageProfileComponent | Manage Profile page |
| Account.AuthWrapperComponent | This component wraps register and login pages |
| Account.ChangePasswordComponent | Change password form |
| Account.PersonalSettingsComponent | Personal settings form |
| Account.TenantBoxComponentInputs | Tenant changing box |
| FeatureManagement.FeatureManagementComponent | Features modal |
| Identity.UsersComponent | Users page |
| Identity.RolesComponent | Roles page |
| PermissionManagement.PermissionManagementComponent | Permissions modal |
| SettingManagement.SettingManagementComponent | Setting Management page |
| TenantManagement.TenantsComponent | Tenants page |
## What's Next? ## What's Next?
- [Custom Setting Page](./Custom-Setting-Page.md) - [Custom Setting Page](./Custom-Setting-Page.md)

163
docs/en/UI/Angular/Confirmation-Service.md

@ -0,0 +1,163 @@
# Confirmation Popup
You can use the `ConfirmationService` in @abp/ng.theme.shared package to display a confirmation popup by placing at the root level in your project.
## Getting Started
You do not have to provide the `ConfirmationService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ConfirmationService } from '@abp/ng.theme.shared';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private confirmation: ConfirmationService) {}
}
```
## Usage
You can use the `success`, `warn`, `error`, and `info` methods of `ConfirmationService` to display a confirmation popup.
### How to Display a Confirmation Popup
```js
const confirmationStatus$ = this.confirmation.success('Message', 'Title');
```
- The `ConfirmationService` methods accept three parameters that are `message`, `title`, and `options`.
- `success`, `warn`, `error`, and `info` methods return an [RxJS Subject](https://rxjs-dev.firebaseapp.com/guide/subject) to listen to confirmation popup closing event. The type of event value is [`Confirmation.Status`](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts#L24) that is an enum.
### How to Listen Closing Event
You can subscribe to the confirmation closing event like below:
```js
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
constructor(private confirmation: ConfirmationService) {}
this.confirmation
.warn('::WillBeDeleted', { key: '::AreYouSure', defaultValue: 'Are you sure?' })
.subscribe((status: Confirmation.Status) => {
// your code here
});
```
- The `message` and `title` parameters accept a string, localization key or localization object. See the [localization document](./Localization.md)
- `Confirmation.Status` is an enum and has three properties;
- `Confirmation.Status.confirm` is a closing event value that will be emitted when the popup is closed by the confirm button.
- `Confirmation.Status.reject` is a closing event value that will be emitted when the popup is closed by the cancel button.
- `Confirmation.Status.dismiss` is a closing event value that will be emitted when the popup is closed by pressing the escape.
If you are not interested in the confirmation status, you do not have to subscribe to the returned observable:
```js
this.confirmation.error('You are not authorized.', 'Error');
```
### How to Display a Confirmation Popup With Given Options
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods:
```js
const options: Partial<Confirmation.Options> = {
hideCancelBtn: false,
hideYesBtn: false,
cancelText: 'Close',
yesText: 'Confirm',
messageLocalizationParams: ['Demo'],
titleLocalizationParams: [],
};
this.confirmation.warn(
'AbpIdentity::RoleDeletionConfirmationMessage',
'Are you sure?',
options,
);
```
- `hideCancelBtn` option hides the cancellation button when `true`. Default value is `false`
- `hideYesBtn` option hides the confirmation button when `true`. Default value is `false`
- `cancelText` is the text of the cancellation button. A localization key or localization object can be passed. Default value is `AbpUi::Cancel`
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`
- `messageLocalizationParams` is the interpolation parameters for the localization of the message.
- `titleLocalizationParams` is the interpolation parameters for the localization of the title.
With the options above, the confirmation popup looks like this:
![confirmation](./images/confirmation.png)
### How to Remove a Confirmation Popup
The open confirmation popup can be removed manually via the `clear` method:
```js
this.confirmation.clear();
```
## API
### success
```js
success(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Confirmation` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts)
### warn
```js
warn(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### error
```js
error(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### info
```js
info(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### clear
```js
clear(
status: Confirmation.Status = Confirmation.Status.dismiss
): void
```
- `status` parameter is the value of the confirmation closing event.
## What's Next?
- [Toast Overlay](./Toaster-Service.md)

101
docs/en/UI/Angular/Container-Strategy.md

@ -0,0 +1,101 @@
# ContainerStrategy
`ContainerStrategy` is an abstract class exposed by @abp/ng.core package. There are two container strategies extending it: `ClearContainerStrategy` and `InsertIntoContainerStrategy`. Implementing the same methods and properties, both of these strategies help you define how your containers will be prepared and where your content will be projected.
## API
`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**.
### constructor
```js
constructor(
public containerRef: ViewContainerRef,
private index?: number, // works only in InsertIntoContainerStrategy
)
```
- `containerRef` is the `ViewContainerRef` that will be used when projecting the content.
### getIndex
```js
getIndex(): number
```
This method return the given index clamped by `0` and `length` of the `containerRef`. For strategies without an index, it returns `0`.
### prepare
```js
prepare(): void
```
This method is called before content projection. Based on used container strategy, it either clears the container or does nothing (noop).
## ClearContainerStrategy
`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**.
## InsertIntoContainerStrategy
`InsertIntoContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **project your content at a specific node index in the container**.
## Predefined Container Strategies
Predefined container strategies are accessible via `CONTAINER_STRATEGY` constant.
### Clear
```js
CONTAINER_STRATEGY.Clear(containerRef: ViewContainerRef)
```
Clears given container before content projection.
### Append
```js
CONTAINER_STRATEGY.Append(containerRef: ViewContainerRef)
```
Projected content will be appended to the container.
### Prepend
```js
CONTAINER_STRATEGY.Prepend(containerRef: ViewContainerRef)
```
Projected content will be prepended to the container.
### Insert
```js
CONTAINER_STRATEGY.Insert(
containerRef: ViewContainerRef,
index: number,
)
```
Projected content will be inserted into to the container at given index (clamped by `0` and `length` of the `containerRef`).
## See Also
- [ProjectionStrategy](./Projection-Strategy.md)

78
docs/en/UI/Angular/Content-Projection-Service.md

@ -0,0 +1,78 @@
# Content Projection
You can use the `ContentProjectionService` in @abp/ng.core package in order to project content in an easy and explicit way.
## Getting Started
You do not have to provide the `ContentProjectionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ContentProjectionService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private contentProjectionService: ContentProjectionService) {}
}
```
## Usage
You can use the `projectContent` method of `ContentProjectionService` to render components and templates dynamically in your project.
### How to Project Components to Root Level
If you pass a `RootComponentProjectionStrategy` as the first parameter of `projectContent` method, the `ContentProjectionService` will resolve the projected component and place it at the root level. If provided, it will also pass the component a context.
```js
const strategy = PROJECTION_STRATEGY.AppendComponentToBody(
SomeOverlayComponent,
{ someOverlayProp: "SOME_VALUE" }
);
const componentRef = this.contentProjectionService.projectContent(strategy);
```
In the example above, `SomeOverlayComponent` component will placed at the **end** of `<body>` and a `ComponentRef` will be returned. Additionally, the given context will be applied, so `someOverlayProp` of the component will be set to `SOME_VALUE`.
> You should keep the returned `ComponentRef` instance, as it is a reference to the projected component and you will need that reference to destroy the projected view and the component instance.
### How to Project Components and Templates into a Container
If you pass a `ComponentProjectionStrategy` or `TemplateProjectionStrategy` as the first parameter of `projectContent` method, and a `ViewContainerRef` as the second parameter of that strategy, the `ContentProjectionService` will project the component or template to the given container. If provided, it will also pass the component or the template a context.
```js
const strategy = PROJECTION_STRATEGY.ProjectComponentToContainer(
SomeComponent,
viewContainerRefOfTarget,
{ someProp: "SOME_VALUE" }
);
const componentRef = this.contentProjectionService.projectContent(strategy);
```
In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeComponent` component will be placed inside it. In addition, the given context will be applied and `someProp` of the component will be set to `SOME_VALUE`.
> You should keep the returned `ComponentRef` or `EmbeddedViewRef`, as they are a reference to the projected content and you will need them to destroy it when necessary.
Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy.
## API
### projectContent
```js
projectContent<T extends Type<any> | TemplateRef<any>>(
projectionStrategy: ProjectionStrategy<T>,
injector = this.injector,
): ComponentRef<C> | EmbeddedViewRef<C>
```
- `projectionStrategy` parameter is the primary focus here and is explained above.
- `injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`.
## What's Next?
- [TrackByService](./Track-By-Service.md)

117
docs/en/UI/Angular/Context-Strategy.md

@ -0,0 +1,117 @@
# ContextStrategy
`ContextStrategy` is an abstract class exposed by @abp/ng.core package. There are three context strategies extending it: `ComponentContextStrategy`, `TemplateContextStrategy`, and `NoContextStrategy`. Implementing the same methods and properties, all of these strategies help you define how projected content will get their context.
## ComponentContextStrategy
`ComponentContextStrategy` is a class that extends `ContextStrategy`. It lets you **pass context to a projected component**.
### constructor
```js
constructor(public context: Partial<InferredInstanceOf<T>>) {}
```
- `T` refers to component type here, i.e. `Type<C>`.
- `InferredInstanceOf` is a utility type exposed by @abp/ng.core package. It infers component shape.
- `context` will be mapped to properties of the projected component.
### setContext
```js
setContext(componentRef: ComponentRef<InferredInstanceOf<T>>): Partial<InferredInstanceOf<T>>
```
This method maps each prop of the context to the component property with the same name and calls change detection. It returns the context after mapping.
## TemplateContextStrategy
`TemplateContextStrategy` is a class that extends `ContextStrategy`. It lets you **pass context to a projected template**.
### constructor
```js
constructor(public context: Partial<InferredContextOf<T>>) {}
```
- `T` refers to template context type here, i.e. `TemplateRef<C>`.
- `InferredContextOf` is a utility type exposed by @abp/ng.core package. It infers context shape.
- `context` will be mapped to properties of the projected template.
### setContext
```js
setContext(): Partial<InferredContextOf<T>>
```
This method does nothing and only returns the context, because template context is not mapped but passed in as parameter to `createEmbeddedView` method.
## NoContextStrategy
`NoContextStrategy` is a class that extends `ContextStrategy`. It lets you **skip passing any context to projected content**.
### constructor
```js
constructor()
```
Unlike other context strategies, `NoContextStrategy` contructor takes no parameters.
### setContext
```js
setContext(): undefined
```
Since there is no context, this method gets no parameters and will return `undefined`.
## Predefined Context Strategies
Predefined context strategies are accessible via `CONTEXT_STRATEGY` constant.
### None
```js
CONTEXT_STRATEGY.None()
```
This strategy will not pass any context to the projected content.
### Component
```js
CONTEXT_STRATEGY.Component(context: Partial<InferredContextOf<T>>)
```
This strategy will help you pass the given context to the projected component.
### Template
```js
CONTEXT_STRATEGY.Template(context: Partial<InferredContextOf<T>>)
```
This strategy will help you pass the given context to the projected template.
## See Also
- [ProjectionStrategy](./Projection-Strategy.md)

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

@ -1,8 +1,7 @@
# How to Insert Scripts and Styles # Dom Insertion (of Scripts and Styles)
You can use the `DomInsertionService` in @abp/ng.core package in order to insert scripts and styles in an easy and explicit way. You can use the `DomInsertionService` in @abp/ng.core package in order to insert scripts and styles in an easy and explicit way.
## Getting Started ## Getting Started
You do not have to provide the `DomInsertionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. You do not have to provide the `DomInsertionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
@ -20,8 +19,7 @@ class DemoComponent {
## Usage ## Usage
You can use the `insertContent` method of `DomInsertionService` to create a `<script>` or `<style>` element with given content in the DOM at the desired position. You can use the `insertContent` method of `DomInsertionService` to create a `<script>` or `<style>` element with given content in the DOM at the desired position. There is also the `projectContent` method for dynamically rendering components and templates.
### How to Insert Scripts ### How to Insert Scripts
@ -37,17 +35,18 @@ class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {} constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() { ngOnInit() {
this.domInsertionService.insertContent( const scriptElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendScriptToBody('alert()') CONTENT_STRATEGY.AppendScriptToBody('alert()')
); );
} }
} }
``` ```
In the example above, `<script>alert()</script>` element will place at the **end** of `<body>`. In the example above, `<script>alert()</script>` element will place at the **end** of `<body>` and `scriptElement` will be an `HTMLScriptElement`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method.
### How to Insert Styles ### How to Insert Styles
@ -63,29 +62,79 @@ class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {} constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() { ngOnInit() {
this.domInsertionService.insertContent( const styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}') CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
); );
} }
} }
``` ```
In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>`. In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>` and `styleElement` will be an `HTMLStyleElement`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method.
### How to Remove Inserted Scripts & Styles
If you pass the inserted `HTMLScriptElement` or `HTMLStyleElement` element as the first parameter of `removeContent` method, the `DomInsertionService` will remove the given element.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
private styleElement: HTMLStyleElement;
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
ngOnDestroy() {
this.domInsertionService.removeContent(this.styleElement);
}
}
```
In the example above, `<style>body {margin: 0;}</style>` element **will be removed** from `<head>` when the component is destroyed.
## API ## API
### insertContent ### insertContent
```js ```js
insertContent(strategy: ContentStrategy): void insertContent<T extends HTMLScriptElement | HTMLStyleElement>(
contentStrategy: ContentStrategy<T>,
): T
```
- `contentStrategy` parameter is the primary focus here and is explained above.
- returns `HTMLScriptElement` or `HTMLStyleElement` based on given strategy.
### removeContent
```js
removeContent(element: HTMLScriptElement | HTMLStyleElement): void
```
- `element` parameter is the inserted `HTMLScriptElement` or `HTMLStyleElement` element, which was returned by `insertContent` method.
### has
```js
has(content: string): boolean
``` ```
`strategy` parameter is the primary focus here and is explained above. The `has` method returns a boolean value that indicates the given content has already been added to the DOM or not.
- `content` parameter is the content of the inserted `HTMLScriptElement` or `HTMLStyleElement` element.
## What's Next? ## What's Next?
- [TrackByService](./Track-By-Service.md) - [ContentProjectionService](./Content-Projection-Service.md)

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

@ -87,3 +87,4 @@ DOM_STRATEGY.BeforeElement(target: HTMLElement)
- [LazyLoadService](./Lazy-Load-Service.md) - [LazyLoadService](./Lazy-Load-Service.md)
- [LoadingStrategy](./Loading-Strategy.md) - [LoadingStrategy](./Loading-Strategy.md)
- [ContentStrategy](./Content-Strategy.md) - [ContentStrategy](./Content-Strategy.md)
- [ProjectionStrategy](./Projection-Strategy.md)

2
docs/en/UI/Angular/Http-Requests.md → docs/en/UI/Angular/HTTP-Requests.md

@ -206,4 +206,4 @@ You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob
## What's Next? ## What's Next?
* [Localization](./Localization.md) * [Localization](./Localization.md)

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

@ -52,7 +52,7 @@ class DemoComponent {
The `load` method returns an observable to which you can subscibe in your component or with an `async` pipe. In the example above, the `NgIf` directive will render `<some-component>` only **if the script gets successfully loaded or is already loaded before**. The `load` method returns an observable to which you can subscibe in your component or with an `async` pipe. In the example above, the `NgIf` directive will render `<some-component>` only **if the script gets successfully loaded or is already loaded before**.
> You can subscribe multiple times in your template with `async` pipe. The styles will only be loaded once. > You can subscribe multiple times in your template with `async` pipe. The Scripts will only be loaded once.
Please refer to [LoadingStrategy](./Loading-Strategy.md) to see all available loading strategies and how you can build your own loading strategy. Please refer to [LoadingStrategy](./Loading-Strategy.md) to see all available loading strategies and how you can build your own loading strategy.

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

@ -57,7 +57,7 @@ This method creates and returns an observable stream that emits on success and t
## Predefined Loading Strategies ## Predefined Loading Strategies
Predefined content security strategies are accessible via `LOADING_STRATEGY` constant. Predefined loading strategies are accessible via `LOADING_STRATEGY` constant.
### AppendAnonymousScriptToHead ### AppendAnonymousScriptToHead

2
docs/en/UI/Angular/Permission-Management.md

@ -76,4 +76,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`.
## What's Next? ## What's Next?
* [Config State](./Config-State.md) - [Confirmation Popup](./Confirmation-Service.md)

200
docs/en/UI/Angular/Projection-Strategy.md

@ -0,0 +1,200 @@
# ProjectionStrategy
`ProjectionStrategy` is an abstract class exposed by @abp/ng.core package. There are three projection strategies extending it: `ComponentProjectionStrategy`, `RootComponentProjectionStrategy`, and `TemplateProjectionStrategy`. Implementing the same methods and properties, all of these strategies help you define how your content projection will work.
## ComponentProjectionStrategy
`ComponentProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a component into a container**.
### constructor
```js
constructor(
component: T,
private containerStrategy: ContainerStrategy,
private contextStrategy?: ContextStrategy,
)
```
- `component` is class of the component you would like to project.
- `containerStrategy` is the `ContainerStrategy` that will be used when projecting the component.
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_)
Please refer to [ContainerStrategy](./Container-Strategy.md) and [ContextStrategy](./Context-Strategy.md) documentation for their usage.
### injectContent
```js
injectContent(injector: Injector): ComponentRef<T>
```
This method prepares the container, resolves the component, sets its context, and projects it to the container. It returns a `ComponentRef` instance, which you should keep in order to clear projected components later on.
## RootComponentProjectionStrategy
`RootComponentProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a component into the document**, such as appending it to `<body>`.
### constructor
```js
constructor(
component: T,
private contextStrategy?: ContextStrategy,
private domStrategy?: DomStrategy,
)
```
- `component` is class of the component you would like to project.
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_)
- `domStrategy` is the `DomStrategy` that will be used when inserting component. (_default: AppendToBody_)
Please refer to [ContextStrategy](./Context-Strategy.md) and [DomStrategy](./Dom-Strategy.md) documentation for their usage.
### injectContent
```js
injectContent(injector: Injector): ComponentRef<T>
```
This method resolves the component, sets its context, and projects it to the document. It returns a `ComponentRef` instance, which you should keep in order to clear projected components later on.
## TemplateProjectionStrategy
`TemplateProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a template into a container**.
### constructor
```js
constructor(
template: T,
private containerStrategy: ContainerStrategy,
private contextStrategy?: ContextStrategy,
)
```
- `template` is `TemplateRef` you would like to project.
- `containerStrategy` is the `ContainerStrategy` that will be used when projecting the component.
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_)
Please refer to [ContainerStrategy](./Container-Strategy.md) and [ContextStrategy](./Context-Strategy.md) documentation for their usage.
### injectContent
```js
injectContent(): EmbeddedViewRef<T>
```
This method prepares the container, and projects the template together with the defined context to it. It returns an `EmbeddedViewRef`, which you should keep in order to clear projected templates later on.
## Predefined Projection Strategies
Predefined projection strategies are accessible via `PROJECTION_STRATEGY` constant.
### AppendComponentToBody
```js
PROJECTION_STRATEGY.AppendComponentToBody(
component: T,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the component and places it at the **end** of `<body>` tag in the document.
### AppendComponentToContainer
```js
PROJECTION_STRATEGY.AppendComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the component and places it at the **end** of the container.
### AppendTemplateToContainer
```js
PROJECTION_STRATEGY.AppendTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the template and places it at the **end** of the container.
### PrependComponentToContainer
```js
PROJECTION_STRATEGY.PrependComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the component and places it at the **beginning** of the container.
### PrependTemplateToContainer
```js
PROJECTION_STRATEGY.PrependTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the template and places it at the **beginning** of the container.
### ProjectComponentToContainer
```js
PROJECTION_STRATEGY.ProjectComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Clears the container, sets given context to the component, and places it **in the cleared** the container.
### ProjectTemplateToContainer
```js
PROJECTION_STRATEGY.ProjectTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Clears the container, sets given context to the template, and places it **in the cleared** the container.
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)

157
docs/en/UI/Angular/Toaster-Service.md

@ -0,0 +1,157 @@
# Toast Overlay
You can use the `ToasterService` in @abp/ng.theme.shared package to display messages in an overlay by placing at the root level in your project.
## Getting Started
You do not have to provide the `ToasterService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ToasterService } from '@abp/ng.theme.shared';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private toaster: ToasterService) {}
}
```
## Usage
You can use the `success`, `warn`, `error`, and `info` methods of `ToasterService` to display an overlay.
### How to Display a Toast Overlay
```js
this.toast.success('Message', 'Title');
```
- The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`.
- `success`, `warn`, `error`, and `info` methods return the id of opened toast overlay. The toast can be removed with this id.
### How to Display a Toast Overlay With Given Options
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods:
```js
import { Toaster, ToasterService } from '@abp/ng.theme.shared';
//...
constructor(private toaster: ToasterService) {}
//...
const options: Partial<Toaster.ToastOptions> = {
life: 10000,
sticky: false,
closable: true,
tapToDismiss: true,
messageLocalizationParams: ['Demo', '1'],
titleLocalizationParams: []
};
this.toaster.error('AbpUi::EntityNotFoundErrorMessage', 'AbpUi::Error', options);
```
- `life` option is the closing time in milliseconds. Default value is `5000`.
- `sticky` option keeps toast overlay on the screen by ignoring the `life` option when `true`. Default value is `false`.
- `closable` option displays the close icon on the toast overlay when it is `true`. Default value is `true`.
- `tapToDismiss` option, when `true`, allows closing the toast overlay by clicking over it. Default value is `false`.
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`.
- `messageLocalizationParams` is the interpolation parameters for the localization of the message.
- `titleLocalizationParams` is the interpolation parameters for the localization of the title.
With the options above, the toast overlay looks like this:
![toast](./images/toast.png)
### How to Remove a Toast Overlay
The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast:
```js
const toastId = this.toast.success('Message', 'Title')
this.toast.remove(toastId);
```
### How to Remove All Toasts
The all open toasts can be removed manually via the `clear` method:
```js
this.toast.clear();
```
## API
### success
```js
success(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
- `Config` namespace can be imported from `@abp/ng.core`.
- `Toaster` namespace can be imported from `@abp/ng.theme.shared`.
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Toaster` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts)
### warn
```js
warn(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### error
```js
error(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### info
```js
info(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### remove
```js
remove(id: number): void
```
Removes an open toast by the given id.
### clear
```js
clear(): void
```
Removes all open toasts.
## See Also
- [Confirmation Popup](./Confirmation-Service.md)
## What's Next?
- [Config State](./Config-State.md)

BIN
docs/en/UI/Angular/images/confirmation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/UI/Angular/images/toast.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

278
docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md

@ -1,3 +1,277 @@
## Dynamic Forms # Dynamic Forms
`Warning:` Before getting into this document, be sure that you have clearly understood [abp form elements](Form-elements.md) document.
## Introduction
`abp-dynamic-form` creates a bootstrap form for a given c# model.
Basic usage:
````xml
<abp-dynamic-form abp-model="@Model.MyDetailedModel"/>
````
Model:
````csharp
public class DynamicFormsModel : PageModel
{
[BindProperty]
public DetailedModel MyDetailedModel { get; set; }
public List<SelectListItem> CountryList { get; set; } = new List<SelectListItem>
{
new SelectListItem { Value = "CA", Text = "Canada"},
new SelectListItem { Value = "US", Text = "USA"},
new SelectListItem { Value = "UK", Text = "United Kingdom"},
new SelectListItem { Value = "RU", Text = "Russia"}
};
public void OnGet()
{
MyDetailedModel = new DetailedModel
{
Name = "",
Description = "Lorem ipsum dolor sit amet.",
IsActive = true,
Age = 65,
Day = DateTime.Now,
MyCarType = CarType.Coupe,
YourCarType = CarType.Sedan,
Country = "RU",
NeighborCountries = new List<string>() { "UK", "CA" }
};
}
public class DetailedModel
{
[Required]
[Placeholder("Enter your name...")]
[Display(Name = "Name")]
public string Name { get; set; }
[TextArea(Rows = 4)]
[Display(Name = "Description")]
[InputInfoText("Describe Yourself")]
public string Description { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Is Active")]
public bool IsActive { get; set; }
[Required]
[Display(Name = "Age")]
public int Age { get; set; }
[Required]
[Display(Name = "My Car Type")]
public CarType MyCarType { get; set; }
[Required]
[AbpRadioButton(Inline = true)]
[Display(Name = "Your Car Type")]
public CarType YourCarType { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Day")]
public DateTime Day { get; set; }
[SelectItems(nameof(CountryList))]
[Display(Name = "Country")]
public string Country { get; set; }
[SelectItems(nameof(CountryList))]
[Display(Name = "Neighbor Countries")]
public List<string> NeighborCountries { get; set; }
}
public enum CarType
{
Sedan,
Hatchback,
StationWagon,
Coupe
}
}
````
## Demo
See the [dynamic forms demo page](https://bootstrap-taghelpers.abp.io/Components/DynamicForms) to see it in action.
## Attributes
### abp-model
Sets the c# model for dynamic form. Properties of this modal are turned into inputs in the form.
### submit-button
Can be `True` or `False`.
If `True`, a submit button will be generated at the bottom of the form.
Default value is `False`.
### required-symbols
Can be `True` or `False`.
If `True`, required inputs will have a symbol (*) that indicates they are required.
Default value is `True`.
## Form Content Placement
By default, `abp-dynamicform` clears the inner html and places the inputs into itself. If you want to add additional content to dynamic form or place the inputs to some specific area, you can use ` <abp-form-content />` tag. This tag will be replaced by form content and rest of the inner html of `abp-dynamic-form` tag will be unchanged.
Usage:
````xml
<abp-dynamic-form abp-model="@Model.MyExampleModel">
<div>
Some content....
</div>
<div class="input-area">
<abp-form-content />
</div>
<div>
Some more content....
</div>
</abp-dynamic-form>
````
## Input Order
`abp-dynamic-form` orders the properties by their `DisplayOrder` attribute and then their property order in model class.
Default `DisplayOrder` attribute number is 10000 for every property.
See example below:
````csharp
public class OrderExampleModel
{
[DisplayOrder(10004)]
public string Name{ get; set; }
[DisplayOrder(10005)]
public string Surname{ get; set; }
//Default 10000
public string EmailAddress { get; set; }
[DisplayOrder(10003)]
public string PhoneNumber { get; set; }
[DisplayOrder(9999)]
public string City { get; set; }
}
````
In this example, input fields will be displayed with this order: `City` > `EmailAddress` > `PhoneNumber` > `Name` > `Surname`.
## Ignoring a property
By default, `abp-dynamic-form` generates input for every property in model class. If you want to ignore a property, use `DynamicFormIgnore` attribute.
See example below:
````csharp
public class MyModel
{
public string Name { get; set; }
[DynamicFormIgnore]
public string Surname { get; set; }
}
````
In this example, no input will be generated for `Surname` property.
## Indicating Text box, Radio Group and Combobox
If you have read the [Form elements document](Form-elements.md), you noticed that `abp-radio` and `abp-select` tags are very similar on c# model. So we have to use `[AbpRadioButton()]` attribute to tell `abp-dynamic-form` which of your properties will be radio group and which will be combobox. See example below:
````xml
<abp-dynamic-form abp-model="@Model.MyDetailedModel"/>
````
Model:
````csharp
public class DynamicFormsModel : PageModel
{
[BindProperty]
public DetailedModel MyDetailedModel { get; set; }
public List<SelectListItem> CountryList { get; set; } = new List<SelectListItem>
{
new SelectListItem { Value = "CA", Text = "Canada"},
new SelectListItem { Value = "US", Text = "USA"},
new SelectListItem { Value = "UK", Text = "United Kingdom"},
new SelectListItem { Value = "RU", Text = "Russia"}
};
public void OnGet()
{
MyDetailedModel = new DetailedModel
{
ComboCarType = CarType.Coupe,
RadioCarType = CarType.Sedan,
ComboCountry = "RU",
RadioCountry = "UK"
};
}
public class DetailedModel
{
public CarType ComboCarType { get; set; }
[AbpRadioButton(Inline = true)]
public CarType RadioCarType { get; set; }
[SelectItems(nameof(CountryList))]
public string ComboCountry { get; set; }
[AbpRadioButton()]
[SelectItems(nameof(CountryList))]
public string RadioCountry { get; set; }
}
public enum CarType
{
Sedan,
Hatchback,
StationWagon,
Coupe
}
}
````
As you see in example above:
* If `[AbpRadioButton()]` are used on a **Enum** property, it will be a radio group. Otherwise, combobox.
* If `[SelectItems()]` and `[AbpRadioButton()]` are used on a property, it will be a radio group.
* If just `[SelectItems()]` is used on a property, it will be a combobox.
* If none of these attributes are used on a property, it will be a text box.
## Localization
`abp-dynamic-form` handles localization as well.
By default, it will try to find "DisplayName:{PropertyName}" or "{PropertyName}" localization keys and set the localization value as input label.
You can set it yourself by using `[Display()]` attribute of Asp.Net Core. You can use a localization key in this attribute. See example below:
````csharp
[Display(Name = "Name")]
public string Name { get; set; }
````
This is not documented yet. You can see a [demo](http://bootstrap-taghelpers.abp.io/Components/DynamicForms) for now.

261
docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md

@ -0,0 +1,261 @@
# Form Elements
## Introduction
Abp provides form input tag helpers to make building forms easier.
## Demo
See the [form elements demo page](https://bootstrap-taghelpers.abp.io/Components/FormElements) to see it in action.
## abp-input
`abp-input` tag creates a Bootstrap form input for a given c# property. It uses [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/tr-tr/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-input-tag-helper) in background, so every data annotation attribute of `input` tag helper of Asp.Net Core is also valid for `abp-input`.
Usage:
````xml
<abp-input asp-for="@Model.MyModel.Name"/>
<abp-input asp-for="@Model.MyModel.Description"/>
<abp-input asp-for="@Model.MyModel.Password"/>
<abp-input asp-for="@Model.MyModel.IsActive"/>
````
Model:
````csharp
public class FormElementsModel : PageModel
{
public SampleModel MyModel { get; set; }
public void OnGet()
{
MyModel = new SampleModel();
}
public class SampleModel
{
[Required]
[Placeholder("Enter your name...")]
[InputInfoText("What is your name?")]
public string Name { get; set; }
[Required]
[FormControlSize(AbpFormControlSize.Large)]
public string SurName { get; set; }
[TextArea(Rows = 4)]
public string Description { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public bool IsActive { get; set; }
}
}
````
### Attributes
You can set some of the attributes on your c# property, or directly on html tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes.
#### Property Attributes
- `[TextArea()]`: Converts the input into a text area.
* `[Placeholder()]`: Sets placeholder for input. You can use a localization key directly.
* `[InputInfoText()]`: Sets a small info text for input. You can use a localization key directly.
* `[FormControlSize()]`: Sets size of form-control wrapper element. Available values are
- `AbpFormControlSize.Default`
- `AbpFormControlSize.Small`
- `AbpFormControlSize.Medium`
- `AbpFormControlSize.Large`
* `[DisabledInput]` : Input is disabled.
* `[ReadOnlyInput]`: Input is read-only.
#### Tag Attributes
* `info`: Sets a small info text for input. You can use a localization key directly.
* `auto-focus`: If true, browser auto focuses on the element.
* `size`: Sets size of form-control wrapper element. Available values are
- `AbpFormControlSize.Default`
- `AbpFormControlSize.Small`
- `AbpFormControlSize.Medium`
- `AbpFormControlSize.Large`
* `disabled`: Input is disabled.
* `readonly`: Input is read-only.
* `label`: Sets the label for input.
* `display-required-symbol`: Adds the required symbol (*) to label if input is required. Default `True`.
### Label & Localization
You can set label of your input in different ways:
- You can use `Label` attribute and directly set the label. But it doesn't auto localize your localization key. So use it as `label="@L["{LocalizationKey}"].Value"`.
- You can set it using `[Display(name="{LocalizationKey}")]` attribute of Asp.Net Core.
- You can just let **abp** find the localization key for the property. It will try to find "DisplayName:{PropertyName}" or "{PropertyName}" localization keys, if `label` or `[DisplayName]` attributes are not set.
## abp-select
`abp-select` tag creates a Bootstrap form select for a given c# property. It uses [Asp.Net Core Select Tag Helper](https://docs.microsoft.com/tr-tr/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-select-tag-helper) in background, so every data annotation attribute of `select` tag helper of Asp.Net Core is also valid for `abp-select`.
`abp-select` tag needs a list of `Microsoft.AspNetCore.Mvc.Rendering.SelectListItem ` to work. It can be provided by `asp-items` attriube on the tag or `[SelectItems()]` attribute on c# property. (if you are using [abp-dynamic-form](Dynamic-forms.md), c# attribute is the only way.)
`abp-select` supports multiple selection.
`abp-select` auto-creates a select list for **Enum** properties. No extra data is needed. If property is nullable, an empty key and value is added to top of the auto-generated list.
Usage:
````xml
<abp-select asp-for="@Model.MyModel.City" asp-items="@Model.CityList"/>
<abp-select asp-for="@Model.MyModel.AnotherCity"/>
<abp-select asp-for="@Model.MyModel.MultipleCities" asp-items="@Model.CityList"/>
<abp-select asp-for="@Model.MyModel.MyCarType"/>
<abp-select asp-for="@Model.MyModel.MyNullableCarType"/>
````
Model:
````csharp
public class FormElementsModel : PageModel
{
public SampleModel MyModel { get; set; }
public List<SelectListItem> CityList { get; set; }
public void OnGet()
{
MyModel = new SampleModel();
CityList = new List<SelectListItem>
{
new SelectListItem { Value = "NY", Text = "New York"},
new SelectListItem { Value = "LDN", Text = "London"},
new SelectListItem { Value = "IST", Text = "Istanbul"},
new SelectListItem { Value = "MOS", Text = "Moscow"}
};
}
public class SampleModel
{
public string City { get; set; }
[SelectItems(nameof(CityList))]
public string AnotherCity { get; set; }
public List<string> MultipleCities { get; set; }
public CarType MyCarType { get; set; }
public CarType? MyNullableCarType { get; set; }
}
public enum CarType
{
Sedan,
Hatchback,
StationWagon,
Coupe
}
}
````
### Attributes
You can set some of the attributes on your c# property, or directly on html tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes.
#### Property Attributes
* `[SelectItems()]`: Sets the select data. Parameter should be the name of the data list. (see example above)
- `[InputInfoText()]`: Sets a small info text for input. You can use a localization key directly.
- `[FormControlSize()]`: Sets size of form-control wrapper element. Available values are
- `AbpFormControlSize.Default`
- `AbpFormControlSize.Small`
- `AbpFormControlSize.Medium`
- `AbpFormControlSize.Large`
#### Tag Attributes
- `asp-items`: Sets the select data. This Should be a list of SelectListItem.
- `info`: Sets a small info text for input. You can use a localization key directly.
- `size`: Sets size of form-control wrapper element. Available values are
- `AbpFormControlSize.Default`
- `AbpFormControlSize.Small`
- `AbpFormControlSize.Medium`
- `AbpFormControlSize.Large`
- `label`: Sets the label for input.
- `display-required-symbol`: Adds the required symbol (*) to label if input is required. Default `True`.
### Label & Localization
You can set label of your input in different ways:
- You can use `Label` attribute and directly set the label. But it doesn't auto localize your localization key. So use it as `label="@L["{LocalizationKey}"].Value".`
- You can set it using `[Display(name="{LocalizationKey}")]` attribute of Asp.Net Core.
- You can just let **abp** find the localization key for the property. It will try to find "DisplayName:{PropertyName}" or "{PropertyName}" localization keys.
Localizations of combobox values are set by `abp-select` for **Enum** property. It searches for "{EnumTypeName}.{EnumPropertyName}" or "{EnumPropertyName}" localization keys. For instance, in the example above, it will use "CarType.StationWagon" or "StationWagon" keys for localization when it localizes combobox values.
## abp-radio
`abp-radio` tag creates a Bootstrap form radio group for a given c# property. Usage is very similar to `abp-select` tag.
Usage:
````xml
<abp-radio asp-for="@Model.MyModel.CityRadio" asp-items="@Model.CityList" inline="true"/>
<abp-radio asp-for="@Model.MyModel.CityRadio2"/>
````
Model:
````csharp
public class FormElementsModel : PageModel
{
public SampleModel MyModel { get; set; }
public List<SelectListItem> CityList { get; set; } = new List<SelectListItem>
{
new SelectListItem { Value = "NY", Text = "New York"},
new SelectListItem { Value = "LDN", Text = "London"},
new SelectListItem { Value = "IST", Text = "Istanbul"},
new SelectListItem { Value = "MOS", Text = "Moscow"}
};
public void OnGet()
{
MyModel = new SampleModel();
MyModel.CityRadio = "IST";
MyModel.CityRadio2 = "MOS";
}
public class SampleModel
{
public string CityRadio { get; set; }
[SelectItems(nameof(CityList))]
public string CityRadio2 { get; set; }
}
}
````
### Attributes
You can set some of the attributes on your c# property, or directly on html tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes.
#### Property Attributes
- `[SelectItems()]`: Sets the select data. Parameter should be the name of the data list. (see example above)
#### Tag Attributes
- `asp-items`: Sets the select data. This Should be a list of SelectListItem.
- `Inline`: If true, radio buttons will be in single line, next to each other. If false, they will be under each other.

14
docs/en/UI/AspNetCore/Tag-Helpers/Index.md

@ -12,18 +12,18 @@ ABP Framework also adds some **useful features** to the standard bootstrap compo
Here, the list of components those are wrapped by the ABP Framework: Here, the list of components those are wrapped by the ABP Framework:
* [Alerts](Alerts.md)
* [Buttons](Buttons.md) * [Buttons](Buttons.md)
* [Cards](Cards.md) * [Cards](Cards.md)
* [Alerts](Alerts.md)
* [Tabs](Tabs.md)
* [Grids](Grids.md)
* [Modals](Modals.md)
* [Collapse](Collapse.md) * [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md) * [Dropdowns](Dropdowns.md)
* [Grids](Grids.md)
* [List Groups](List-Groups.md) * [List Groups](List-Groups.md)
* [Modals](Modals.md)
* [Paginator](Paginator.md) * [Paginator](Paginator.md)
* [Popovers](Popovers.md) * [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md) * [Progress Bars](Progress-Bars.md)
* [Tabs](Tabs.md)
* [Tooltips](Tooltips.md) * [Tooltips](Tooltips.md)
* ... * ...
@ -31,8 +31,8 @@ Here, the list of components those are wrapped by the ABP Framework:
## Form Elements ## Form Elements
See [demo](https://bootstrap-taghelpers.abp.io/Components/FormElements). **Abp Tag Helpers** add new features to standard **Asp.Net Core MVC input & select Tag Helpers** and wrap them with **Bootstrap** form controls. See [Form Elements documentation](Form-elements.md) .
## Dynamic Inputs ## Dynamic Forms
See [demo](https://bootstrap-taghelpers.abp.io/Components/DynamicForms). **Abp Tag helpers** offer an easy way to build complete **Bootstrap forms**. See [Dynamic Forms documentation](Dynamic-Forms.md).

2
docs/en/UI/AspNetCore/Tag-Helpers/List-Groups.md

@ -19,7 +19,7 @@ Basic usage:
## Demo ## Demo
See the [list groups demo page](https://bootstrap-taghelpers.abp.io/Components/ListGroups) to see it in action. See the [list groups demo page](https://bootstrap-taghelpers.abp.io/Components/ListGroup) to see it in action.
## Attributes ## Attributes

4
docs/en/UI/AspNetCore/Tag-Helpers/Progress-Bars.md

@ -26,7 +26,7 @@ Basic usage:
## Demo ## Demo
See the [progress bars demo page](https://bootstrap-taghelpers.abp.io/Components/Progress-Bars) to see it in action. See the [progress bars demo page](https://bootstrap-taghelpers.abp.io/Components/Progressbars) to see it in action.
## Attributes ## Attributes
@ -67,4 +67,4 @@ A value indicates if the background style of the progress bar is stripped. Shoul
A value indicates if the stripped background style of the progress bar is animated. Should be one of the following values: A value indicates if the stripped background style of the progress bar is animated. Should be one of the following values:
* `false` (default value) * `false` (default value)
* `true` * `true`

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

@ -2,20 +2,29 @@
The core module provides a useful data structure known as a [doubly linked list](https://en.wikipedia.org/wiki/Doubly_linked_list). Briefly, a doubly linked list is a series of records (a.k.a. nodes) which has information on the previous node, the next node, and its own value (or data). The @abp/utils package provides a useful data structure known as a [doubly linked list](https://en.wikipedia.org/wiki/Doubly_linked_list). It is availabe in both Angular (via an import) and MVC (via `abp.utils.common` global object).
Briefly, a doubly linked list is a series of records (a.k.a. nodes) which has information on the previous node, the next node, and its own value (or data).
## Getting Started ## Getting Started
To create a doubly linked list, all you have to do is to import and create a new instance of it: To create a doubly linked list, all you have to do is to create a new instance of it:
In Angular:
```js ```js
import { LinkedList } from '@abp/ng.core'; import { LinkedList } from '@abp/utils';
const list = new LinkedList(); const list = new LinkedList();
``` ```
In MVC:
```js
var list = new abp.utils.common.LinkedList();
```
The constructor does not get any parameters. The constructor does not get any parameters.
@ -33,7 +42,7 @@ There are several methods to create new nodes in a linked list and all of them a
#### addHead(value) #### addHead(value)
```js ```js
addHead(value: T): ListNode\<T\> addHead(value: T): ListNode<T>
``` ```
Adds a node with given value as the first node in list: Adds a node with given value as the first node in list:
@ -57,7 +66,7 @@ list.addHead('c');
#### addManyHead(values) #### addManyHead(values)
```js ```js
addManyHead(values: T\[\]): ListNode\<T\>\[\] addManyHead(values: T[]): ListNode<T>[]
``` ```
Adds multiple nodes with given values as the first nodes in list: Adds multiple nodes with given values as the first nodes in list:
@ -77,7 +86,7 @@ list.addManyHead(['x', 'y', 'z']);
#### addTail(value) #### addTail(value)
```js ```js
addTail(value: T): ListNode\<T\> addTail(value: T): ListNode<T>
``` ```
Adds a node with given value as the last node in list: Adds a node with given value as the last node in list:
@ -101,7 +110,7 @@ list.addTail('c');
#### addManyTail(values) #### addManyTail(values)
```js ```js
addManyTail(values: T\[\]): ListNode\<T\>\[\] addManyTail(values: T[]): ListNode<T>[]
``` ```
Adds multiple nodes with given values as the last nodes in list: Adds multiple nodes with given values as the last nodes in list:
@ -118,10 +127,10 @@ list.addManyTail(['x', 'y', 'z']);
#### addAfter(value, previousValue, compareFn) #### addAfter(value, previousValue [, compareFn])
```js ```js
addAfter(value: T, previousValue: T, compareFn = compare): ListNode\<T\> addAfter(value: T, previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
``` ```
Adds a node with given value after the first node that has the previous value: Adds a node with given value after the first node that has the previous value:
@ -165,10 +174,10 @@ list.addAfter(
#### addManyAfter(values, previousValue, compareFn) #### addManyAfter(values, previousValue [, compareFn])
```js ```js
addManyAfter(values: T\[\], previousValue: T, compareFn = compare): ListNode\<T\>\[\] addManyAfter(values: T[], previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
``` ```
Adds multiple nodes with given values after the first node that has the previous value: Adds multiple nodes with given values after the first node that has the previous value:
@ -207,10 +216,10 @@ list.addManyAfter(
#### addBefore(value, nextValue, compareFn) #### addBefore(value, nextValue [, compareFn])
```js ```js
addBefore(value: T, nextValue: T, compareFn = compare): ListNode\<T\> addBefore(value: T, nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
``` ```
Adds a node with given value before the first node that has the next value: Adds a node with given value before the first node that has the next value:
@ -254,10 +263,10 @@ list.addBefore(
#### addManyBefore(values, nextValue, compareFn) #### addManyBefore(values, nextValue [, compareFn])
```js ```js
addManyBefore(values: T\[\], nextValue: T, compareFn = compare): ListNode\<T\>\[\] addManyBefore(values: T[], nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
``` ```
Adds multiple nodes with given values before the first node that has the next value: Adds multiple nodes with given values before the first node that has the next value:
@ -299,7 +308,7 @@ list.addManyBefore(
#### addByIndex(value, position) #### addByIndex(value, position)
```js ```js
addByIndex(value: T, position: number): ListNode\<T\> addByIndex(value: T, position: number): ListNode<T>
``` ```
Adds a node with given value at the specified position in the list: Adds a node with given value at the specified position in the list:
@ -337,7 +346,7 @@ list.addByIndex('x', -1);
#### addManyByIndex(values, position) #### addManyByIndex(values, position)
```js ```js
addManyByIndex(values: T\[\], position: number): ListNode\<T\>\[\] addManyByIndex(values: T[], position: number): ListNode<T>[]
``` ```
Adds multiple nodes with given values at the specified position in the list: Adds multiple nodes with given values at the specified position in the list:
@ -371,7 +380,7 @@ list.addManyByIndex(['x', 'y'], -1);
#### add(value).head() #### add(value).head()
```js ```js
add(value: T).head(): ListNode\<T\> add(value: T).head(): ListNode<T>
``` ```
Adds a node with given value as the first node in list: Adds a node with given value as the first node in list:
@ -399,7 +408,7 @@ list.add('c').head();
#### add(value).tail() #### add(value).tail()
```js ```js
add(value: T).tail(): ListNode\<T\> add(value: T).tail(): ListNode<T>
``` ```
Adds a node with given value as the last node in list: Adds a node with given value as the last node in list:
@ -424,10 +433,10 @@ list.add('c').tail();
#### add(value).after(previousValue, compareFn) #### add(value).after(previousValue [, compareFn])
```js ```js
add(value: T).after(previousValue: T, compareFn = compare): ListNode\<T\> add(value: T).after(previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
``` ```
Adds a node with given value after the first node that has the previous value: Adds a node with given value after the first node that has the previous value:
@ -471,10 +480,10 @@ list
#### add(value).before(nextValue, compareFn) #### add(value).before(nextValue [, compareFn])
```js ```js
add(value: T).before(nextValue: T, compareFn = compare): ListNode\<T\> add(value: T).before(nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
``` ```
Adds a node with given value before the first node that has the next value: Adds a node with given value before the first node that has the next value:
@ -521,7 +530,7 @@ list
#### add(value).byIndex(position) #### add(value).byIndex(position)
```js ```js
add(value: T).byIndex(position: number): ListNode\<T\> add(value: T).byIndex(position: number): ListNode<T>
``` ```
Adds a node with given value at the specified position in the list: Adds a node with given value at the specified position in the list:
@ -563,7 +572,7 @@ list.add('x').byIndex(-1);
#### addMany(values).head() #### addMany(values).head()
```js ```js
addMany(values: T\[\]).head(): ListNode\<T\>\[\] addMany(values: T[]).head(): ListNode<T>[]
``` ```
Adds multiple nodes with given values as the first nodes in list: Adds multiple nodes with given values as the first nodes in list:
@ -587,7 +596,7 @@ list.addMany(['x', 'y', 'z']).head();
#### addMany(values).tail() #### addMany(values).tail()
```js ```js
addMany(values: T\[\]).tail(): ListNode\<T\>\[\] addMany(values: T[]).tail(): ListNode<T>[]
``` ```
Adds multiple nodes with given values as the last nodes in list: Adds multiple nodes with given values as the last nodes in list:
@ -608,10 +617,10 @@ list.addMany(['x', 'y', 'z']).tail();
#### addMany(values).after(previousValue, compareFn) #### addMany(values).after(previousValue [, compareFn])
```js ```js
addMany(values: T\[\]).after(previousValue: T, compareFn = compare): ListNode\<T\>\[\] addMany(values: T[]).after(previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
``` ```
Adds multiple nodes with given values after the first node that has the previous value: Adds multiple nodes with given values after the first node that has the previous value:
@ -650,10 +659,10 @@ list
#### addMany(values).before(nextValue, compareFn) #### addMany(values).before(nextValue [, compareFn])
```js ```js
addMany(values: T\[\]).before(nextValue: T, compareFn = compare): ListNode\<T\>\[\] addMany(values: T[]).before(nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
``` ```
Adds multiple nodes with given values before the first node that has the next value: Adds multiple nodes with given values before the first node that has the next value:
@ -695,7 +704,7 @@ list
#### addMany(values).byIndex(position) #### addMany(values).byIndex(position)
```js ```js
addMany(values: T\[\]).byIndex(position: number): ListNode\<T\>\[\] addMany(values: T[]).byIndex(position: number): ListNode<T>[]
``` ```
Adds multiple nodes with given values at the specified position in the list: Adds multiple nodes with given values at the specified position in the list:
@ -739,7 +748,7 @@ There are a few methods to remove nodes from a linked list and all of them are s
#### dropHead() #### dropHead()
```js ```js
dropHead(): ListNode\<T\> | undefined dropHead(): ListNode<T> | undefined
``` ```
Removes the first node from the list: Removes the first node from the list:
@ -759,7 +768,7 @@ list.dropHead();
#### dropManyHead(count) #### dropManyHead(count)
```js ```js
dropManyHead(count: number): ListNode\<T\>\[\] dropManyHead(count: number): ListNode<T>[]
``` ```
Removes the first nodes from the list based on given count: Removes the first nodes from the list based on given count:
@ -779,7 +788,7 @@ list.dropManyHead(2);
#### dropTail() #### dropTail()
```js ```js
dropTail(): ListNode\<T\> | undefined dropTail(): ListNode<T> | undefined
``` ```
Removes the last node from the list: Removes the last node from the list:
@ -799,7 +808,7 @@ list.dropTail();
#### dropManyTail(count) #### dropManyTail(count)
```js ```js
dropManyTail(count: number): ListNode\<T\>\[\] dropManyTail(count: number): ListNode<T>[]
``` ```
Removes the last nodes from the list based on given count: Removes the last nodes from the list based on given count:
@ -819,7 +828,7 @@ list.dropManyTail(2);
#### dropByIndex(position) #### dropByIndex(position)
```js ```js
dropByIndex(position: number): ListNode\<T\> | undefined dropByIndex(position: number): ListNode<T> | undefined
``` ```
Removes the node with the specified position from the list: Removes the node with the specified position from the list:
@ -853,7 +862,7 @@ list.dropByIndex(-2);
#### dropManyByIndex(count, position) #### dropManyByIndex(count, position)
```js ```js
dropManyByIndex(count: number, position: number): ListNode\<T\>\[\] dropManyByIndex(count: number, position: number): ListNode<T>[]
``` ```
Removes the nodes starting from the specified position from the list based on given count: Removes the nodes starting from the specified position from the list based on given count:
@ -884,10 +893,10 @@ list.dropManyByIndex(2, -2);
#### dropByValue(value, compareFn) #### dropByValue(value [, compareFn])
```js ```js
dropByValue(value: T, compareFn = compare): ListNode\<T\> | undefined dropByValue(value: T, compareFn?: ListComparisonFn<T>): ListNode<T> | undefined
``` ```
Removes the first node with given value from the list: Removes the first node with given value from the list:
@ -922,10 +931,10 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### dropByValueAll(value, compareFn) #### dropByValueAll(value [, compareFn])
```js ```js
dropByValueAll(value: T, compareFn = compare): ListNode\<T\>\[\] dropByValueAll(value: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
``` ```
Removes all nodes with given value from the list: Removes all nodes with given value from the list:
@ -963,7 +972,7 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### drop().head() #### drop().head()
```js ```js
drop().head(): ListNode\<T\> | undefined drop().head(): ListNode<T> | undefined
``` ```
Removes the first node in list: Removes the first node in list:
@ -987,7 +996,7 @@ list.drop().head();
#### drop().tail() #### drop().tail()
```js ```js
drop().tail(): ListNode\<T\> | undefined drop().tail(): ListNode<T> | undefined
``` ```
Removes the last node in list: Removes the last node in list:
@ -1011,7 +1020,7 @@ list.drop().tail();
#### drop().byIndex(position) #### drop().byIndex(position)
```js ```js
drop().byIndex(position: number): ListNode\<T\> | undefined drop().byIndex(position: number): ListNode<T> | undefined
``` ```
Removes the node with the specified position from the list: Removes the node with the specified position from the list:
@ -1046,10 +1055,10 @@ list.drop().byIndex(-2);
#### drop().byValue(value, compareFn) #### drop().byValue(value [, compareFn])
```js ```js
drop().byValue(value: T, compareFn = compare): ListNode\<T\> | undefined drop().byValue(value: T, compareFn?: ListComparisonFn<T>): ListNode<T> | undefined
``` ```
Removes the first node with given value from the list: Removes the first node with given value from the list:
@ -1088,10 +1097,10 @@ list
#### drop().byValueAll(value, compareFn) #### drop().byValueAll(value [, compareFn])
```js ```js
drop().byValueAll(value: T, compareFn = compare): ListNode\<T\>\[\] drop().byValueAll(value: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
``` ```
Removes all nodes with given value from the list: Removes all nodes with given value from the list:
@ -1133,7 +1142,7 @@ list
#### dropMany(count).head() #### dropMany(count).head()
```js ```js
dropMany(count: number).head(): ListNode\<T\>\[\] dropMany(count: number).head(): ListNode<T>[]
``` ```
Removes the first nodes from the list based on given count: Removes the first nodes from the list based on given count:
@ -1157,7 +1166,7 @@ list.dropMany(2).head();
#### dropMany(count).tail() #### dropMany(count).tail()
```js ```js
dropMany(count: number).tail(): ListNode\<T\>\[\] dropMany(count: number).tail(): ListNode<T>[]
``` ```
Removes the last nodes from the list based on given count: Removes the last nodes from the list based on given count:
@ -1181,7 +1190,7 @@ list.dropMany(2).tail();
#### dropMany(count).byIndex(position) #### dropMany(count).byIndex(position)
```js ```js
dropMany(count: number).byIndex(position: number): ListNode\<T\>\[\] dropMany(count: number).byIndex(position: number): ListNode<T>[]
``` ```
Removes the nodes starting from the specified position from the list based on given count: Removes the nodes starting from the specified position from the list based on given count:
@ -1222,10 +1231,40 @@ There are a few methods to find specific nodes in a linked list.
#### head
```js
head: ListNode<T> | undefined;
```
Refers to the first node in the list.
#### tail
```js
tail: ListNode<T> | undefined;
```
Refers to the last node in the list.
#### length
```js
length: number;
```
Is the total number of nodes in the list.
#### find(predicate) #### find(predicate)
```js ```js
find(predicate: ListIteratorFunction\<T\>): ListNode\<T\> | undefined find(predicate: ListIteratorFn<T>): ListNode<T> | undefined
``` ```
Finds the first node from the list that matches the given predicate: Finds the first node from the list that matches the given predicate:
@ -1235,7 +1274,7 @@ list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
const found = list.find(node => node.value === 'b'); var found = list.find(node => node.value === 'b');
/* /*
found.value === "b" found.value === "b"
@ -1249,7 +1288,7 @@ found.next.value === "b"
#### findIndex(predicate) #### findIndex(predicate)
```js ```js
findIndex(predicate: ListIteratorFunction\<T\>): number findIndex(predicate: ListIteratorFn<T>): number
``` ```
Finds the position of the first node from the list that matches the given predicate: Finds the position of the first node from the list that matches the given predicate:
@ -1259,10 +1298,10 @@ list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
const i0 = list.findIndex(node => node.next && node.next.value === 'b'); var i0 = list.findIndex(node => node.next && node.next.value === 'b');
const i1 = list.findIndex(node => node.value === 'b'); var i1 = list.findIndex(node => node.value === 'b');
const i2 = list.findIndex(node => node.previous && node.previous.value === 'b'); var i2 = list.findIndex(node => node.previous && node.previous.value === 'b');
const i3 = list.findIndex(node => node.value === 'x'); var i3 = list.findIndex(node => node.value === 'x');
/* /*
i0 === 0 i0 === 0
@ -1277,7 +1316,7 @@ i3 === -1
#### get(position) #### get(position)
```js ```js
get(position: number): ListNode\<T\> | undefined get(position: number): ListNode<T> | undefined
``` ```
Finds and returns the node with specific position in the list: Finds and returns the node with specific position in the list:
@ -1287,7 +1326,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
const found = list.get(1); var found = list.get(1);
/* /*
found.value === "b" found.value === "b"
@ -1298,10 +1337,10 @@ found.next.value === "c"
#### indexOf(value, compareFn) #### indexOf(value [, compareFn])
```js ```js
indexOf(value: T, compareFn = compare): number indexOf(value: T, compareFn?: ListComparisonFn<T>): number
``` ```
Finds the position of the first node from the list that has the given value: Finds the position of the first node from the list that has the given value:
@ -1311,10 +1350,10 @@ list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
const i0 = list.indexOf('a'); var i0 = list.indexOf('a');
const i1 = list.indexOf('b'); var i1 = list.indexOf('b');
const i2 = list.indexOf('c'); var i2 = list.indexOf('c');
const i3 = list.indexOf('x'); var i3 = list.indexOf('x');
/* /*
i0 === 0 i0 === 0
@ -1333,11 +1372,11 @@ list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]);
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} // {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
const i0 = indexOf(1, (value, searchedValue) => value.x === searchedValue); var i0 = indexOf(1, (value, searchedValue) => value.x === searchedValue);
const i1 = indexOf(2, (value, searchedValue) => value.x === searchedValue); var i1 = indexOf(2, (value, searchedValue) => value.x === searchedValue);
const i2 = indexOf(3, (value, searchedValue) => value.x === searchedValue); var i2 = indexOf(3, (value, searchedValue) => value.x === searchedValue);
const i3 = indexOf(0, (value, searchedValue) => value.x === searchedValue); var i3 = indexOf(0, (value, searchedValue) => value.x === searchedValue);
const i4 = indexOf(4, (value, searchedValue) => value.x === searchedValue); var i4 = indexOf(4, (value, searchedValue) => value.x === searchedValue);
/* /*
i0 === 0 i0 === 0
@ -1360,13 +1399,13 @@ There are a few ways to iterate over or display a linked list.
#### forEach(callback) #### forEach(iteratorFn)
```js ```js
forEach(callback: ListIteratorFunction\<T\>): void forEach(iteratorFn: ListIteratorFn<T>): void
``` ```
Runs a callback function on all nodes in a linked list from head to tail: Runs a function on all nodes in a linked list from head to tail:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addTailMany(['a', 'b', 'c']);
@ -1381,7 +1420,6 @@ list.forEach((node, index) => console.log(node.value + index));
``` ```
#### \*\[Symbol.iterator\]\(\) #### \*\[Symbol.iterator\]\(\)
A linked list is iterable. In other words, you may use methods like `for...of` on it. A linked list is iterable. In other words, you may use methods like `for...of` on it.
@ -1391,7 +1429,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
for(const node of list) { for(const node of list) { /* ES6 for...of statement */
console.log(node.value); console.log(node.value);
} }
@ -1405,7 +1443,7 @@ for(const node of list) {
#### toArray() #### toArray()
```js ```js
toArray(): T\[\] toArray(): T[]
``` ```
Converts a linked list to an array of values: Converts a linked list to an array of values:
@ -1415,7 +1453,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
const arr = list.toArray(); var arr = list.toArray();
/* /*
arr === ['a', 'b', 'c'] arr === ['a', 'b', 'c']
@ -1427,7 +1465,7 @@ arr === ['a', 'b', 'c']
#### toNodeArray() #### toNodeArray()
```js ```js
toNodeArray(): T\[\] toNodeArray(): ListNode<T>[]
``` ```
Converts a linked list to an array of nodes: Converts a linked list to an array of nodes:
@ -1437,7 +1475,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
const arr = list.toNodeArray(); var arr = list.toNodeArray();
/* /*
arr[0].value === 'a' arr[0].value === 'a'
@ -1448,10 +1486,10 @@ arr[2].value === 'a'
#### toString() #### toString([mapperFn])
```js ```js
toString(): string toString(mapperFn: ListMapperFn<T> = JSON.stringify): string
``` ```
Converts a linked list to a string representation of nodes and their relations: Converts a linked list to a string representation of nodes and their relations:
@ -1461,7 +1499,7 @@ list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]);
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} // "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
const str = list.toString(); var str = list.toString();
/* /*
str === '"a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}' str === '"a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}'
@ -1477,7 +1515,7 @@ list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }]).tail();
// {"x":1} <-> {"x":2} <-> {"x":3} <-> {"x":4} <-> {"x":5} // {"x":1} <-> {"x":2} <-> {"x":3} <-> {"x":4} <-> {"x":5}
const str = list.toString(value => value.x); var str = list.toString(value => value.x);
/* /*
str === '1 <-> 2 <-> 3 <-> 4 <-> 5' str === '1 <-> 2 <-> 3 <-> 4 <-> 5'
@ -1486,3 +1524,93 @@ str === '1 <-> 2 <-> 3 <-> 4 <-> 5'
## API
### Classes
#### LinkedList
```js
export class LinkedList<T = any> {
// properties and methods are explained above
}
```
#### ListNode
```js
export class ListNode<T = any> {
next: ListNode | undefined;
previous: ListNode | undefined;
constructor(public readonly value: T) {}
}
```
`ListNode` is the node that is being stored in the `LinkedList` for every record.
- `value` is the value stored in the node and is passed through the constructor.
- `next` refers to the next node in the list.
- `previous` refers to the previous node in the list.
```js
list.addTailMany([ 0, 1, 2 ]);
console.log(
list.head.value, // 0
list.head.next.value, // 1
list.head.next.next.value, // 2
list.head.next.next.previous.value, // 1
list.head.next.next.previous.previous.value, // 0
list.tail.value, // 2
list.tail.previous.value, // 1
list.tail.previous.previous.value, // 0
list.tail.previous.previous.next.value, // 1
list.tail.previous.previous.next.next.value, // 2
);
```
### Types
#### ListMapperFn
```js
type ListMapperFn<T = any> = (value: T) => any;
```
This function is used in `toString` method to map the node values before generating a string representation of the list.
#### ListComparisonFn
```js
type ListComparisonFn<T = any> = (nodeValue: T, comparedValue: any) => boolean;
```
This function is used while adding, dropping, ang finding nodes based on a comparison value.
#### ListIteratorFn
```js
type ListIteratorFn<T = any, R = boolean> = (
node: ListNode<T>,
index?: number,
list?: LinkedList,
) => R;
```
This function is used while iterating over the list either to do something with each node or to find a node.

49
docs/en/docs-nav.json

@ -5,17 +5,7 @@
"items": [ "items": [
{ {
"text": "From Startup Templates", "text": "From Startup Templates",
"path": "Getting-Started-With-Startup-Templates.md", "path": "Getting-Started.md"
"items": [
{
"text": "Application with MVC (Razor Pages) UI",
"path": "Getting-Started-AspNetCore-MVC-Template.md"
},
{
"text": "Application with Angular UI",
"path": "Getting-Started-Angular-Template.md"
}
]
}, },
{ {
"text": "From Empty Projects", "text": "From Empty Projects",
@ -155,6 +145,10 @@
{ {
"text": "Data Filtering", "text": "Data Filtering",
"path": "Data-Filtering.md" "path": "Data-Filtering.md"
},
{
"text": "Object Extensions",
"path": "Object-Extensions.md"
} }
] ]
}, },
@ -298,7 +292,17 @@
}, },
{ {
"text": "Tag Helpers", "text": "Tag Helpers",
"path": "UI/AspNetCore/Tag-Helpers/Index.md" "path": "UI/AspNetCore/Tag-Helpers/Index.md",
"items": [
{
"text": "Form Elements",
"path": "UI/AspNetCore/Tag-Helpers/Form-elements.md"
},
{
"text": "Dynamic Forms",
"path": "UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md"
}
]
}, },
{ {
"text": "Widgets", "text": "Widgets",
@ -333,6 +337,14 @@
"text": "Permission Management", "text": "Permission Management",
"path": "UI/Angular/Permission-Management.md" "path": "UI/Angular/Permission-Management.md"
}, },
{
"text": "Confirmation Popup",
"path": "UI/Angular/Confirmation-Service.md"
},
{
"text": "Toast Overlay",
"path": "UI/Angular/Toaster-Service.md"
},
{ {
"text": "Config State", "text": "Config State",
"path": "UI/Angular/Config-State.md" "path": "UI/Angular/Config-State.md"
@ -353,6 +365,10 @@
"text": "DomInsertionService", "text": "DomInsertionService",
"path": "UI/Angular/Dom-Insertion-Service.md" "path": "UI/Angular/Dom-Insertion-Service.md"
}, },
{
"text": "ContentProjectionService",
"path": "UI/Angular/Content-Projection-Service.md"
},
{ {
"text": "TrackByService", "text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md" "path": "UI/Angular/Track-By-Service.md"
@ -363,8 +379,13 @@
"text": "Common", "text": "Common",
"items": [ "items": [
{ {
"text": "Linked List (Doubly)", "text": "Utilities",
"path": "UI/Common/Utils/Linked-List.md" "items": [
{
"text": "Linked List (Doubly)",
"path": "UI/Common/Utils/Linked-List.md"
}
]
} }
] ]
} }

BIN
docs/en/images/bookstore-home.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/images/bookstore-login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/en/images/db-migrator-output.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/en/images/package-manager-console-update-database.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
docs/en/images/rn-environment-local-ip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/en/images/rn-expo-interface.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/en/images/rn-host-local-ip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
docs/en/images/rn-login-iphone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/images/rn-tiered-local-ip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
docs/en/images/solution-files-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
docs/en/images/solution-files-non-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
docs/en/images/swagger-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
docs/en/images/vs-app-solution-structure-mongodb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

BIN
docs/en/images/vs-app-solution-structure-tiered.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

BIN
docs/en/images/vs-app-solution-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

BIN
docs/en/images/vs-spa-app-backend-structure-mongodb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

BIN
docs/en/images/vs-spa-app-backend-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

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

@ -537,11 +537,13 @@ import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books'; import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service'; import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({ @State<Books.State>({
name: 'BooksState', name: 'BooksState',
defaults: { books: {} } as Books.State, defaults: { books: {} } as Books.State,
}) })
@Injectable()
export class BooksState { export class BooksState {
@Selector() @Selector()
static getBooks(state: Books.State) { static getBooks(state: Books.State) {

4
docs/zh-Hans/API/Auto-API-Controllers.md

@ -6,7 +6,7 @@ ABP可以按照惯例 **自动** 将你的应用程序服务配置为API控制
## 配置 ## 配置
基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示: 基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示:
````csharp ````csharp
[DependsOn(BookStoreApplicationModule)] [DependsOn(BookStoreApplicationModule)]
@ -82,7 +82,7 @@ Configure<AbpAspNetCoreMvcOptions>(options =>
* 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`. * 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`.
* 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复. * 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复.
* 将结果转换为**camelCase**. * 将结果转换为**camelCase**.
* 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`. * 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`.
* 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method. * 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method.
* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId'). * 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId').

2
docs/zh-Hans/Application-Services.md

@ -380,4 +380,4 @@ public class DistrictKey
### 生命周期 ### 生命周期
应用服务的生命周期是[transient](Dependency-Injection)的,它们会自动注册到依赖注入系统. 应用服务的生命周期是[transient](Dependency-Injection)的,它们会自动注册到依赖注入系统.

2
docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md

@ -86,7 +86,7 @@ ABP框架定义了Tag Helper用于简单的创建bootstrap按钮.
### `icon-type` ### `icon-type`
`icon-type` 是一个可选参数。它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它. `icon-type` 是一个可选参数.它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它.
你可以为按钮选择以下图标类型: 你可以为按钮选择以下图标类型:

32
docs/zh-Hans/Audit-Logging.md

@ -43,12 +43,12 @@ Configure<AbpAuditingOptions>(options =>
* `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统. * `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统.
* `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志. * `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志.
* `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表. * `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表.
* `EntityHistorySelectors`:选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分. * `EntityHistorySelectors`:选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分.
* `Contributors`: `AuditLogContributor` 实现的列表. 贡献者是扩展审计日志系统的一种方式. 有关详细信息请参阅下面的"审计日志贡献者"部分. * `Contributors`: `AuditLogContributor` 实现的列表. 贡献者是扩展审计日志系统的一种方式. 有关详细信息请参阅下面的"审计日志贡献者"部分.
### 实体历史选择器 ### 实体历史选择器
保存您的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**. 保存你的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**.
要保存的所有实体的所有更改,只需使用 `AddAllEntities()` 扩展方法. 要保存的所有实体的所有更改,只需使用 `AddAllEntities()` 扩展方法.
@ -131,7 +131,7 @@ public class HomeController : AbpController
可以为任何类型的类(注册到[依赖注入](Dependency-Injection.md)并从依赖注入解析)启用审计日志,默认情况下仅对控制器和应用程序服务启用. 可以为任何类型的类(注册到[依赖注入](Dependency-Injection.md)并从依赖注入解析)启用审计日志,默认情况下仅对控制器和应用程序服务启用.
对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,您的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录. 对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,你的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录.
### 启用/禁用 实体 & 属性 ### 启用/禁用 实体 & 属性
@ -211,8 +211,8 @@ public class MyUser : Entity<Guid>
* **AuditLogInfo**: 具有以下属性: * **AuditLogInfo**: 具有以下属性:
* `ApplicationName`: 当你保存不同的应用审计日志到同一个数据库,这个属性用来区分应用程序. * `ApplicationName`: 当你保存不同的应用审计日志到同一个数据库,这个属性用来区分应用程序.
* `UserId`:当前用户的Id,用户未登录为 `null`. * `UserId`:当前用户的Id,用户未登录为 `null`.
* `UserName`:当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找). * `UserName`:当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找).
* `TenantId`: 当前租户的Id,对于多租户应用. * `TenantId`: 当前租户的Id,对于多租户应用.
* `TenantName`: 当前租户的名称,对于多租户应用. * `TenantName`: 当前租户的名称,对于多租户应用.
* `ExecutionTime`: 审计日志对象创建的时间. * `ExecutionTime`: 审计日志对象创建的时间.
@ -222,28 +222,28 @@ public class MyUser : Entity<Guid>
* `ClientIpAddress`: 客户端/用户设备的IP地址. * `ClientIpAddress`: 客户端/用户设备的IP地址.
* `CorrelationId`: 当前[相关Id](CorrelationId.md). 相关Id用于在单个逻辑操作中关联由不同应用程序(或微服务)写入的审计日志. * `CorrelationId`: 当前[相关Id](CorrelationId.md). 相关Id用于在单个逻辑操作中关联由不同应用程序(或微服务)写入的审计日志.
* `BrowserInfo`: 当前用户的浏览器名称/版本信息,如果有的话. * `BrowserInfo`: 当前用户的浏览器名称/版本信息,如果有的话.
* `HttpMethod`: 当前HTTP请求的方法(GET,POST,PUT,DELETE ...等). * `HttpMethod`: 当前HTTP请求的方法(GET,POST,PUT,DELETE ...等).
* `HttpStatusCode`: HTTP响应状态码. * `HttpStatusCode`: HTTP响应状态码.
* `Url`: 请求的URL. * `Url`: 请求的URL.
* **AuditLogActionInfo**: 一个 审计日志动作通常是web请求期间控制器动作或[应用服务](Application-Services.md)方法调用. 一个审计日志可以包含多个动作. 动作对象具有以下属性: * **AuditLogActionInfo**: 一个 审计日志动作通常是web请求期间控制器动作或[应用服务](Application-Services.md)方法调用. 一个审计日志可以包含多个动作. 动作对象具有以下属性:
* `ServiceName`:执行的控制器/服务的名称. * `ServiceName`:执行的控制器/服务的名称.
* `MethodName`:控制器/服务执行的方法的名称. * `MethodName`:控制器/服务执行的方法的名称.
* `Parameters`:传递给方法的参数的JSON格文本. * `Parameters`:传递给方法的参数的JSON格文本.
* `ExecutionTime`: 执行的时间. * `ExecutionTime`: 执行的时间.
* `ExecutionDuration`: 方法执行时长,以毫秒为单位. 可以用来观察方法的性能. * `ExecutionDuration`: 方法执行时长,以毫秒为单位. 可以用来观察方法的性能.
* **EntityChangeInfo**: 表示一个实体在Web请求中的变更. 审计日志可以包含0个或多个实体的变更. 实体变更具有以下属性: * **EntityChangeInfo**: 表示一个实体在Web请求中的变更. 审计日志可以包含0个或多个实体的变更. 实体变更具有以下属性:
* `ChangeTime`: 当实体被改变的时间. * `ChangeTime`: 当实体被改变的时间.
* `ChangeType`:具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2). * `ChangeType`:具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2).
* `EntityId`: 更改实体的Id. * `EntityId`: 更改实体的Id.
* `EntityTenantId`:实体所属的租户Id. * `EntityTenantId`:实体所属的租户Id.
* `EntityTypeFullName`: 实体的类型(类)的完整命名空间名称(例如Book实体的*Acme.BookStore.Book*. * `EntityTypeFullName`: 实体的类型(类)的完整命名空间名称(例如Book实体的*Acme.BookStore.Book*.
* **EntityPropertyChangeInfo**: 表示一个实体的属性的更改.一个实体的更改信息(上面已说明)可含有具有以下属性的一个或多个属性的更改: * **EntityPropertyChangeInfo**: 表示一个实体的属性的更改.一个实体的更改信息(上面已说明)可含有具有以下属性的一个或多个属性的更改:
* `NewValue`: 属性的新值. 如果实体已被删除为 `null`. * `NewValue`: 属性的新值. 如果实体已被删除为 `null`.
* `OriginalValue`:变更前旧/初始值. 如果实体是新创建为 `null`. * `OriginalValue`:变更前旧/初始值. 如果实体是新创建为 `null`.
* `PropertyName`: 实体类的属性名称. * `PropertyName`: 实体类的属性名称.
* `PropertyTypeFullName`:属性类型的完整命名空间名称. * `PropertyTypeFullName`:属性类型的完整命名空间名称.
* **Exception**: 审计日志对象可能包含零个或多个异常. 可以得到失败请求的异常信息. * **Exception**: 审计日志对象可能包含零个或多个异常. 可以得到失败请求的异常信息.
* **Comment**:用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释. * **Comment**:用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释.
除了上面说明的标准属性之外,`AuditLogInfo`, `AuditLogActionInfo` 和 `EntityChangeInfo` 对象还实现了`IHasExtraProperties` 接口,你可以向这些对象添加自定义属性. 除了上面说明的标准属性之外,`AuditLogInfo`, `AuditLogActionInfo` 和 `EntityChangeInfo` 对象还实现了`IHasExtraProperties` 接口,你可以向这些对象添加自定义属性.
@ -331,7 +331,7 @@ public class MyService : ITransientDependency
### 手动创建审计日志范围 ### 手动创建审计日志范围
你很少需要手动创建审计日志的范围,但如果你需要,可以使用 `IAuditingManager` 创建审计日志的范围. 你很少需要手动创建审计日志的范围,但如果你需要,可以使用 `IAuditingManager` 创建审计日志的范围.
例: 例:
````csharp ````csharp
public class MyService : ITransientDependency public class MyService : ITransientDependency
@ -366,7 +366,7 @@ public class MyService : ITransientDependency
} }
```` ````
您可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象. 你可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象.
## 审计日志模块 ## 审计日志模块

2
docs/zh-Hans/Authorization.md

@ -153,7 +153,7 @@ myGroup.AddPermission(
myGroup.AddPermission("Author_Management", isEnabled: false); myGroup.AddPermission("Author_Management", isEnabled: false);
```` ````
通常你不需要定义禁用权限(除非您暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法. 通常你不需要定义禁用权限(除非你暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法.
> 注意:检查一个未定义的权限会抛出异常,而被禁用的权限的返回禁止(false). > 注意:检查一个未定义的权限会抛出异常,而被禁用的权限的返回禁止(false).

3
docs/zh-Hans/AutoMapper-Integration.md

@ -1,3 +0,0 @@
## AutoMapper Integration
TODO

4
docs/zh-Hans/Background-Workers.md

@ -2,7 +2,7 @@
## 介绍 ## 介绍
背景工人在应用简单独立的线程在后台运行。一般来说,他们定期运行,以执行一些任务。例子; 背景工人在应用简单独立的线程在后台运行.一般来说,他们定期运行,以执行一些任务.例子;
后台工作者在应用程序后台运行的简单的独立线程,一般来说它们定期运行执行一些任务.例如; 后台工作者在应用程序后台运行的简单的独立线程,一般来说它们定期运行执行一些任务.例如;
* 后台工作者可以定期**删除过时的日志**. * 后台工作者可以定期**删除过时的日志**.
@ -72,7 +72,7 @@ public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
} }
```` ````
* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性。 * `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性.
* 它需要实现 `DoWorkAsync` 方法**执行**定期任务. * 它需要实现 `DoWorkAsync` 方法**执行**定期任务.
* 最好使用 `PeriodicBackgroundWorkerContext` **解析依赖** 而不是构造函数. 因为 `AsyncPeriodicBackgroundWorkerBase` 使用 `IServiceScope` 在你的任务执行结束时会对其 **disposed**. * 最好使用 `PeriodicBackgroundWorkerContext` **解析依赖** 而不是构造函数. 因为 `AsyncPeriodicBackgroundWorkerBase` 使用 `IServiceScope` 在你的任务执行结束时会对其 **disposed**.
* `AsyncPeriodicBackgroundWorkerBase` **捕获并记录** 由 `DoWorkAsync` 方法抛出的 **异常**. * `AsyncPeriodicBackgroundWorkerBase` **捕获并记录** 由 `DoWorkAsync` 方法抛出的 **异常**.

18
docs/zh-Hans/Best-Practices/Application-Services.md

@ -17,7 +17,7 @@
##### 基础DTO ##### 基础DTO
**推荐** 为实体定义一个**基础**DTO. **推荐** 为聚合根定义一个**基础**DTO.
- 直接包含实体中所有的**原始属性**. - 直接包含实体中所有的**原始属性**.
- 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`). - 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`).
@ -27,7 +27,7 @@
```c# ```c#
[Serializable] [Serializable]
public class IssueDto : FullAuditedEntityDto<Guid> public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -57,7 +57,7 @@ public class IssueLabelDto
````C# ````C#
[Serializable] [Serializable]
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid> public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -66,14 +66,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
} }
[Serializable] [Serializable]
public class MilestoneDto : EntityDto<Guid> public class MilestoneDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public bool IsClosed { get; set; } public bool IsClosed { get; set; }
} }
[Serializable] [Serializable]
public class LabelDto : EntityDto<Guid> public class LabelDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public string Color { get; set; } public string Color { get; set; }
@ -120,6 +120,7 @@ Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
* **推荐** 使用 `CreateAsync` 做为**方法名**. * **推荐** 使用 `CreateAsync` 做为**方法名**.
* **推荐** 使用**专门的输入DTO**来创建实体. * **推荐** 使用**专门的输入DTO**来创建实体.
* **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
* **推荐** 使用 **data annotations** 进行输入验证. * **推荐** 使用 **data annotations** 进行输入验证.
* 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量). * 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
* **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性. * **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性.
@ -134,7 +135,7 @@ Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
````C# ````C#
[Serializable] [Serializable]
public class CreateQuestionDto public class CreateQuestionDto : ExtensibleObject
{ {
[Required] [Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)] [StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)]
@ -151,6 +152,7 @@ public class CreateQuestionDto
- **推荐** 使用 `UpdateAsync` 做为**方法名**. - **推荐** 使用 `UpdateAsync` 做为**方法名**.
- **推荐** 使用**专门的输入DTO**来更新实体. - **推荐** 使用**专门的输入DTO**来更新实体.
- **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
- **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO. - **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO.
- **推荐** 使用 **data annotations** 进行输入验证. - **推荐** 使用 **data annotations** 进行输入验证.
- 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量). - 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
@ -199,6 +201,10 @@ Task<int> VoteAsync(Guid id, VoteType type);
* **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询. * **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询.
#### 额外的属性
* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务.
#### 操作/删除 实体 #### 操作/删除 实体
* **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作. * **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作.

1
docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md

@ -2,6 +2,7 @@
* **推荐** 在 **application.contracts** 层中定义DTO. * **推荐** 在 **application.contracts** 层中定义DTO.
* **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等). * **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等).
* **推荐** 从**聚合根**的**扩展DTO**继承(如 `ExtensibleAuditedEntityDto<TKey>`), 因为聚合根是可扩展的额外的属性使用这种方式映射到DTO.
* **推荐** 定义 **public getter 和 setter** 的DTO成员 . * **推荐** 定义 **public getter 和 setter** 的DTO成员 .
* **推荐** 使用 **data annotations** **验证** service输入DTO的属性. * **推荐** 使用 **data annotations** **验证** service输入DTO的属性.
* **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口. * **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口.

14
docs/zh-Hans/Blog-Posts/2019-02-22/Post.md

@ -13,7 +13,7 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
- 使用[Ocelot](https://github.com/ThreeMammals/Ocelot)库开发了多个**网关** / BFF(后端为前端(Backend for Frontends)). - 使用[Ocelot](https://github.com/ThreeMammals/Ocelot)库开发了多个**网关** / BFF(后端为前端(Backend for Frontends)).
- 使用[IdentityServer](https://identityserver.io/)框架开发**身份验证服务**.它也是一个带有必要UI的SSO(单点登录)应用程序. - 使用[IdentityServer](https://identityserver.io/)框架开发**身份验证服务**.它也是一个带有必要UI的SSO(单点登录)应用程序.
- 有**多个数据库**.一些微服务有自己的数据库,而一些服务/应用程序共享一个数据库(以演示不同的用例). - 有**多个数据库**.一些微服务有自己的数据库,而一些服务/应用程序共享一个数据库(以演示不同的用例).
- 具有不同类型的数据库:**SQL Server**(使用**Entity Framework Core** ORM)和**MongoDB**. - 具有不同类型的数据库:**SQL Server**(使用**Entity Framework Core** ORM)和**MongoDB**.
- 有一个**控制台应用程序**来显示通过身份验证使用服务的最简单方法. - 有一个**控制台应用程序**来显示通过身份验证使用服务的最简单方法.
- 使用[Redis](https://redis.io/)进行**分布式缓存**. - 使用[Redis](https://redis.io/)进行**分布式缓存**.
- 使用[RabbitMQ](https://www.rabbitmq.com/)进行服务到服务(service-to-service)的**消息传递**. - 使用[RabbitMQ](https://www.rabbitmq.com/)进行服务到服务(service-to-service)的**消息传递**.
@ -28,27 +28,27 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
## 路线图 ## 路线图
在第一个稳定版本(v1.0)之前还有很多工作要做.您可以在GitHub仓库上看到[优先的积压项目](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog). 在第一个稳定版本(v1.0)之前还有很多工作要做.你可以在GitHub仓库上看到[优先的积压项目](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog).
根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋. 根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋.
我们还将完善[文档](https://abp.io/documents/abp/latest),因为它现在还远未完成. 我们还将完善[文档](https://abp.io/documents/abp/latest),因为它现在还远未完成.
第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将您的想法写为对此帖的评论. 第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将你的想法写为对此帖的评论.
## 中文网 ## 中文网
中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming). 中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming).
## NDC {London} 2019 ## NDC {London} 2019
很高兴作为合作伙伴参加[NDC {London}](https://ndc-london.com/)2019 .我们已经与许多开发人员讨论过当前的ASP.NET Boilerplate和ABP vNext,我们得到了很好的反馈. 很高兴作为合作伙伴参加[NDC {London}](https://ndc-london.com/)2019 .我们已经与许多开发人员讨论过当前的ASP.NET Boilerplate和ABP vNext,我们得到了很好的反馈.
我们还有机会与[Scott Hanselman](https://twitter.com/shanselman)和[Jon Galloway](https://twitter.com/jongalloway)交谈.他们参观了我们的展位,我们谈到了ABP vNext的想法.他们喜欢新的ABP框架的功能,方法和目标.在twitter上查看一些照片和评论: 我们还有机会与[Scott Hanselman](https://twitter.com/shanselman)和[Jon Galloway](https://twitter.com/jongalloway)交谈.他们参观了我们的展位,我们谈到了ABP vNext的想法.他们喜欢新的ABP框架的功能,方法和目标.在twitter上查看一些照片和评论:
![scott-and-jon](scott-and-jon.png) ![scott-and-jon](scott-and-jon.png)
## 跟上步伐 ## 跟上步伐
* 您可以标星并关注**GitHub**存储库:https://github.com/abpframework/abp * 你可以标星并关注**GitHub**存储库:https://github.com/abpframework/abp
* 您可以关注官方**Twitter**帐户获取新闻:https://twitter.com/abpframework * 你可以关注官方**Twitter**帐户获取新闻:https://twitter.com/abpframework

12
docs/zh-Hans/Blog-Posts/2019-06-19 v0_18_Release/Post.md

@ -4,7 +4,7 @@ ABP v0.18已发布, 包含解决的[80+个issue](https://github.com/abpframework
## 网站更改 ## 网站更改
[abp.io](https://abp.io)网站**完全更新**以突出ABP框架的目标和重要功能.文档和博客网址也会更改: [abp.io](https://abp.io)网站**完全更新**以突出ABP框架的目标和重要功能.文档和博客网址也会更改:
- `abp.io/documents`移至[docs.abp.io](https://docs.abp.io). - `abp.io/documents`移至[docs.abp.io](https://docs.abp.io).
- `abp.io/blog`转移到[blog.abp.io](https://blog.abp.io). - `abp.io/blog`转移到[blog.abp.io](https://blog.abp.io).
@ -21,25 +21,25 @@ ABP CLI现在是创建新项目的首选方式,你仍然可以从[开始](https:
### 用法 ### 用法
使用命令行窗口安装ABP CLI: 使用命令行窗口安装ABP CLI:
```` bash ```` bash
dotnet tool install -g Volo.Abp.Cli dotnet tool install -g Volo.Abp.Cli
```` ````
创建一个新应用程序: 创建一个新应用程序:
```` bash ```` bash
abp new Acme.BookStore abp new Acme.BookStore
```` ````
将模块添加到应用程序: 将模块添加到应用程序:
```` bash ```` bash
abp add-module Volo.Blogging abp add-module Volo.Blogging
```` ````
更新解决方案中所有与ABP相关的包: 更新解决方案中所有与ABP相关的包:
```` bash ```` bash
abp update abp update
@ -59,7 +59,7 @@ abp update
## 更改日志 ## 更改日志
以下是此版本附带的一些其他功能和增强功能: 以下是此版本附带的一些其他功能和增强功能:
* 新[Volo.Abp.Dapper](https://www.nuget.org/packages/Volo.Abp.Dapper)包. * 新[Volo.Abp.Dapper](https://www.nuget.org/packages/Volo.Abp.Dapper)包.
* 新[Volo.Abp.Specifications](https://www.nuget.org/packages/Volo.Abp.Specifications)包. * 新[Volo.Abp.Specifications](https://www.nuget.org/packages/Volo.Abp.Specifications)包.

4
docs/zh-Hans/Blog-Posts/2019-08-16 v0_19_Release/Post.md

@ -14,12 +14,12 @@ ABP v0.19已发布,包含解决的[~90个问题](https://github.com/abpframework
* 更新了[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)和[下载页面](https://abp.io/get-started),以便能够使用新的UI选项生成项目. * 更新了[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)和[下载页面](https://abp.io/get-started),以便能够使用新的UI选项生成项目.
* 创建了[教程](https://docs.abp.io/en/abp/latest/Tutorials/Angular/Part-I)以使用新的UI选项快速入门. * 创建了[教程](https://docs.abp.io/en/abp/latest/Tutorials/Angular/Part-I)以使用新的UI选项快速入门.
我们基于最新的Angular工具和趋势创建了模板,文档和基础架构: 我们基于最新的Angular工具和趋势创建了模板,文档和基础架构:
* 使用[NgBootstrap](https://ng-bootstrap.github.io/)和[PrimeNG](https://www.primefaces.org/primeng/)作为UI组件库.你可以使用自己喜欢的库,没问题,但预构建的模块可以使用这些库. * 使用[NgBootstrap](https://ng-bootstrap.github.io/)和[PrimeNG](https://www.primefaces.org/primeng/)作为UI组件库.你可以使用自己喜欢的库,没问题,但预构建的模块可以使用这些库.
* 使用[NGXS](https://ngxs.gitbook.io/ngxs/)作为状态管理库. * 使用[NGXS](https://ngxs.gitbook.io/ngxs/)作为状态管理库.
Angular是第一个SPA UI选项,但它不是最后一个.在v1.0发布之后,我们将开始第二个UI选项的工作.虽然尚未决定,但候选的有Blazor,React和Vue.js. 等待你的反馈.你可以使用以下issue进行投票(thumb): Angular是第一个SPA UI选项,但它不是最后一个.在v1.0发布之后,我们将开始第二个UI选项的工作.虽然尚未决定,但候选的有Blazor,React和Vue.js. 等待你的反馈.你可以使用以下issue进行投票(thumb):
* [Blazor](https://github.com/abpframework/abp/issues/394) * [Blazor](https://github.com/abpframework/abp/issues/394)
* [Vue.js](https://github.com/abpframework/abp/issues/1168) * [Vue.js](https://github.com/abpframework/abp/issues/1168)

4
docs/zh-Hans/Blog-Posts/2019-09-25 v0_21_Release/Post.md

@ -14,8 +14,8 @@ ABP框架越来越接近v1.0.我们打算在今年10月中旬发布1.0. 现在,
## Techorama荷兰2019 ## Techorama荷兰2019
[Techorama NL](https://techorama.nl/)是欧洲最大的会议之一.今年,Volosoft是会议的赞助商,并将有一个展位与软件开发人员讨论ABP框架和软件开发.我们的展位墙如下图所示: [Techorama NL](https://techorama.nl/)是欧洲最大的会议之一.今年,Volosoft是会议的赞助商,并将有一个展位与软件开发人员讨论ABP框架和软件开发.我们的展位墙如下图所示:
![volosoft-booth](volosoft-booth.png) ![volosoft-booth](volosoft-booth.png)
如果您也参加会议,请到展位讨论ABP框架.我们还为您准备了一些私货:) 如果你也参加会议,请到展位讨论ABP框架.我们还为你准备了一些私货:)

16
docs/zh-Hans/Blog-Posts/2020-03-19 v2_3_Release/Post.md

@ -24,7 +24,7 @@
我们终于完成了**react native移动应用程序**.目前,它可以让你**登录**,管理**用户**和**租户**.它利用ABP框架相同的设置,授权和本地化系统. 我们终于完成了**react native移动应用程序**.目前,它可以让你**登录**,管理**用户**和**租户**.它利用ABP框架相同的设置,授权和本地化系统.
应用程序的一些截图: 应用程序的一些截图:
![mobile-ui](react-native-ui.png) ![mobile-ui](react-native-ui.png)
@ -34,7 +34,7 @@
从我们的Angular应用程序中调用服务器中的REST端点是很常见的.这种情况下,我们一般创建**服务**(在服务器上包含各个服务的方法)和**模型对象**(对应服务器上的[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)). 从我们的Angular应用程序中调用服务器中的REST端点是很常见的.这种情况下,我们一般创建**服务**(在服务器上包含各个服务的方法)和**模型对象**(对应服务器上的[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)).
除了手动创建这样的与服务器交互的服务外,我们可以使用像[NSWAG](https://github.com/RicoSuter/NSwag)工具来为我们生成服务代理.但是NSWAG有以下几个我们遇到的问题: 除了手动创建这样的与服务器交互的服务外,我们可以使用像[NSWAG](https://github.com/RicoSuter/NSwag)工具来为我们生成服务代理.但是NSWAG有以下几个我们遇到的问题:
* 它产生一个**大,单一**的.ts文件; * 它产生一个**大,单一**的.ts文件;
* 当你的应用程序增长时,它变得**太大**了. * 当你的应用程序增长时,它变得**太大**了.
@ -58,12 +58,12 @@ abp generate-proxy
### 添加模块的源代码 ### 添加模块的源代码
应用程序启动模板带有一些[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index), 以**Nuget和NPM包**的方式**预先安装了** .这样做有几个重要的优点: 应用程序启动模板带有一些[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index), 以**Nuget和NPM包**的方式**预先安装了** .这样做有几个重要的优点:
* 当新版本可用时, 你可以 **轻松地[升级](https://docs.abp.io/en/abp/latest/CLI#update)** 这些模块. * 当新版本可用时, 你可以 **轻松地[升级](https://docs.abp.io/en/abp/latest/CLI#update)** 这些模块.
* 你的解决方案**更干净**,这样你就可以专注于自己的代码. * 你的解决方案**更干净**,这样你就可以专注于自己的代码.
但是,当你需要对一个依赖的模块**大量定制**时,就不如它的代码在你的应用程序中那么容易.为了解决这个问题,我们引入了一个[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)的新命令, 在你的解决方案中用代码**替换**Nuget包.用法很简单: 但是,当你需要对一个依赖的模块**大量定制**时,就不如它的代码在你的应用程序中那么容易.为了解决这个问题,我们引入了一个[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)的新命令, 在你的解决方案中用代码**替换**Nuget包.用法很简单:
````bash ````bash
abp add-module --with-source-code abp add-module --with-source-code
@ -75,19 +75,19 @@ abp add-module --with-source-code
此外,我们也创建了文档来说明如何定制依赖的模块而不改变它们的源代码(见下面的部分).仍然建议以包的方式使用模块,以便在以后可以轻松升级. 此外,我们也创建了文档来说明如何定制依赖的模块而不改变它们的源代码(见下面的部分).仍然建议以包的方式使用模块,以便在以后可以轻松升级.
> 免费模块的源代码是**MIT**许可,所以你可以自由更改它们并添加到您的解决方案中. > 免费模块的源代码是**MIT**许可,所以你可以自由更改它们并添加到你的解决方案中.
### 切换到预览版 ### 切换到预览版
ABP框架正在迅速发展,我们经常发布新版本.不过,如果你想更紧密地追随它,你可以使用**每日预览包**. ABP框架正在迅速发展,我们经常发布新版本.不过,如果你想更紧密地追随它,你可以使用**每日预览包**.
我们创建了一个ABP CLI命令来轻松地为你的解决方案**更新到最新的预览包**.在你的解决方案的根文件夹中运行以下命令: 我们创建了一个ABP CLI命令来轻松地为你的解决方案**更新到最新的预览包**.在你的解决方案的根文件夹中运行以下命令:
````bash ````bash
abp switch-to-preview abp switch-to-preview
```` ````
它会修改所有ABP相关的NuGet和NPM包的版本.当你需要时你也可以**切换回最新稳定版**: 它会修改所有ABP相关的NuGet和NPM包的版本.当你需要时你也可以**切换回最新稳定版**:
````bash ````bash
abp switch-to-stable abp switch-to-stable
@ -131,7 +131,7 @@ abp switch-to-stable
## 下一步? ## 下一步?
我们未来几个月的目标如下: 我们未来几个月的目标如下:
* 完成**文档和示例**,写更多的教程. * 完成**文档和示例**,写更多的教程.
* 使框架和现有模块的更加**可定制和可扩展**. * 使框架和现有模块的更加**可定制和可扩展**.

14
docs/zh-Hans/CLI.md

@ -41,12 +41,12 @@ abp new Acme.BookStore
* `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有: * `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有:
* `app` (default): [应用程序模板](Startup-Templates/Application.md). 其他选项: * `app` (default): [应用程序模板](Startup-Templates/Application.md). 其他选项:
* `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项: * `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项:
* `mvc`: ASP.NET Core MVC.此模板的其他选项: * `mvc`: ASP.NET Core MVC.此模板的其他选项:
* `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的.如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景. * `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的.如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景.
* `angular`: Angular. 这个模板还有一些额外的选项: * `angular`: Angular. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `none`: 无UI. 这个模板还有一些额外的选项: * `none`: 无UI. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `--mobile` 或者 `-m`: 指定移动应用程序框架. 默认框架是 `react-native`. 其他选项: * `--mobile` 或者 `-m`: 指定移动应用程序框架. 默认框架是 `react-native`. 其他选项:
* `none`: 不包含移动应用程序. * `none`: 不包含移动应用程序.
@ -57,7 +57,7 @@ abp new Acme.BookStore
* `module`: [Module template](Startup-Templates/Module.md). 其他选项: * `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI). * `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,您会希望使用最新的版本. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`). * `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中. * `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串! * `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串!
@ -186,9 +186,9 @@ abp generate-proxy [options]
#### Options #### Options
* `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行. * `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注! * `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注!
* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令. * `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
示例: 示例:

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

@ -50,7 +50,7 @@ ObjectExtensionManager.Instance
* 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名). * 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名).
* 你还需要提供一个使用[EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)定义数据库映射属性的操作. * 你还需要提供一个使用[EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)定义数据库映射属性的操作.
> 必须在使用相关的 `DbContext` 之前执行此代码. 应用程序启动模板定义了一个名为 `YourProjectNameEntityExtensions` 的静态类. 你可以在此类中定义扩展确保在正确的时间执行它. 否则你需要自己处理. > 必须在使用相关的 `DbContext` 之前执行此代码. 应用程序启动模板定义了一个名为 `YourProjectNameEfCoreEntityExtensionMappings` 的静态类. 你可以在此类中定义扩展确保在正确的时间执行它. 否则你需要自己处理.
定义实体扩展后你需要使用EF Core的[Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration)和[Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database)命令来创建code first迁移类并更新数据库. 定义实体扩展后你需要使用EF Core的[Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration)和[Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database)命令来创建code first迁移类并更新数据库.
@ -58,8 +58,6 @@ ObjectExtensionManager.Instance
## 创建新实体映射到同一个数据库表/Collection ## 创建新实体映射到同一个数据库表/Collection
尽管额外属性方法**易于使用**并且适用于一些场景,但它具有[实体文档](Entities.md)中描述的一些缺点.
另一个方法是**创建你自己的实体**映射到**同一个数据库库**(对于MongoDB数据库是collection) 另一个方法是**创建你自己的实体**映射到**同一个数据库库**(对于MongoDB数据库是collection)
[应用程序启动模板](Startup-Templates/Application.md)的 `AppUser` 已经实现了这种方法. [EF Core迁移文档](Entity-Framework-Core-Migrations.md)描述了在这些情况下如何实现和管理**EF Core数据库迁移**. 这种方法同样适用于MongoDB,但你不需要处理数据库迁移问题. [应用程序启动模板](Startup-Templates/Application.md)的 `AppUser` 已经实现了这种方法. [EF Core迁移文档](Entity-Framework-Core-Migrations.md)描述了在这些情况下如何实现和管理**EF Core数据库迁移**. 这种方法同样适用于MongoDB,但你不需要处理数据库迁移问题.

104
docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md

@ -54,12 +54,13 @@ context.Services.Replace(
## 重写一个服务类 ## 重写一个服务类
大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法。 大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法.
### 示例: 重写服务方法 ### 示例: 重写服务方法
````csharp ````csharp
[Dependency(ReplaceServices = true)] [Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))]
public class MyIdentityUserAppService : IdentityUserAppService public class MyIdentityUserAppService : IdentityUserAppService
{ {
//... //...
@ -161,6 +162,105 @@ public class MyIdentityUserManager : IdentityUserManager
控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写. 控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写.
## 扩展数据传输对象
你可以如[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md)所述扩展实体. 并使用上面介绍的重写相关服务**使用自定义属性****执行其他业务逻辑**.
应用程序使用的数据传输对象(**DTO**)同样可扩展. 这样你可以使服务返回其他属性并在UI(或其他客户端)得到其他属性.
### 示例
假设你已经按照[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md)中的说明添加了 `SocialSecurityNumber` 并希望从 `IdentityUserAppService的GetListAsync` 方法获取用户列表时包括此属性.
你可以使用[对象扩展系统](Object-Extensions.md)将属性添加到 `IdentityUserDto`. 在应用程序启动模板带有的 `YourProjectNameDtoExtensions` 类中编写以下代码:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserDto, string>(
"SocialSecurityNumber"
);
````
这段代码为 `IdentityUserDto` 类添加了 `string` 类型的 `SocialSecurityNumber` 属性. 现在你可以在RREST API客户端调用 `/api/identity/users` HTTP API(内部使用 `IdentityUserAppService`),你会在 `extraProperties` 部分看到 `SocialSecurityNumber` 值.
````json
{
"totalCount": 1,
"items": [{
"tenantId": null,
"userName": "admin",
"name": "admin",
"surname": null,
"email": "admin@abp.io",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"twoFactorEnabled": false,
"lockoutEnabled": true,
"lockoutEnd": null,
"concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c",
"isDeleted": false,
"deleterId": null,
"deletionTime": null,
"lastModificationTime": "2020-04-09T21:25:47.0740706",
"lastModifierId": null,
"creationTime": "2020-04-09T21:25:46.8308744",
"creatorId": null,
"id": "8edecb8f-1894-a9b1-833b-39f4725db2a3",
"extraProperties": {
"SocialSecurityNumber": "123456789"
}
}]
}
````
手动添加了 `123456789` 值到数据库中.
所有预构建的模块都在DTO中支持额外属性,你可以对其轻松的配置.
### 定义检查
当为实体[定义](Customizing-Application-Modules-Extending-Entities.md)额外的属性时,由于安全性它不会自动出现在所有相关的DTO中. 额外属性可能包含敏感数据并且你可能不想默认公开给客户端.
因此如果要用于DTO,需要为相应的DTO显式定义相同的属性(如上所述). 如果要允许在用户创建时进行设置还需要为 `IdentityUserCreateDto` 定义.
如果属性并不是安全敏感,这可能会很枯燥. 对象扩展系统允许你忽略检查定义的属性. 参阅示例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
options.CheckPairDefinitionOnMapping = false;
}
);
````
这是定义实体属性的另一种方法( 有关 `ObjectExtensionManager` 更多信息,请参阅[文档](Object-Extensions.md)). 这次我们设置了 `CheckPairDefinitionOnMapping` 为false,在将实体映射到DTO时会跳过定义检查.
如果你不喜欢这种方法,但想简单的向多个对象(DTO)添加单个属, `AddOrUpdateProperty` 可以使用类型数组添加额外的属性:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
### 关于用户界面
该系统允许你向实体和DTO添加额外的属性并执行自定义业务代码,但它与用户界面无关.
参阅 [重写用户界面](Customizing-Application-Modules-Overriding-User-Interface.md) 指南了解关于UI部分.
## 如何找到服务? ## 如何找到服务?
[模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务. [模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务.

10
docs/zh-Hans/Entities.md

@ -72,7 +72,7 @@ public class BookAppService : ApplicationService, IBookAppService
* `BookAppService` 注入图书实体的默认[仓库](Repositories.md),使用`InsertAsync`方法插入 `Book` 到数据库中. * `BookAppService` 注入图书实体的默认[仓库](Repositories.md),使用`InsertAsync`方法插入 `Book` 到数据库中.
* `GuidGenerator`类型是 `IGuidGenerator`,它是在`ApplicationService`基类中定义的属性. ABP将这样常用属性预注入,所以不需要手动[注入](Dependency-Injection.md). * `GuidGenerator`类型是 `IGuidGenerator`,它是在`ApplicationService`基类中定义的属性. ABP将这样常用属性预注入,所以不需要手动[注入](Dependency-Injection.md).
* 如果您想遵循DDD最佳实践,请参阅下面的*聚合示例*部分. * 如果你想遵循DDD最佳实践,请参阅下面的*聚合示例*部分.
### 具有复合键的实体 ### 具有复合键的实体
@ -228,7 +228,7 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
## 基类和接口的审计属性 ## 基类和接口的审计属性
有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**. 有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**.
### 审计接口 ### 审计接口
@ -285,7 +285,7 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
所有这些基类都有非泛型版本,可以使用 `AuditedEntity` 和 `FullAuditedAggregateRoot` 来支持复合主键; 所有这些基类都有非泛型版本,可以使用 `AuditedEntity` 和 `FullAuditedAggregateRoot` 来支持复合主键;
所有这些基类也有 `... WithUser`,像 `FullAuditedAggregateRootWithUser<TUser>` 和 `FullAuditedAggregateRootWithUser<TKey, TUser>`. 这样就可以将导航属性添加到你的用户实体. 但在聚合根之间添加导航属性不是一个好做法,所以这种用法是不建议的(除非你使用EF Core之类的ORM可以很好地支持这种情况,并且你真的需要它. 请记住这种方法不适用于NoSQL数据库(如MongoDB),你必须真正实现聚合模式). 所有这些基类也有 `... WithUser`,像 `FullAuditedAggregateRootWithUser<TUser>` 和 `FullAuditedAggregateRootWithUser<TKey, TUser>`. 这样就可以将导航属性添加到你的用户实体. 但在聚合根之间添加导航属性不是一个好做法,所以这种用法是不建议的(除非你使用EF Core之类的ORM可以很好地支持这种情况,并且你真的需要它. 请记住这种方法不适用于NoSQL数据库(如MongoDB),你必须真正实现聚合模式).
## 额外的属性 ## 额外的属性
@ -379,10 +379,10 @@ public static class IdentityUserExtensions
* 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**. * 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**.
* 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易. * 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易.
### 额外属性背后的实体 ### 额外属性背后的实体
`IHasExtraProperties` 不限于与实体一起使用. 你可以为任何类型的类实现这个接口,使用 `GetProperty`,`SetProperty` 和其他相关方法. `IHasExtraProperties` 不限于与实体一起使用. 你可以为任何类型的类实现这个接口,使用 `GetProperty`,`SetProperty` 和其他相关方法.
## 另请参阅 ## 另请参阅
* [实体设计最佳实践指南](Best-Practices/Entities.md) * [实体设计最佳实践指南](Best-Practices/Entities.md)

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

@ -1,7 +1,7 @@
 
# EF Core数据库迁移 # EF Core数据库迁移
本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论您可能希望为自己的应用程序实现的**各种场景**. 本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论你可能希望为自己的应用程序实现的**各种场景**.
> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md). > 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md).
@ -95,7 +95,7 @@ Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids";
这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ). 这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ).
**每个模块都使用自己的 `DbContext` 类**来访问数据库。同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体: **每个模块都使用自己的 `DbContext` 类**来访问数据库.同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体:
````csharp ````csharp
[ConnectionStringName("Default")] [ConnectionStringName("Default")]
@ -268,10 +268,10 @@ public class BackgroundJobsDbContext
##### 重用模块的表 ##### 重用模块的表
您可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择: 你可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择:
1. 你可以**直接使用模块定义的实体**(你仍然可以在某种程度上[扩展实体](Customizing-Application-Modules-Extending-Entities.md)). 1. 你可以**直接使用模块定义的实体**(你仍然可以在某种程度上[扩展实体](Customizing-Application-Modules-Extending-Entities.md)).
2. 你可以**创建一个新的实体**映射到同一个数据库表。 2. 你可以**创建一个新的实体**映射到同一个数据库表.
###### 使用由模块定义的实体 ###### 使用由模块定义的实体
@ -307,7 +307,7 @@ namespace Acme.BookStore
示例注入了 `IRepository<IdentityUser,Guid>`(默认仓储). 它定义了标准的存储库方法并实现了 `IQueryable` 接口. 示例注入了 `IRepository<IdentityUser,Guid>`(默认仓储). 它定义了标准的存储库方法并实现了 `IQueryable` 接口.
另外,身份模块定义了 `IIdentityUserRepository`(自定义仓储),你的应用程序也可以注入和使用它. `IIdentityUserRepository` 为 `IdentityUser` 实体提供了额外的定制方法,但它没有实现 `IQueryable`. 另外,身份模块定义了 `IIdentityUserRepository`(自定义仓储),你的应用程序也可以注入和使用它. `IIdentityUserRepository` 为 `IdentityUser` 实体提供了额外的定制方法,但它没有实现 `IQueryable`.
###### 创建一个新的实体 ###### 创建一个新的实体
@ -352,7 +352,7 @@ namespace Acme.BookStore.Roles
* 它继承了[`AggregateRoot<Guid>`类](Entities.md)和实现了[`IMultiTenant`]接口(Multi-Tenancy.md),因为 `IdentityRole` 也做了同样的继承. * 它继承了[`AggregateRoot<Guid>`类](Entities.md)和实现了[`IMultiTenant`]接口(Multi-Tenancy.md),因为 `IdentityRole` 也做了同样的继承.
* 你可以添加 `IdentityRole` 实体定义的任何属性. 本例只加了 `TenantId` 和 `Name` 属性,因为我们这里只需要它们. 你可以把setters设置为私有(如同本例)以防意外更改身份模块的属性. * 你可以添加 `IdentityRole` 实体定义的任何属性. 本例只加了 `TenantId` 和 `Name` 属性,因为我们这里只需要它们. 你可以把setters设置为私有(如同本例)以防意外更改身份模块的属性.
* 你可以添加自定义(附加)属性. 本例添加了 `Title` 属性. * 你可以添加自定义(附加)属性. 本例添加了 `Title` 属性.
* **构造函数是私有的**,所以它不允许直接创建一个新的 `AppRole` 实体。创建角色身份模块的责任. 你可以查询角色,设置/更新自定义属性,但做为最佳实践你不应该在代码中创建和删除角色(尽管没有强制的限制). * **构造函数是私有的**,所以它不允许直接创建一个新的 `AppRole` 实体.创建角色身份模块的责任. 你可以查询角色,设置/更新自定义属性,但做为最佳实践你不应该在代码中创建和删除角色(尽管没有强制的限制).
现在是时候定义EF Core映射. 打开应用程序的 `DbContext` (此示例中是 `BookStoreDbContext` )添加以下属性: 现在是时候定义EF Core映射. 打开应用程序的 `DbContext` (此示例中是 `BookStoreDbContext` )添加以下属性:
@ -360,7 +360,7 @@ namespace Acme.BookStore.Roles
public DbSet<AppRole> Roles { get; set; } public DbSet<AppRole> Roles { get; set; }
```` ````
然后在 `OnModelCreating` 方法中配置映射(调用 `base.OnModelCreating(builder)` 之后): 然后在 `OnModelCreating` 方法中配置映射(调用 `base.OnModelCreating(builder)` 之后):
````csharp ````csharp
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)

6
docs/zh-Hans/Entity-Framework-Core-MySQL.md

@ -12,12 +12,12 @@
## UseMySQL() ## UseMySQL()
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseMySQL()`. 检查下列文件: 查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseMySQL()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. * `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. * `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. > 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
## 更改连接字符串 ## 更改连接字符串
@ -27,7 +27,7 @@ MySQL连接字符串与SQL Server连接字符串不同. 所以检查你的解决
## 更改迁移DbContext ## 更改迁移DbContext
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据您的DBMS配置字段. MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据你的DBMS配置字段.
启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表. 启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表.

2
docs/zh-Hans/Entity-Framework-Core-Other-DBMS.md

@ -63,7 +63,7 @@ MySQL连接字符串与SQL Server连接字符串不同. 所以检查你的解决
## 更改迁移DbContext ## 更改迁移DbContext
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据您的DBMS配置字段. MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据你的DBMS配置字段.
启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表. 启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表.

4
docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md

@ -12,12 +12,12 @@
## UsePostgreSql() ## UsePostgreSql()
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UsePostgreSql()`. 检查下列文件: 查找你的解决方案中 `UseSqlServer()`调用,替换为 `UsePostgreSql()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. * `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. * `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. > 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
## 更改连接字符串 ## 更改连接字符串

4
docs/zh-Hans/Entity-Framework-Core-SQLite.md

@ -12,12 +12,12 @@
## UseSqlite() ## UseSqlite()
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseSqlite()`. 检查下列文件: 查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseSqlite()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs. * `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs. * `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件. > 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
## 更改连接字符串 ## 更改连接字符串

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

@ -108,7 +108,7 @@ protected override void OnModelCreating(ModelBuilder builder)
### 配置连接字符串选择 ### 配置连接字符串选择
如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称. 如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称.
例: 例:
```csharp ```csharp
[ConnectionStringName("MySecondConnString")] [ConnectionStringName("MySecondConnString")]
@ -274,7 +274,7 @@ public override async Task DeleteAsync(
## 访问 EF Core API ## 访问 EF Core API
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例: 大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例:
````csharp ````csharp
public class BookService public class BookService
@ -304,7 +304,7 @@ public class BookService
默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中. 默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中.
实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档: 实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档:
* [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md) * [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md)
* [实体](Entities.md) * [实体](Entities.md)
@ -313,7 +313,7 @@ public class BookService
### ObjectExtensionManager.Instance ### ObjectExtensionManager.Instance
`ObjectExtensionManager` 实现单例模式,因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作。 `ObjectExtensionManager` 实现单例模式,因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作.
### MapEfCoreProperty ### MapEfCoreProperty
@ -417,7 +417,7 @@ context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
}); });
```` ````
现在,您的自定义仓储也可以使用`IBookStoreDbContext`接口: 现在,你的自定义仓储也可以使用`IBookStoreDbContext`接口:
````csharp ````csharp
public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>, IBookRepository public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>, IBookRepository

4
docs/zh-Hans/Exception-Handling.md

@ -295,8 +295,8 @@ services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
框架会自动抛出以下异常类型: 框架会自动抛出以下异常类型:
- 当用户没有权限执行操作时,会抛出 `AbpAuthorizationException` 异常. 有关更多信息,请参阅授权文档(TODO:link). - 当用户没有权限执行操作时,会抛出 `AbpAuthorizationException` 异常. 有关更多信息,请参阅授权文档[authorization](Authorization.md).
- 如果当前请求的输入无效,则抛出`AbpValidationException 异常`. 有关更多信息,请参阅授权文档(TODO:link). - 如果当前请求的输入无效,则抛出`AbpValidationException 异常`. 有关更多信息,请参阅[验证文档](Validation.md).
- 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常大多数由 [repositories](Repositories.md) 抛出. - 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常大多数由 [repositories](Repositories.md) 抛出.
你同样可以在代码中抛出这些类型的异常(虽然很少需要这样做) 你同样可以在代码中抛出这些类型的异常(虽然很少需要这样做)

4
docs/zh-Hans/Getting-Started-AspNetCore-Application.md

@ -156,7 +156,7 @@ services.AddApplication<AppModule>(options =>
}); });
```` ````
4. 更新 `Program.cs`代码, 不再使用`WebHost.CreateDefaultBuilder()`方法(因为它使用默认的DI容器): 4. 更新 `Program.cs`代码, 不再使用`WebHost.CreateDefaultBuilder()`方法(因为它使用默认的DI容器):
````csharp ````csharp
public class Program public class Program
@ -186,4 +186,4 @@ public class Program
### 源码 ### 源码
从[此处](https://github.com/abpframework/abp/tree/dev/samples/BasicAspNetCoreApplication)获取本教程中创建的示例项目的源代码. 从[此处](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)获取本教程中创建的示例项目的源代码.

2
docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md

@ -4,7 +4,7 @@
本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面. 本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面.
如果你之前未安装,请使用命令行安装ABP CLI: 如果你之前未安装,请使用命令行安装ABP CLI:
````bash ````bash
dotnet tool install -g Volo.Abp.Cli dotnet tool install -g Volo.Abp.Cli

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

@ -121,4 +121,4 @@ namespace AbpConsoleDemo
### 源码 ### 源码
从[这里](https://github.com/abpframework/abp/tree/dev/samples/BasicConsoleApplication)获取本教程中创建的示例项目的源代码. 从[这里](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication)获取本教程中创建的示例项目的源代码.

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

@ -1,3 +1,198 @@
# 如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证 # 如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证
TODO... 本文介绍了如何将AzureAD集成到ABP应用程序中,用 **Azure Active Directory** 凭据使用 OAuth 2.0 登录.
添加Azure Active Directory到ABP框架非常简单,只需要正确的完成几个配置.
为了覆盖更多范围,我们演示两种不同的集成AzureAD的**方法**.
1. **AddAzureAD**: 该方法使用微软[AzureAD UI nuget 包](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/),在网络上搜索如何将AzureAD集成到应用程序时,这个包是最流行的.
2. **AddOpenIdConnect**: 该方法使用默认的[OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/). 它不仅可用于AzureAD,还可用于所有OpenId连接.
> 这些方法之间的功能**没有区别**,AddAzureAD是具有预定义Cookie设置的OpenIdConnection([源](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122))的抽象方法.
>
> 但是默认配置的登录方案在与ABP应用程序集成方面存在关键差异,下面将对此进行说明.
## 1. AddAzureAD
这个方法使用 [Microsoft AzureAD UI nuget 包](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/),它是最常用的集成AzureAD方法.
如果选择这种方法,需要将 `Microsoft.AspNetCore.Authentication.AzureAD.UI` 软件包安装到 **.Web** 项目中. 由于AddAzureAD扩展使用[配置绑定](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration),你需要更改 **.Web** 项目中的appsettings.json文件.
#### **更改 `appsettings.json`**
你添加向 `appsettings.json` 添加新的配置节,在配置 `OpenIdConnectOptions` 时绑定配置:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
> 这里重要的配置是CallbackPath. 值必须与你的 Azure AD-> app registrations-> Authentication -> RedirectUri 之一相同.
然后你需要配置 `OpenIdConnectOptions` 完成集成.
#### 配置 OpenIdConnectOptions
在你的 **.Web** 项目找到 **ApplicationWebModule** 使用以下代码修改 `ConfigureAuthentication` 方法:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "Acme.BookStore";
})
.AddAzureAD(options => configuration.Bind("AzureAd", options));
context.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Scope.Add("email");
});
}
````
> **不要忘记:**
>
> * 在 `AddAuthentication()` 之后添加 `.AddAzureAD(options => configuration.Bind("AzureAd", options))` . 它绑定了你的 AzureAD 配置并且容易忘记.
> * 添加 `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. 它会禁用默认的 Microsoft claim type 映射.
> * 添加 `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. 映射 [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) 很重要,因为默认SignIn Manager和行为使用这个claim type用于外部登录信息.
> * 添加 `options.SignInScheme = IdentityConstants.ExternalScheme` 因为 [默认登录方法为 `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
> * 如果你使用的是 **v2.0** 端点,应添加 `options.Scope.Add("email")` 因为 v2.0 端点不会将 `email` 做为默认值返回. [账户模块](../Modules/Account.md) 使用 `email` claim 来 [注册外部账户](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
你已经完成了集成.
## 2. 替代方法: AddOpenIdConnect
如果你不想在应用程序安装一个额外的NuGet包,你可以使用默认的[OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/),它适用于所有的OpenId连接,包括AzureAD外部认证.
你不必使用 `appsettings.json` 配置, 但将AzureAD信息放在 `appsettings.json` 是一个很好的做法.
为了从 `appsettings.json` 获取AzureAD信息在 `OpenIdConnectOptions` 配置使用,只需要在你的 **.Web** 项目中的 `appsettings.json` 添加一个新的配置节:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
然后在你的 **.Web** 项目的 **ApplicationWebModule** 用以下代码修改 `ConfigureAuthentication` 方法:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "BookStore";
})
.AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
});
}
````
集成结束. 请记住你可以连接任何其他外部认证供应商.
## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码.
# FAQ
* Help! `GetExternalLoginInfoAsync` 返回 `null`!
* 有两方面的原因;
1. 你在尝试验证错误的方案. 检查是否设置 **SignInScheme** 为 `IdentityConstants.ExternalScheme`:
````csharp
options.SignInScheme = IdentityConstants.ExternalScheme;
````
2. 你的 `ClaimTypes.NameIdentifier` 为 `null`. 检查是否添加 claim 映射:
````csharp
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
````
* Help! 我一直得到 ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** 错误!
* 如果你在appsettings设置 **CallbackPath** 为:
````csharp
"AzureAd": {
...
"CallbackPath": "/signin-azuread-oidc"
}
````
你在azure门户的应用程序**重定向URI**必须具有之类 `https://localhost:44320/signin-azuread-oidc` 的<u>域</u>, 而不仅是 `/signin-azuread-oidc`.
* Help! 我一直得到 ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** 错误!
* 当你使用 Azure Authority **v2.0 端点** 而不请求 `email` 域, 会发生这些情况. [Abp 创建用户检查了唯一的邮箱](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). 只需添加
````csharp
options.Scope.Add("email");
````
到你的 openid 配置.
* 如何**调试/监视**在映射之前获得的声明?
* 你可以在 openid 配置下加一个简单的事件在映射之前进行调试,例如:
````csharp
options.Events.OnTokenValidated = (async context =>
{
var claimsFromOidcProvider = context.Principal.Claims.ToList();
await Task.CompletedTask;
});
````
## 另请参阅
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md).
* [如何为ABP应用程序定制SignIn Manager](Customize-SignIn-Manager.md).

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

@ -1,3 +1,101 @@
# 如何为ABP应用程序定制SignIn Manager # 如何为ABP应用程序定制SignIn Manager
TODO... 在使用[应用程序启动模板](../Startup-Templates/Application.md)创建新项目后,你可能想要扩展或更改SignIn Manager的默认行为,以满足你需要的身份验证和注册流程. ABP[账户模块](../Modules/Account.md)使用[身份管理模块](../Modules/Identity.md)做为SignIn Manager,而[身份管理模块](../Modules/Identity.md)使用默认的[Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs)([参阅此处]((https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17))).
编写自定义SignIn Manager,你需要扩展[Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs)类并注入到DI容器.
本文介绍了如何为你自己的应用程序自定义SignIn Manager.
## 创建 CustomSignInManager
创建一个类并继承自Microsoft Identity 包的 [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs).
````csharp
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
public CustomSignInManager(
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
}
````
> 重点是使用**Volo.Abp.Identity.IdentityUser**做为泛型参数,而不是应用程序的AppUser.
然后你可以覆盖SignIn Manager的任何方法并且为你的身份验证和注册流程添加需要的方法和属性.
## 重写 GetExternalLoginInfoAsync 方法
在这个用例中我们重写第三方身份验证时使用的 `GetExternalLoginInfoAsync` 方法实现.
一个好的开始是从复制[源码](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674)而不是从零开始. 在这个用例中我们对源码进行较少的修改,为了帮助理解概念它显式显示了方法和属性的命名空间.
````csharp
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey(XsrfKey))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens()
};
}
````
要使你自定义的SignIn Manager类生效,你需要将其注册[依赖注入系统](../Dependency-Injection.md)中.
## 注册到依赖注入
应该使用 [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs) 的 [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) 类的 **AddSignInManager** 扩展方法注册 `CustomSignInManager`.
在你的 `.Web` 项目找到 `YourProjectNameWebModule` 的 `PreConfigureServices` 方法添加以下代码替换老的 `SignInManager`:
````csharp
PreConfigure<IdentityBuilder>(identityBuilder =>
{
identityBuilder.AddSignInManager<CustomSignInManager>();
});
````
## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码.
## 另请参阅
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md).
* [身份管理模块](../Modules/Identity.md).

2
docs/zh-Hans/Index.md

@ -12,7 +12,7 @@ ABP是一个**开源应用程序框架**,专注于基于ASP.NET Core的Web应用
* [ASP.NET Core MVC 模板](Getting-Started-AspNetCore-MVC-Template.md) * [ASP.NET Core MVC 模板](Getting-Started-AspNetCore-MVC-Template.md)
如果您想从头开始(使用空项目),请手动安装ABP框架并使用以下教程: 如果你想从头开始(使用空项目),请手动安装ABP框架并使用以下教程:
* [控制台应用程序](Getting-Started-Console-Application.md) * [控制台应用程序](Getting-Started-Console-Application.md)
* [ASP.NET Core Web 应用程序](Getting-Started-AspNetCore-Application.md) * [ASP.NET Core Web 应用程序](Getting-Started-AspNetCore-Application.md)

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

Loading…
Cancel
Save