Browse Source

Merge remote-tracking branch 'abpframework/dev' into docs

pull/3693/head
liangshiwei 6 years ago
parent
commit
24ece27304
  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. 4
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  7. 3
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  8. 4
      docs/en/Exception-Handling.md
  9. 128
      docs/en/Getting-Started-Angular-Template.md
  10. 106
      docs/en/Getting-Started-AspNetCore-MVC-Template.md
  11. 6
      docs/en/Getting-Started-With-Startup-Templates.md
  12. 407
      docs/en/Getting-Started.md
  13. 4
      docs/en/How-To/Customize-SignIn-Manager.md
  14. 2
      docs/en/Tutorials/Angular/Part-I.md
  15. 2
      docs/en/Tutorials/Angular/Part-II.md
  16. 2
      docs/en/Tutorials/Angular/Part-III.md
  17. 2
      docs/en/Tutorials/AspNetCore-Mvc/Part-I.md
  18. 2
      docs/en/Tutorials/AspNetCore-Mvc/Part-II.md
  19. 2
      docs/en/Tutorials/AspNetCore-Mvc/Part-III.md
  20. 10
      docs/en/Tutorials/Part-1.md
  21. 4
      docs/en/Tutorials/Part-2.md
  22. 10
      docs/en/UI/Angular/Dom-Insertion-Service.md
  23. 278
      docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md
  24. 261
      docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md
  25. 14
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  26. 2
      docs/en/UI/AspNetCore/Tag-Helpers/List-Groups.md
  27. 4
      docs/en/UI/AspNetCore/Tag-Helpers/Progress-Bars.md
  28. 300
      docs/en/UI/Common/Utils/Linked-List.md
  29. 33
      docs/en/docs-nav.json
  30. BIN
      docs/en/images/bookstore-home.png
  31. BIN
      docs/en/images/bookstore-login.png
  32. BIN
      docs/en/images/db-migrator-output.png
  33. BIN
      docs/en/images/package-manager-console-update-database.png
  34. BIN
      docs/en/images/rn-environment-local-ip.png
  35. BIN
      docs/en/images/rn-expo-interface.png
  36. BIN
      docs/en/images/rn-host-local-ip.png
  37. BIN
      docs/en/images/rn-login-iphone.png
  38. BIN
      docs/en/images/rn-tiered-local-ip.png
  39. BIN
      docs/en/images/solution-files-mvc.png
  40. BIN
      docs/en/images/solution-files-non-mvc.png
  41. BIN
      docs/en/images/swagger-ui.png
  42. BIN
      docs/en/images/vs-app-solution-structure-mongodb.png
  43. BIN
      docs/en/images/vs-app-solution-structure-tiered.png
  44. BIN
      docs/en/images/vs-app-solution-structure.png
  45. BIN
      docs/en/images/vs-spa-app-backend-structure-mongodb.png
  46. BIN
      docs/en/images/vs-spa-app-backend-structure.png
  47. 2
      docs/pt-BR/Tutorials/Angular/Part-I.md
  48. 2
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  49. 3
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  50. 4
      docs/zh-Hans/Entities.md
  51. 4
      docs/zh-Hans/Exception-Handling.md
  52. 4
      docs/zh-Hans/How-To/Customize-SignIn-Manager.md
  53. 3
      docs/zh-Hans/Modules/Index.md
  54. 155
      docs/zh-Hans/Startup-Templates/Application.md
  55. 3
      docs/zh-Hans/UI/Angular/AddingSettingTab.md
  56. 85
      docs/zh-Hans/UI/Angular/Component-Replacement.md
  57. 294
      docs/zh-Hans/UI/Angular/Config-State.md
  58. 87
      docs/zh-Hans/UI/Angular/Container-Strategy.md
  59. 77
      docs/zh-Hans/UI/Angular/Content-Projection-Service.md
  60. 55
      docs/zh-Hans/UI/Angular/Content-Security-Strategy.md
  61. 85
      docs/zh-Hans/UI/Angular/Content-Strategy.md
  62. 100
      docs/zh-Hans/UI/Angular/Context-Strategy.md
  63. 46
      docs/zh-Hans/UI/Angular/Custom-Setting-Page.md
  64. 130
      docs/zh-Hans/UI/Angular/Dom-Insertion-Service.md
  65. 80
      docs/zh-Hans/UI/Angular/Dom-Strategy.md
  66. 3
      docs/zh-Hans/UI/Angular/HTTP-Requests.md
  67. 3
      docs/zh-Hans/UI/Angular/Lazy-Load-Service.md
  68. 80
      docs/zh-Hans/UI/Angular/Permission-Management.md
  69. 0
      docs/zh-Hans/UI/Angular/Projection-Strategy.md
  70. 3
      docs/zh-Hans/UI/Angular/Service-Proxies.md
  71. 98
      docs/zh-Hans/UI/Angular/Track-By-Service.md
  72. 8
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md
  73. 2
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/List-Groups.md
  74. 2
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Progress-Bars.md
  75. 273
      docs/zh-Hans/UI/Common/Utils/Linked-List.md
  76. 41
      docs/zh-Hans/docs-nav.json
  77. BIN
      docs/zh-Hans/images/bookstore-home.png
  78. BIN
      docs/zh-Hans/images/bookstore-login.png
  79. BIN
      docs/zh-Hans/images/db-migrator-output.png
  80. BIN
      docs/zh-Hans/images/package-manager-console-update-database.png
  81. BIN
      docs/zh-Hans/images/rn-environment-local-ip.png
  82. BIN
      docs/zh-Hans/images/rn-expo-interface.png
  83. BIN
      docs/zh-Hans/images/rn-host-local-ip.png
  84. BIN
      docs/zh-Hans/images/rn-login-iphone.png
  85. BIN
      docs/zh-Hans/images/rn-tiered-local-ip.png
  86. BIN
      docs/zh-Hans/images/solution-files-mvc.png
  87. BIN
      docs/zh-Hans/images/solution-files-non-mvc.png
  88. BIN
      docs/zh-Hans/images/swagger-ui.png
  89. BIN
      docs/zh-Hans/images/vs-app-solution-structure-mongodb.png
  90. BIN
      docs/zh-Hans/images/vs-app-solution-structure-tiered.png
  91. BIN
      docs/zh-Hans/images/vs-app-solution-structure.png
  92. BIN
      docs/zh-Hans/images/vs-spa-app-backend-structure-mongodb.png
  93. BIN
      docs/zh-Hans/images/vs-spa-app-backend-structure.png
  94. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/ru.json
  95. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Core/CoreScriptContributor.cs
  96. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs
  97. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Utils/UtilsScriptContributor.cs
  98. 5
      framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreTestBaseModule.cs
  99. 20
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs
  100. 16
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

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:Create": "Create",
"Menu:Organizations": "Organizations",
"Menu:Accounting": "Accounting",
"Menu:Packages": "Packages",
"NpmPackageDeletionWarningMessage": "This NPM 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",
"Create": "Create",
"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",
"UniqueComputerId": "Unique computer id",
"LastSeenDate": "Last seen date",
@ -91,6 +93,14 @@
"OrganizationNamePlaceholder": "Organization name...",
"UsernameOrEmail": "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>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>2.6.0</Version>
<Version>2.7.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl>

4
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 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.
@ -174,4 +174,4 @@ public class MyDistributedIdentityUserCreatedEventHandler :
## See Also
* [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)

3
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
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))]
public class MyIdentityUserAppService : IdentityUserAppService
{
//...
@ -262,4 +263,4 @@ See [Overriding the User Interface](Customizing-Application-Modules-Overriding-U
## 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.

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:
- `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See authorization document (TODO: link) for more.
- `AbpValidationException` is thrown if the input of the current request is not valid. See validation 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](Validation.md) for more.
- `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).

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.
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)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

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.
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)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

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:
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md)
* [Getting Started with the Angular UI](Getting-Started-Angular-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?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 items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey"))
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey("XsrfKey"))
if (!items.ContainsKey(XsrfKey))
{
return null;
}

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 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 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 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 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 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 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:
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:
@ -34,14 +34,14 @@ The ASP.NET Core {{UI_Value}} tutorial series consists of 3 parts:
### 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
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
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)
@ -982,11 +982,13 @@ import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {

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

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

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

@ -125,6 +125,16 @@ 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
```
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?
- [ContentProjectionService](./Content-Projection-Service.md)

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:
* [Alerts](Alerts.md)
* [Buttons](Buttons.md)
* [Cards](Cards.md)
* [Alerts](Alerts.md)
* [Tabs](Tabs.md)
* [Grids](Grids.md)
* [Modals](Modals.md)
* [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md)
* [Grids](Grids.md)
* [List Groups](List-Groups.md)
* [Modals](Modals.md)
* [Paginator](Paginator.md)
* [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md)
* [Tabs](Tabs.md)
* [Tooltips](Tooltips.md)
* ...
@ -31,8 +31,8 @@ Here, the list of components those are wrapped by the ABP Framework:
## 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
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

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

@ -26,7 +26,7 @@ Basic usage:
## 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
@ -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:
* `false` (default value)
* `true`
* `true`

300
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
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
import { LinkedList } from '@abp/ng.core';
import { LinkedList } from '@abp/utils';
const list = new LinkedList();
```
In MVC:
```js
var list = new abp.utils.common.LinkedList();
```
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)
```js
addHead(value: T): ListNode\<T\>
addHead(value: T): ListNode<T>
```
Adds a node with given value as the first node in list:
@ -57,7 +66,7 @@ list.addHead('c');
#### addManyHead(values)
```js
addManyHead(values: T\[\]): ListNode\<T\>\[\]
addManyHead(values: T[]): ListNode<T>[]
```
Adds multiple nodes with given values as the first nodes in list:
@ -77,7 +86,7 @@ list.addManyHead(['x', 'y', 'z']);
#### addTail(value)
```js
addTail(value: T): ListNode\<T\>
addTail(value: T): ListNode<T>
```
Adds a node with given value as the last node in list:
@ -101,7 +110,7 @@ list.addTail('c');
#### addManyTail(values)
```js
addManyTail(values: T\[\]): ListNode\<T\>\[\]
addManyTail(values: T[]): ListNode<T>[]
```
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
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:
@ -165,10 +174,10 @@ list.addAfter(
#### addManyAfter(values, previousValue, compareFn)
#### addManyAfter(values, previousValue [, compareFn])
```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:
@ -207,10 +216,10 @@ list.addManyAfter(
#### addBefore(value, nextValue, compareFn)
#### addBefore(value, nextValue [, compareFn])
```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:
@ -254,10 +263,10 @@ list.addBefore(
#### addManyBefore(values, nextValue, compareFn)
#### addManyBefore(values, nextValue [, compareFn])
```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:
@ -299,7 +308,7 @@ list.addManyBefore(
#### addByIndex(value, position)
```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:
@ -337,7 +346,7 @@ list.addByIndex('x', -1);
#### addManyByIndex(values, position)
```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:
@ -371,7 +380,7 @@ list.addManyByIndex(['x', 'y'], -1);
#### add(value).head()
```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:
@ -399,7 +408,7 @@ list.add('c').head();
#### add(value).tail()
```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:
@ -424,10 +433,10 @@ list.add('c').tail();
#### add(value).after(previousValue, compareFn)
#### add(value).after(previousValue [, compareFn])
```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:
@ -471,10 +480,10 @@ list
#### add(value).before(nextValue, compareFn)
#### add(value).before(nextValue [, compareFn])
```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:
@ -521,7 +530,7 @@ list
#### add(value).byIndex(position)
```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:
@ -563,7 +572,7 @@ list.add('x').byIndex(-1);
#### addMany(values).head()
```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:
@ -587,7 +596,7 @@ list.addMany(['x', 'y', 'z']).head();
#### addMany(values).tail()
```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:
@ -608,10 +617,10 @@ list.addMany(['x', 'y', 'z']).tail();
#### addMany(values).after(previousValue, compareFn)
#### addMany(values).after(previousValue [, compareFn])
```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:
@ -650,10 +659,10 @@ list
#### addMany(values).before(nextValue, compareFn)
#### addMany(values).before(nextValue [, compareFn])
```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:
@ -695,7 +704,7 @@ list
#### addMany(values).byIndex(position)
```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:
@ -739,7 +748,7 @@ There are a few methods to remove nodes from a linked list and all of them are s
#### dropHead()
```js
dropHead(): ListNode\<T\> | undefined
dropHead(): ListNode<T> | undefined
```
Removes the first node from the list:
@ -759,7 +768,7 @@ list.dropHead();
#### dropManyHead(count)
```js
dropManyHead(count: number): ListNode\<T\>\[\]
dropManyHead(count: number): ListNode<T>[]
```
Removes the first nodes from the list based on given count:
@ -779,7 +788,7 @@ list.dropManyHead(2);
#### dropTail()
```js
dropTail(): ListNode\<T\> | undefined
dropTail(): ListNode<T> | undefined
```
Removes the last node from the list:
@ -799,7 +808,7 @@ list.dropTail();
#### dropManyTail(count)
```js
dropManyTail(count: number): ListNode\<T\>\[\]
dropManyTail(count: number): ListNode<T>[]
```
Removes the last nodes from the list based on given count:
@ -819,7 +828,7 @@ list.dropManyTail(2);
#### dropByIndex(position)
```js
dropByIndex(position: number): ListNode\<T\> | undefined
dropByIndex(position: number): ListNode<T> | undefined
```
Removes the node with the specified position from the list:
@ -853,7 +862,7 @@ list.dropByIndex(-2);
#### dropManyByIndex(count, position)
```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:
@ -884,10 +893,10 @@ list.dropManyByIndex(2, -2);
#### dropByValue(value, compareFn)
#### dropByValue(value [, compareFn])
```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:
@ -922,10 +931,10 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### dropByValueAll(value, compareFn)
#### dropByValueAll(value [, compareFn])
```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:
@ -963,7 +972,7 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### drop().head()
```js
drop().head(): ListNode\<T\> | undefined
drop().head(): ListNode<T> | undefined
```
Removes the first node in list:
@ -987,7 +996,7 @@ list.drop().head();
#### drop().tail()
```js
drop().tail(): ListNode\<T\> | undefined
drop().tail(): ListNode<T> | undefined
```
Removes the last node in list:
@ -1011,7 +1020,7 @@ list.drop().tail();
#### drop().byIndex(position)
```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:
@ -1046,10 +1055,10 @@ list.drop().byIndex(-2);
#### drop().byValue(value, compareFn)
#### drop().byValue(value [, compareFn])
```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:
@ -1088,10 +1097,10 @@ list
#### drop().byValueAll(value, compareFn)
#### drop().byValueAll(value [, compareFn])
```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:
@ -1133,7 +1142,7 @@ list
#### dropMany(count).head()
```js
dropMany(count: number).head(): ListNode\<T\>\[\]
dropMany(count: number).head(): ListNode<T>[]
```
Removes the first nodes from the list based on given count:
@ -1157,7 +1166,7 @@ list.dropMany(2).head();
#### dropMany(count).tail()
```js
dropMany(count: number).tail(): ListNode\<T\>\[\]
dropMany(count: number).tail(): ListNode<T>[]
```
Removes the last nodes from the list based on given count:
@ -1181,7 +1190,7 @@ list.dropMany(2).tail();
#### dropMany(count).byIndex(position)
```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:
@ -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)
```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:
@ -1235,7 +1274,7 @@ list.addTailMany(['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"
@ -1249,7 +1288,7 @@ found.next.value === "b"
#### findIndex(predicate)
```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:
@ -1259,10 +1298,10 @@ list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
const i0 = list.findIndex(node => node.next && node.next.value === 'b');
const i1 = list.findIndex(node => node.value === 'b');
const i2 = list.findIndex(node => node.previous && node.previous.value === 'b');
const i3 = list.findIndex(node => node.value === 'x');
var i0 = list.findIndex(node => node.next && node.next.value === 'b');
var i1 = list.findIndex(node => node.value === 'b');
var i2 = list.findIndex(node => node.previous && node.previous.value === 'b');
var i3 = list.findIndex(node => node.value === 'x');
/*
i0 === 0
@ -1277,7 +1316,7 @@ i3 === -1
#### get(position)
```js
get(position: number): ListNode\<T\> | undefined
get(position: number): ListNode<T> | undefined
```
Finds and returns the node with specific position in the list:
@ -1287,7 +1326,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
const found = list.get(1);
var found = list.get(1);
/*
found.value === "b"
@ -1298,10 +1337,10 @@ found.next.value === "c"
#### indexOf(value, compareFn)
#### indexOf(value [, compareFn])
```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:
@ -1311,10 +1350,10 @@ list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
const i0 = list.indexOf('a');
const i1 = list.indexOf('b');
const i2 = list.indexOf('c');
const i3 = list.indexOf('x');
var i0 = list.indexOf('a');
var i1 = list.indexOf('b');
var i2 = list.indexOf('c');
var i3 = list.indexOf('x');
/*
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}
const i0 = indexOf(1, (value, searchedValue) => value.x === searchedValue);
const i1 = indexOf(2, (value, searchedValue) => value.x === searchedValue);
const i2 = indexOf(3, (value, searchedValue) => value.x === searchedValue);
const i3 = indexOf(0, (value, searchedValue) => value.x === searchedValue);
const i4 = indexOf(4, (value, searchedValue) => value.x === searchedValue);
var i0 = indexOf(1, (value, searchedValue) => value.x === searchedValue);
var i1 = indexOf(2, (value, searchedValue) => value.x === searchedValue);
var i2 = indexOf(3, (value, searchedValue) => value.x === searchedValue);
var i3 = indexOf(0, (value, searchedValue) => value.x === searchedValue);
var i4 = indexOf(4, (value, searchedValue) => value.x === searchedValue);
/*
i0 === 0
@ -1360,13 +1399,13 @@ There are a few ways to iterate over or display a linked list.
#### forEach(callback)
#### forEach(iteratorFn)
```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
list.addTailMany(['a', 'b', 'c']);
@ -1390,7 +1429,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
for(const node of list) {
for(const node of list) { /* ES6 for...of statement */
console.log(node.value);
}
@ -1404,7 +1443,7 @@ for(const node of list) {
#### toArray()
```js
toArray(): T\[\]
toArray(): T[]
```
Converts a linked list to an array of values:
@ -1414,7 +1453,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
const arr = list.toArray();
var arr = list.toArray();
/*
arr === ['a', 'b', 'c']
@ -1426,7 +1465,7 @@ arr === ['a', 'b', 'c']
#### toNodeArray()
```js
toNodeArray(): T\[\]
toNodeArray(): ListNode<T>[]
```
Converts a linked list to an array of nodes:
@ -1436,7 +1475,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
const arr = list.toNodeArray();
var arr = list.toNodeArray();
/*
arr[0].value === 'a'
@ -1447,10 +1486,10 @@ arr[2].value === 'a'
#### toString()
#### toString([mapperFn])
```js
toString(): string
toString(mapperFn: ListMapperFn<T> = JSON.stringify): string
```
Converts a linked list to a string representation of nodes and their relations:
@ -1460,7 +1499,7 @@ list.addTailMany(['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"}'
@ -1476,9 +1515,102 @@ list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }]).tail();
// {"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'
*/
```
```
## 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.

33
docs/en/docs-nav.json

@ -5,17 +5,7 @@
"items": [
{
"text": "From Startup Templates",
"path": "Getting-Started-With-Startup-Templates.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"
}
]
"path": "Getting-Started.md"
},
{
"text": "From Empty Projects",
@ -302,7 +292,17 @@
},
{
"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",
@ -371,8 +371,13 @@
"text": "Common",
"items": [
{
"text": "Linked List (Doubly)",
"path": "UI/Common/Utils/Linked-List.md"
"text": "Utilities",
"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 { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {

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

@ -50,7 +50,7 @@ ObjectExtensionManager.Instance
* 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名).
* 你还需要提供一个使用[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迁移类并更新数据库.

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

@ -60,6 +60,7 @@ context.Services.Replace(
````csharp
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))]
public class MyIdentityUserAppService : IdentityUserAppService
{
//...
@ -262,4 +263,4 @@ ObjectExtensionManager.Instance
## 如何找到服务?
[模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务.
[模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务.

4
docs/zh-Hans/Entities.md

@ -379,10 +379,10 @@ public static class IdentityUserExtensions
* 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**.
* 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易.
### 额外属性背后的实体
### 额外属性背后的实体
`IHasExtraProperties` 不限于与实体一起使用. 你可以为任何类型的类实现这个接口,使用 `GetProperty`,`SetProperty` 和其他相关方法.
## 另请参阅
* [实体设计最佳实践指南](Best-Practices/Entities.md)
* [实体设计最佳实践指南](Best-Practices/Entities.md)

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

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

4
docs/zh-Hans/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 items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey"))
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey("XsrfKey"))
if (!items.ContainsKey(XsrfKey))
{
return null;
}

3
docs/zh-Hans/Modules/Index.md

@ -16,6 +16,7 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages**
* **Background Jobs**: 用于在使用默认后台作业管理器时保存后台作业.
* **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块.
* [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块.
* **Feature Management**: 用于保存和管理功能.
* **Identity**: 基于Microsoft Identity管理角色,用户和他们的权限.
* **Identity Server**: 集成了IdentityServer4.
* **Permission Management**: 用于保存权限.
@ -27,4 +28,4 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages**
## 商业应用模块
[ABP商业](https://commercial.abp.io/)许可证在ABP框架上提供了额外的预构建应用程序模块. 参见ABP商业版提供的[模块列表](https://commercial.abp.io/module).
[ABP商业](https://commercial.abp.io/)许可证在ABP框架上提供了额外的预构建应用程序模块. 参见ABP商业版提供的[模块列表](https://commercial.abp.io/module).

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

@ -139,7 +139,7 @@ ABP是一个模块化的框架,理想的设计是让每个模块都有自己的
初始化种子数据很重要,ABP具有模块化的种子数据基础设施. 种子数据的更多信息,请参阅[文档](../Data-Seeding.md).
虽然创建数据库和应用迁移似乎只对关系数据库有用,但即使选择NoSQL数据库提供程序(如MongoDB),也会生成此项目. 这时,它会为应用程序提供必要的初始数据.
虽然创建数据库和应用迁移似乎只对关系数据库有用,但即使选择NoSQL数据库提供程序(如MongoDB),也会生成此项目. 这时,它会为应用程序提供必要的初始数据.
* 它依赖 `.EntityFrameworkCore.DbMigrations` 项目 (针对EF Core),因为它需要访问迁移文件.
* 它依赖 `.Application.Contracts` 项目,因为它需要访问权限定义在初始化种子数据时为管理员用户赋予所有权限.
@ -270,6 +270,159 @@ ABP使用开源的[IdentityServer4](https://identityserver.io/)框架做应用
`angular/src/environments` 文件夹下的文件含有应用程序的基础配置.
#### AppModule(应用程序模块)
`AppModule` 是应用程序的根模块. 一些ABP模块和一些基本模块导入到 `AppModule` 中.
ABP 配置模块也已经导入到 `AppModule` 中, 以满足可延迟加载 ABP 模块的初始需求.
#### AppRoutingModule(应用程序路由模块)
`AppRoutingModule` 中有可延迟加载的 ABP 模块作为路由.
> 不应更改ABP模块的路径.
你应该在 `data` 对象中添加 `routes` 属性, 以便在菜单中添加一个链接来重定向到自定义页面.
```js
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGuard, PermissionGuard],
data: {
routes: {
name: 'ProjectName::Menu:Dashboard',
order: 2,
iconClass: 'fa fa-dashboard',
requiredPolicy: 'ProjectName.Dashboard.Host'
} as ABP.Route
}
}
```
在上面的例子中;
* 如果用户没有登录, AuthGuard 会阻塞访问并重定向到登录页面.
* PermissionGuard 使用 `rotues` 对象的 `requiredPolicy` 属性检查用户的权限. 如果用户未被授权访问该页, 则显示403页.
* `routes``name` 属性是菜单链接标签. 可以定义本地化 key.
* `routes` 对象的 `iconClass` 属性是菜单链接图标类.
* `routes` 对象的 `requiredPolicy` 属性是访问页面所需的策略 key.
在上述 `routes` 定义之后, 如果用户被授权, 仪表盘链接将出现在菜单上.
#### Shared Module(共享模块)
所有模块可能需要的模块已导入到 `SharedModule`. 你应该将 `SharedModule` 导入所有模块.
参见 [Sharing Modules(共享模块)](https://angular.io/guide/sharing-ngmodules) 文档.
#### Environments(环境)
`src/environments` 文件夹下的文件包含应用程序的基本配置.
#### Home Module
Home模块是一个可延迟加载的模块, 它加载应用程序的根地址.
#### Styles(样式)
`angular.json` 中向 `styles` 数组添加所需的样式文件. `AppComponent` 在主包加载后通过 `LazyLoadService` 加载一些样式文件, 以缩短第一次绘制的时间.
#### Testing(测试)
你应该在与要测试的文件相同的文件夹中创建测试.
参见[测试文档](https://angular.io/guide/testing/).
#### Depended Packages(依赖包)
* [NG Bootstrap](https://ng-bootstrap.github.io/) 被用作UI组件库.
* [NGXS](https://www.ngxs.io/) 被用作状态管理库.
* [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) 用于支持OAuth 2和OpenId Connect (OIDC).
* [Chart.js](https://www.chartjs.org/) 用于创建小部件.
* [ngx-validate](https://github.com/ng-turkey/ngx-validate) 用于对交互表单进行动态验证.
### React Native
解决方案将[React Native](https://reactnative.dev/)应用程序作为默认值包含在 `react-native` 文件夹中.
服务器端类似于上面描述的解决方案. `*.HttpApi.Host` 的项目提供 API, 所以 React 本机应用程序使用它.
React 本机应用程序是用 [Expo](https://expo.io/)生成的. Expo 是一套基于 React Native 构建的工具, 帮助你快速启动一个应用程序, 尽管它有很多功能.
React Native 应用文件夹结构, 如下图所示:
![react-native-folder-structure](../images/react-native-folder-structure.png)
* `App.js` 是应用程序的引导组件.
* `Environment.js` f文件有应用程序的基本配置. 在这个文件中定义了 `prod` and `dev` 配置.
* [Contexts](https://reactjs.org/docs/context.html) 是在 `src/contexts` 文件夹中创建的.
* [Higher order components](https://reactjs.org/docs/higher-order-components.html) 是在 `src/hocs` 文件夹中创建的.
* [Custom hooks](https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook) 是在 `src/hooks` 中创建的.
* [Axios interceptors](https://github.com/axios/axios#interceptors) 是在 `src/interceptors` 文件夹中创建.
* 工具函数从 `src/utils` 文件夹导出.
#### Components(组件)
可以在所有屏幕上使用的组件是在 `src/components` 文件夹中创建的. 所有组件都是作为一个能够使用 [hooks](https://reactjs.org/docs/hooks-intro.html) 的函数创建的.
#### Screens(屏幕)
![react-native-navigation-structure](../images/react-native-navigation-structure.png)
Screens 是通过在 `src/screens` 文件夹中创建将名称分开的文件夹来创建的. 某些 screens 的某些部分可以拆分为组件.
每个 screen 都在 `src/navigators` 文件夹中的导航器中使用.
#### Navigation(导航)
[React Navigation](https://reactnavigation.org/) 被用作导航库. 导航器是在 `src/navigators` 中创建的. 一个 [drawer](https://reactnavigation.org/docs/drawer-based-navigation/) 导航器和几个 [stack](https://reactnavigation.org/docs/hello-react-navigation/#installing-the-stack-navigator-library) 导航器在此文件夹中创建. 查看 [上图](#screens) 中的导航结构.
#### State Management(状态管理)
[Redux](https://redux.js.org/) 被用作状态管理库. [Redux Toolkit](https://redux-toolkit.js.org/) 库被用作高效Redux开发的工具集.
`src/store` 文件夹中创建 Actions, reducers, sagas, selectors. 存储文件夹如下:
![react-native-store-folder](../images/react-native-store-folder.png)
* [**Store**](https://redux.js.org/basics/store) 在 `src/store/index.js` 文件中定义.
* [**Actions**](https://redux.js.org/basics/actions/) 是将数据从应用程序发送到存储的有效信息负载.
* [**Reducers**](https://redux.js.org/basics/reducers) 指定应用程序的状态如何更改以响应发送到存储的操作.
* [**Redux-Saga**](https://redux-saga.js.org/) 是一个库, 旨在使应用程序的副作用(即异步的事情, 如数据获取和不纯的事情, 如访问浏览器缓存)更容易管理. Sagas 是在 `src/store/sagas` 文件夹中创建的.
* [**Reselect**](https://github.com/reduxjs/reselect) 库用于创建缓存的选择器. 选择器是在 `src/store/selectors` 文件夹中创建的.
#### APIs
[Axios](https://github.com/axios/axios) 用作HTTP客户端库. Axios 实例从 `src/api/API.js` 导出 . 使用相同的配置进行HTTP调用. `src/api` 文件夹中还有为 API 调用创建的 API 文件.
#### Theming(主题)
[Native Base](https://nativebase.io/) 被用作UI组件库. 本地基本组件可以很容易地进行自定义.参见[Native Base customize](https://docs.nativebase.io/Customize.html#Customize) 文档.我们沿着同样的路走.
* Native Base 主题变量在 `src/theme/variables` 文件夹中.
* Native Base 组件样式在 `src/theme/components` 文件夹中.这些文件是用 Native Base's `ejectTheme` 脚本生成的.
* 组件样式用 `src/theme/overrides` 文件夹下的文件覆盖.
#### Testing(单元测试)
将创建单元测试.
参见[测试概述](https://reactjs.org/docs/testing.html)文档.
#### Depended Libraries(依赖库)
* [Native Base](https://nativebase.io/) 用作UI组件库.
* [React Navigation](https://reactnavigation.org/) 用作导航库.
* [Axios](https://github.com/axios/axios) 用作HTTP客户端库.
* [Redux](https://redux.js.org/) 用作状态管理库.
* [Redux Toolkit](https://redux-toolkit.js.org/) 库被用作高效Redux开发的工具集.
* [Redux-Saga](https://redux-saga.js.org/) 用于管理异步进程.
* [Redux Persist](https://github.com/rt2zz/redux-persist) 被用作状态持久化.
* [Reselect](https://github.com/reduxjs/reselect) 用于创建缓存的选择器.
* [i18n-js](https://github.com/fnando/i18n-js) 作为国际化库使用.
* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) 库可以轻松加载字体.
* [Formik](https://github.com/jaredpalmer/formik) 用于构建表单.
* [Yup](https://github.com/jquense/yup) 用于表单验证.
## 下一步是什么?
* 参阅[ASP.NET Core MVC 模板入门](../Getting-Started-AspNetCore-MVC-Template.md)创建此模板的新解决方案并运行它.

3
docs/zh-Hans/UI/Angular/AddingSettingTab.md

@ -1,3 +0,0 @@
## Creating a Settings Tab
TODO...

85
docs/zh-Hans/UI/Angular/Component-Replacement.md

@ -1,3 +1,84 @@
# Component Replacement
## 替换组件
TODO...
你可以将一些ABP的组件替换为你自己的自定义组件.
您可以**替换**但**不能自定义**默认ABP组件的原因是禁用或更改该组件的一部分可能会导致问题. 所以我们把这些组件称为可替换组件.
### 如何替换组件
创建一个你想要使用的新组件,添加到 `AppModule` 中的 `declarations` 和`entryComponents` 中.
然后打开 `app.component.ts` 使用 `AddReplaceableComponent` 将你的组件替换ABP组件. 如下所示:
```js
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 {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
//...
}
}
```
![Example Usage](./images/component-replacement.gif)
### 如何替换布局
每个ABP主题模块有3个布局,分别是`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. 这些布局可以用相同的方式替换.
> 一个布局组件模板应该包含 `<router-outlet></router-outlet>` 元素.
下面的例子解释了如何更换 `ApplicationLayoutComponent`:
运行以下命令在 `angular` 文件夹中生成布局:
```bash
yarn ng generate component shared/my-application-layout --export --entryComponent
# You don't need the --entryComponent option in Angular 9
```
在你的布局模板(`my-layout.component.html`)中添加以下代码:
```html
<router-outlet></router-outlet>
```
打开 `app.component.ts` 添加以下内容:
```js
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 { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added below content
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
}),
);
//...
}
}
```
## 下一步是什么?
- [自定义设置页面](./Custom-Setting-Page.md)

294
docs/zh-Hans/UI/Angular/Config-State.md

@ -0,0 +1,294 @@
## 配置状态
`ConfigStateService` 是一个单例服务,即在应用程序的根级别提供,用于与 `Store` 中的应用程序配置状态进行交互.
## 使用前
为了使用 `ConfigStateService`,你必须将其注入到你的类中.
```js
import { ConfigStateService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private config: ConfigStateService) {}
}
```
你不必在模块或组件/指令级别提供 `ConfigStateService`,因为它已经在**根中**提供.
## 选择器方法
`ConfigStateService` 有许多选择器方法允许你从 `Store` 获取特定或所有的配置.
### 如何从Store获取所有的配置
你可以使用 `ConfigStateService``getAll` 方法从Store获取所有的配置对象. 用法如下:
```js
// this.config is instance of ConfigStateService
const config = this.config.getAll();
```
### 如何从Store获取特定的配置
你可以使用 `ConfigStateService``getOne` 方法从Store获取特定的配置属性. 你需要将属性名做为参数传递给方法:
```js
// this.config is instance of ConfigStateService
const currentUser = this.config.getOne("currentUser");
```
有时你想要获取具体信息,而不是当前用户. 例如你只想获取到 `tenantId`:
```js
const tenantId = this.config.getDeep("currentUser.tenantId");
```
或通过提供键数组作为参数:
```js
const tenantId = this.config.getDeep(["currentUser", "tenantId"]);
```
`getDeep` 可以执行 `getOne` 的所有操作. 但 `getOne` 的执行效率要高一些.
#### 配置状态属性
请参阅 `Config.State` 类型,你可以通过 `getOne``getDeep` 获取所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L7)中找到.
### 如何从Store获取应用程序信息
`getApplicationInfo` 方法从存储为配置状态存储的环境变量中获取应用程序信息. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const appInfo = this.config.getApplicationInfo();
```
该方法不会返回 `undefined``null`,而是会返回一个空对象(`{}`). 换句话说,当你使用上面代码中的 `appInfo` 属性时,永远不会出现错误.
#### 应用程序信息属性
请参阅 `Config.State` 类型,你可以通过 `getApplicationInfo` 获取所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L21)中找到.
### 如何从Store获取
`getApplicationInfo` 方法从存储为配置状态存储的环境变量中获取特定的API URL. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const apiUrl = this.config.getApiUrl();
// environment.apis.default.url
const searchUrl = this.config.getApiUrl("search");
// environment.apis.search.url
```
该方法返回给定键的特定的API `url`. 如果没有Key,则使用 `default`.
### 如何从Store获取所有的设置
你可以使用 `ConfigStateService``getSettings` 获取配置状态所有的设置对象. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const settings = this.config.getSettings();
```
实际上该方法可以通过**传递关键字**来搜索设置.
```js
const localizationSettings = this.config.getSettings("Localization");
/*
{
'Abp.Localization.DefaultLanguage': 'en'
}
*/
```
请注意, **设置搜索区分大小写**.
### 如何从Store获取特定的设置
你可以使用 `ConfigStateService``getSetting` 获取配置状态特定的设置. 你可以这样使用它:
```js
// this.config is instance of ConfigStateService
const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage");
// 'en'
```
### 如何从Store获取特定的权限
你可以使用 `ConfigStateService``getGrantedPolicy` 获取配置状态特定的权限. 你应该将策略key做为参数传递给方法:
```js
// this.config is instance of ConfigStateService
const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity");
// true
```
你还可以使用 **组合策略key** 来微调你的选择:
```js
// this.config is instance of ConfigStateService
const hasIdentityAndAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity && Abp.Account"
);
// false
const hasIdentityOrAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity || Abp.Account"
);
// true
```
创建权限选择器时,请考虑以下**规则**:
- 最多可组合两个键.
- `&&` 操作符查找两个键.
- `||` 操作符查找任意一个键.
- 空字符串 `''` 做为键将返回 `true`
- 使用没有第二个键的操作符将返回 `false`
### 如何从Store中获取翻译
`ConfigStateService``getLocalization` 用法翻译. 这里有一些示例:
```js
// this.config is instance of ConfigStateService
const identity = this.config.getLocalization("AbpIdentity::Identity");
// 'identity'
const notFound = this.config.getLocalization("AbpIdentity::IDENTITY");
// 'AbpIdentity::IDENTITY'
const defaultValue = this.config.getLocalization({
key: "AbpIdentity::IDENTITY",
defaultValue: "IDENTITY"
});
// 'IDENTITY'
```
请参阅[本地化文档](./Localization.md)了解详情.
## 分发方法
`ConfigStateService` 有几种分发方法,让你方便地将预定义操作分发到 `Store`.
### 如何从服务器获取应用程序配置
`dispatchGetAppConfiguration` 触发对端点的请求,该端点使用应用程序状态进行响应,然后将此响应作为配置状态放置到 `Store`中.
```js
// this.config is instance of ConfigStateService
this.config.dispatchGetAppConfiguration();
// returns a state stream which emits after dispatch action is complete
```
请注意,**你不必在应用程序启动时调用此方法**,因为在启动时已经从服务器收到了应用程序配置.
### 如何修补路由配置
`dispatchPatchRouteByName` 根据名称查找路由, 并将其在 `Store` 中的配置替换为作为第二个参数传递的新配置.
```js
// this.config is instance of ConfigStateService
const newRouteConfig: Partial<ABP.Route> = {
name: "Home",
path: "home",
children: [
{
name: "Dashboard",
path: "dashboard"
}
]
};
this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
### 如何添加新路由配置
`dispatchAddRoute``Store` 的配置状态添加一个新路由. 应该将路由配置做为方法参数传递.
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
`newRoute` 将被放置在根级别,没有任何父路由,并且其url将存储为 `'/path'`.
如果你想要**添加一个子路由,您可以这样做:**
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
parentName: "AbpAccount::Login",
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
`newRoute` 做为 `'AbpAccount::Login'` 父路由的子路由被放置,它的url被设置为 `'/account/login/page'`.
#### 路由配置属性
请参阅 `ABP.Route` 类型,获取可在参数中传递给 `dispatchSetEnvironment` 的所有属性. 你可以在[common.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27)中找到.
### 如何设置环境
`dispatchSetEnvironment` 将传递给它的环境变量放在 `Store` 中的配置状态下. 使用方法如下:
```js
// this.config is instance of ConfigStateService
this.config.dispatchSetEnvironment({
/* environment properties here */
});
// returns a state stream which emits after dispatch action is complete
```
注意,**你不必在应用程序启动时调用此方法**,因为环境变量已经在启动时存储了.
#### 环境属性
请参阅 `Config.Environment` 类型,获取可在参数中传递给 `dispatchSetEnvironment` 的所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13)中找到.
## 下一步是什么?
* [组件替换]](./Component-Replacement.md)

87
docs/zh-Hans/UI/Angular/Container-Strategy.md

@ -0,0 +1,87 @@
# ContainerStrategy
`ContainerStrategy`@abp/ng.core 包暴露出的抽象类. 有两种扩展容器扩展策略: `ClearContainerStrategy``InsertIntoContainerStrategy`. 它们实现了相同的方法和属性,这两种策略都可以帮助你定义容器的准备方式和内容的投影位置.
## API
`ClearContainerStrategy` 是一个扩展了 `ContainerStrategy` 的类. 它允许你**将内容投影之前清除容器**.
### 构造函数
```js
constructor(
public containerRef: ViewContainerRef,
private index?: number, // works only in InsertIntoContainerStrategy
)
```
- `containerRef` 是在投影内容时使用的 `ViewContainerRef`.
### getIndex
```js
getIndex(): number
```
该方法返回被 `0``containerRef` `length` 限制的给定索引. 对于没有索引的策略,它返回`0`.
### prepare
```js
prepare(): void
```
此方法在内容投影之前调用. 基于使用的容器策略,它要么清除容器,要么什么都不做(空操作).
## ClearContainerStrategy
`ClearContainerStrategy` 是一个扩展了 `ContainerStrategy` 的类. 它允许你**将内容投影之前清除容器**.
## InsertIntoContainerStrategy
`InsertIntoContainerStrategy` 是一个扩展了 `ContainerStrategy` 的类. 它允许你**将内容投影到容器中的特定节点索引上**.
## 预定义的容器策略
可以通过 `CONTAINER_STRATEGY` 常量访问预定义的容器策略.
### Clear
```js
CONTAINER_STRATEGY.Clear(containerRef: ViewContainerRef)
```
在内容投影之前清除给定的容器.
### Append
```js
CONTAINER_STRATEGY.Append(containerRef: ViewContainerRef)
```
将投影内容附加到容器中.
### Prepend
```js
CONTAINER_STRATEGY.Prepend(containerRef: ViewContainerRef)
```
将投影的内容预先写入容器中.
### Insert
```js
CONTAINER_STRATEGY.Insert(
containerRef: ViewContainerRef,
index: number,
)
```
将投影内容按照给定的索引(在`0` 到 `containerRef`的长度之间)插入到容器中.
## 另请参阅
- [ProjectionStrategy](./Projection-Strategy.md)

77
docs/zh-Hans/UI/Angular/Content-Projection-Service.md

@ -0,0 +1,77 @@
# 内容投影
你可以使用位于@abp/ng.core包中的 `ContentProjectionService` 简单明确的投影内容.
## 入门
你不必在模块或组件级别提供 `ContentProjectionService`,因为它已经在**根中提供了**. 你可以在组件中注入并开始使用它. 为了获得更好的类型支持,你可以将迭代项目的类型传递给它.
```js
import { ContentProjectionService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private contentProjectionService: ContentProjectionService) {}
}
```
## 用法
你可以使用 `ContentProjectionService``projectContent` 方法在你的项目中动态的渲染组件和模板.
### 如何将组件投影到根级别
如果将 `RootComponentProjectionStrategy` 做为 `projectContent` 方法的第一个参数,那么 `ContentProjectionService` 会解析投影组件并放在根级别,它还为组件传递上下文.
```js
const strategy = PROJECTION_STRATEGY.AppendComponentToBody(
SomeOverlayComponent,
{ someOverlayProp: "SOME_VALUE" }
);
const componentRef = this.contentProjectionService.projectContent(strategy);
```
在上面的示例中, `SomeOverlayComponent` 组件放置在 `<body>` 的**末尾**并返回 `ComponentRef`. 另外将应用给定的上下文,因此组件的 `someOverlayProp` 被设置为 `SOME_VALUE`.
> 你应该总是返回 `ComponentRef` 实例,因为它是对投影组件的引用,在你需要时使用该引用销毁投影视图和组件实例.
### 如何将组件和模板投影到容器中
如果将 `ComponentProjectionStrategy``TemplateProjectionStrategy` 做为 `projectContent` 方法的第一个参数,并且传递 `ViewContainerRef` 做为策略的第二个参数传递. 那么 `ContentProjectionService` 把组件或模板投影到给定的容器中,它还为组件或模板传递上下文.
```js
const strategy = PROJECTION_STRATEGY.ProjectComponentToContainer(
SomeComponent,
viewContainerRefOfTarget,
{ someProp: "SOME_VALUE" }
);
const componentRef = this.contentProjectionService.projectContent(strategy);
```
在上面的示例中,`viewContainerRefOfTarget`(它是一个`ViewContainerRef` 实例)将被清除,并把 `SomeComponent` 组件放在其中. 另外将应用给定的上下文,因此组件的 `someProp` 被设置为 `SOME_VALUE`.
> 你应该总是返回 `ComponentRef``EmbeddedViewRef` ,因为它是对投影内容的引用,在你需要时使用该引用销毁它们.
请参考[ProjectionStrategy](./Projection-Strategy.md)查看所有可用的投影策略以及如何构建自己的投影策略.
## API
### projectContent
```js
projectContent<T extends Type<any> | TemplateRef<any>>(
projectionStrategy: ProjectionStrategy<T>,
injector = this.injector,
): ComponentRef<C> | EmbeddedViewRef<C>
```
- `projectionStrategy` 参数是此处的要点,在上面进行了说明.
- `injector` 参数是 `Injector` 实例,你可以传递到投影内容. 在 `TemplateProjectionStrategy` 并没有使用到它.
## 下一步是什么?
- [TrackByService](./Track-By-Service.md)

55
docs/zh-Hans/UI/Angular/Content-Security-Strategy.md

@ -0,0 +1,55 @@
# ContentSecurityStrategy
`ContentSecurityStrategy` 是@abp/ng.core包暴露出的抽象类. 它可以根据[内容安全策略](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)帮助你将内联脚本或样式标记为安全.
## API
### 构造函数
```js
constructor(public nonce?: string)
```
- `nonce` 启用将内联脚本或样式列入白名单,避免在[script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script)和[style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles)指令中使用 `unsafe-inline`.
### applyCSP
```js
applyCSP(element: HTMLScriptElement | HTMLStyleElement): void
```
该方法将上述属性映射到给定`element`.
## LooseContentSecurityPolicy
`LooseContentSecurityPolicy` 是扩展了 `ContentSecurityStrategy` 的类. 它需要 `nonce` 和带有给定 `<script>``<style>` 标记的标签.
## NoContentSecurityPolicy
`NoContentSecurityPolicy` 是扩展了 `ContentSecurityStrategy` 的类. 它不会将内联脚本和样式标记为安全. 你可以将其视为空操作的替代方案.
s
## 预定义内容安全策略
可以通过 `CONTENT_SECURITY_STRATEGY` 常量访问预定义的内容安全策略.
### Loose
```js
CONTENT_SECURITY_STRATEGY.Loose(nonce: string)
```
`nonce` 会被设置.
### None
```js
CONTENT_SECURITY_STRATEGY.None()
```
什么都不会做.
## 另请参阅
- [DomInsertionService](./Dom-Insertion-Service.md)
- [ContentStrategy](./Content-Strategy.md)

85
docs/zh-Hans/UI/Angular/Content-Strategy.md

@ -0,0 +1,85 @@
# ContentStrategy
`ContentStrategy` 是@abp/ng.core包暴露出的抽象类. 它可以帮助您创建内联脚本或样式.
## API
### 构造函数
```js
constructor(
public content: string,
protected domStrategy?: DomStrategy,
protected contentSecurityStrategy?: ContentSecurityStrategy
)
```
- `content` 是被设置为 `<script>``<style>` 元素的 `textContent` 属性.
- `domStrategy` 是在插入创建的元素时将使用的 `DomStrategy` . (默认值: AppendToHead_)
- `contentSecurityStrategy``ContentSecurityStrategy`, 在创建的元素插入使用它它. (默认值: None_)
请参考[DomStrategy](./Dom-Strategy.md)和[ContentSecurityStrategy](./Content-Security-Strategy.md)文档了解它们的用法.
### createElement
```js
createElement(): HTMLScriptElement | HTMLStyleElement
```
该方法创建并且返回 `<script>``<style>` 元素, 将 `content` 设置为 `textContent`.
### insertElement
```js
insertElement(): void
```
该方法创建并且插入一个 `<script>``<style>` 元素.
## ScriptContentStrategy
`ScriptContentStrategy` 是扩展了 `ContentStrategy` 的类. 你可以使用它将 **`<script>`元素插入DOM**.
## StyleContentStrategy
`StyleContentStrategy` 是扩展了 `ContentStrategy` 的类. 你可以使用它将 **`<style>`元素插入DOM**.
## 预定义内容策略
预定义的内容策略可以通过 `CONTENT_STRATEGY` 常量访问.
### AppendScriptToBody
```js
CONTENT_STRATEGY.AppendScriptToBody(content: string)
```
创建具有给定内容的 `<script>` 元素, 并放置在文档中`<body>`标记的**末尾**.
### AppendScriptToHead
```js
CONTENT_STRATEGY.AppendScriptToHead(content: string)
```
创建具有给定内容的 `<script>` 元素, 并放置在文档中`<head>`标签的**末尾**.
### AppendStyleToHead
```js
CONTENT_STRATEGY.AppendStyleToHead(content: string)
```
创建具有给定内容的 `<style>` 元素, 并放置在文档中`<head>`标签的**末尾**.
### PrependStyleToHead
```js
CONTENT_STRATEGY.PrependStyleToHead(content: string)
```
创建具有给定内容的 `<style>` 元素, 并放置在文档中`<head>`标签的**头部**.
## 另请参阅
- [DomInsertionService](./Dom-Insertion-Service.md)

100
docs/zh-Hans/UI/Angular/Context-Strategy.md

@ -0,0 +1,100 @@
# ContextStrategy
`ContextStrategy` 是@abp/ng.core包暴露出的抽象类. 有三种扩展容器扩展策略: `ComponentContextStrategy` , `TemplateContextStrategy``NoContextStrategy`. 它们实现了相同的方法和属性,这三种策略都可以帮助你获得定义投影内容上下文
## ComponentContextStrategy
`ComponentContextStrategy` 是扩展了 `ContextStrategy` 的类, 它允许你**将上下文传递给一个投影组件**.
### 构造函数
```js
constructor(public context: Partial<InferredInstanceOf<T>>) {}
```
- `T` 在这里引用组件类型, 例. `Type<C>`.
- `InferredInstanceOf`@abp/ng.core 包暴露的实用类型. 它可以推断组件.
- `context` 是将映射到投影组件的属性.
### setContext
```js
setContext(componentRef: ComponentRef<InferredInstanceOf<T>>): Partial<InferredInstanceOf<T>>
```
该方法将上下文的每个属性映射到具有相同名称的组件属性,并调用更改检测.映射后返回上下文.
## TemplateContextStrategy
`TemplateContextStrategy` 是扩展了 `ContextStrategy` 的类. 它允许你**将上下文传递到投影模板**.
### 构造函数
```js
constructor(public context: Partial<InferredContextOf<T>>) {}
```
- `T` 在这里引用组件类型, 例. `Type<C>`.
- `InferredInstanceOf`@abp/ng.core 包暴露的实用类型. 它可以推断组件.
- `context` 是将映射到投影组件的属性.
### setContext
```js
setContext(): Partial<InferredContextOf<T>>
```
此方法不执行任何操作它仅返回上下文,因为模板上下文没有被映射. 而是作为参数传递给 `createEmbeddedView` 方法.
## NoContextStrategy
`NoContextStrategy` 是扩展了 `ContextStrategy` 的类. 它允许你**跳过将任何上下文传递到投影内容**.
### 构造函数
```js
constructor()
```
不像其它的策略, `NoContextStrategy` 构造函数没有参数.
### setContext
```js
setContext(): undefined
```
由于没有上下文,方法没有参数并且返回`undefined`.
## 预定义上下文策略
可以通过 `CONTEXT_STRATEGY` 常量访问预定义的上下文策略.
### None
```js
CONTEXT_STRATEGY.None()
```
该策略不会将任何上下文传递到投影内容。
### Component
```js
CONTEXT_STRATEGY.Component(context: Partial<InferredContextOf<T>>)
```
该策略将帮助你将给定的上下文传递到投影组件.
### Template
```js
CONTEXT_STRATEGY.Template(context: Partial<InferredContextOf<T>>)
```
该策略将帮助你将给定的上下文传递到投影模板.
## 另请参阅
- [ProjectionStrategy](./Projection-Strategy.md)

46
docs/zh-Hans/UI/Angular/Custom-Setting-Page.md

@ -0,0 +1,46 @@
# 自定义设置页面
不同的模块提供它们的设置选项卡. 你可以通过3个步骤在项目中自定义设置页面.
1. 创建一个组件
```js
import { Select } from '@ngxs/store';
import { Component } from '@angular/core';
@Component({
selector: 'app-your-custom-settings',
template: `
custom-settings works!
`,
})
export class YourCustomSettingsComponent {
// Your component logic
}
```
2. 添加 `YourCustomSettingsComponent``AppModule` 中的 `declarations``entryComponents` 数组中.
3. 打开 `app.component.ts``ngOnInit` 添加以下内容:
```js
import { addSettingTab } from '@abp/ng.theme.shared';
// ...
ngOnInit() {
addSettingTab({
component: YourCustomSettingsComponent,
name: 'Type here the setting tab title (you can type a localization key, e.g: AbpAccount::Login',
order: 4,
requiredPolicy: 'type here a policy key'
});
}
```
导航到 `/setting-management` 路由你会看到以下变化:
![Custom Settings Tab](./images/custom-settings.png)
## 下一步是什么?
- [懒加载 Scripts 与 Styles](./Lazy-Load-Service.md)

130
docs/zh-Hans/UI/Angular/Dom-Insertion-Service.md

@ -0,0 +1,130 @@
# Dom插入(Scripts与Styles)
你可以使用@abp/ng.core包提供的 `DomInsertionService` 以简单的方式的插入脚本与样式.
## 入门
你不必在模块或组件级别提供 `DomInsertionService` ,因为它已经在**根中**提供. 你可以在组件,指令或服务中直接注入并使用它.
```js
import { DomInsertionService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
}
```
## 用法
你可以使用 `DomInsertionService` 提供的 `insertContent` 方法去创建一个 `<script>``<style>` 元素到DOM的指定位置. 还有 `projectContent` 方法用于渲染组件和模板.
### 如何插入Scripts
`insertContent` 方法的第一个参数需要一个 `ContentStrategy`. 如果传递 `ScriptContentStrategy` 实例, `DomInsertionService` 将创建具有给定内容的 `<script>` 元素并放置在指定的DOM位置.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
const scriptElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendScriptToBody('alert()')
);
}
}
```
在上面的示例中,将 `<script>alert()</script>` 元素放置在 `<body>`的末尾, `scriptElement` 类型是一个 `HTMLScriptElement`.
请参考[ContentStrategy](./Content-Strategy.md)查看所有可用的内容策略以及如何构建自己的内容策略.
> 重要说明: `DomInsertionService` 不会两次插入相同的内容. 为了再次添加内容你首先应该使用 `removeContent` 方法删除旧内容.
### 如何插入Styles
`insertContent` 方法的第一个参数需要一个 `ContentStrategy`. 如果传递 `StyleContentStrategy` 实例, `DomInsertionService` 将创建具有给定内容的 `<style>` 元素并放置在指定的DOM位置.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
const styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
}
```
在上面的示例中,将 `<style>body {margin: 0;}</style>` 元素放置在 `<head>`的末尾, `styleElement` 类型是一个 `HTMLStyleElement`.
请参考[ContentStrategy](./Content-Strategy.md)查看所有可用的内容策略以及如何构建自己的内容策略.
.
> 重要说明: `DomInsertionService` 不会两次插入相同的内容. 为了再次添加内容你首先应该使用 `removeContent` 方法删除旧内容.
### 如何删除已插入的 Scripts & Styles
如果你传递 `HTMLScriptElement``HTMLStyleElement` 做为 `removeContent` 方法的第一个参数, `DomInsertionService` 将删除给定的元素.
```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);
}
}
```
在上面的示例中,销毁组件时,将从 `<head>` 中删除 `<style>body {margin: 0;}</style>` 元素.
## API
### insertContent
```js
insertContent<T extends HTMLScriptElement | HTMLStyleElement>(
contentStrategy: ContentStrategy<T>,
): T
```
- `contentStrategy` 是方法的重要参数,已经在上方进行说明.
- 根据给定的策略返回 `HTMLScriptElement``HTMLStyleElement`.
### removeContent
```js
removeContent(element: HTMLScriptElement | HTMLStyleElement): void
```
- `element` 参数是已插入的 `HTMLScriptElement``HTMLStyleElement` 元素,它们应由 `insertContent` 方法返回.
## 下一步是什么?
- [ContentProjectionService](./Content-Projection-Service.md)

80
docs/zh-Hans/UI/Angular/Dom-Strategy.md

@ -0,0 +1,80 @@
# DomStrategy
`DomStrategy` 是@abp/ng.core包暴露出的抽象类. 它的实例定义了如何将元素附加到DOM以及如何被其它类(如`LoadingStrategy`)使用.
## API
### 构造函数
```js
constructor(
public target?: HTMLElement,
public position?: InsertPosition
)
```
- `target` 是一个 HTMLElement (默认值: document.head_).
- `position` 定义将创建的元素放置在何处. 可以在[此处](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement)找到所有可能的 `position` 值(默认值: 'beforeend'_).
### insertElement
```js
insertElement(element: HTMLElement): void
```
该方法根据 `postion` 将给定 `元素` 插入到目标中.
## 预定义DOM策略
可以通过 `DOM_STRATEGY` 常量访问预定义的dom策略.
### AppendToBody
```js
DOM_STRATEGY.AppendToBody()
```
`insertElement` 将给定 `元素` 放在 `<body>` 的末尾.
### AppendToHead
```js
DOM_STRATEGY.AppendToHead()
```
`insertElement` 将给定 `元素` 放在 `<head>` 的末尾.
### PrependToHead
```js
DOM_STRATEGY.PrependToHead()
```
`insertElement` 将给定 `元素` 放在 `<head>` 的头部.
### AfterElement
```js
DOM_STRATEGY.AfterElement(target: HTMLElement)
```
`insertElement` 将给定 `元素` 放在 `target` 之后 (做为同级元素).
### BeforeElement
```js
DOM_STRATEGY.BeforeElement(target: HTMLElement)
```
`insertElement` 将给定 `元素` 放在 `target` 之前 (做为同级元素).
## 另请参阅
- [DomInsertionService](./Dom-Insertion-Service.md)
- [LazyLoadService](./Lazy-Load-Service.md)
- [LoadingStrategy](./Loading-Strategy.md)
- [ContentStrategy](./Content-Strategy.md)
- [ProjectionStrategy](./Projection-Strategy.md)

3
docs/zh-Hans/UI/Angular/HTTP-Requests.md

@ -0,0 +1,3 @@
## HTTP请求
TODO...

3
docs/zh-Hans/UI/Angular/Lazy-Load-Service.md

@ -0,0 +1,3 @@
# 如何懒加载 Scripts 与 Styles
TODO...

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

@ -1,3 +1,79 @@
# Permission Management
# 权限管理
TODO...
权限是为特定用户,角色或客户端授予或禁止的简单策略. 你可以在[ABP授权文档](../../Authorization.md)中阅读更多信息.
您可以使用 `ConfigState``getGrantedPolicy` 选择器获取经过身份验证的用户的权限.
你可以从Store中获取权限的布尔值:
```js
import { Store } from '@ngxs/store';
import { ConfigState } from '../states';
export class YourComponent {
constructor(private store: Store) {}
ngOnInit(): void {
const canCreate = this.store.selectSnapshot(ConfigState.getGrantedPolicy('AbpIdentity.Roles.Create'));
}
// ...
}
```
或者你可以通过 `ConfigStateService` 获取它:
```js
import { ConfigStateService } from '../services/config-state.service';
export class YourComponent {
constructor(private configStateService: ConfigStateService) {}
ngOnInit(): void {
const canCreate = this.configStateService.getGrantedPolicy('AbpIdentity.Roles.Create');
}
// ...
}
```
## 权限指令
你可以使用 `PermissionDirective` 来根据用户的权限控制DOM元素是否可见.
```html
<div *abpPermission="AbpIdentity.Roles">
仅当用户具有`AbpIdentity.Roles`权限时,此内容才可见.
</div>
```
如上所示,你可以使用 `abpPermission` 结构指令从DOM中删除元素.
该指令也可以用作属性指令,但是我们建议你将其用作结构指令.
## 权限守卫
如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `PermissionGuard`.
`requiredPolicy` 添加到路由模块中的 `routes`属性.
```js
const routes: Routes = [
{
path: 'path',
component: YourComponent,
canActivate: [PermissionGuard],
data: {
routes: {
requiredPolicy: 'AbpIdentity.Roles.Create',
},
},
},
];
```
授予的策略存储在 `ConfigState``auth` 属性中.
## 下一步是什么?
* [Config State](./Config-State.md)

0
docs/zh-Hans/UI/Angular/Projection-Strategy.md

3
docs/zh-Hans/UI/Angular/Service-Proxies.md

@ -0,0 +1,3 @@
## 服务代理
TODO...

98
docs/zh-Hans/UI/Angular/Track-By-Service.md

@ -0,0 +1,98 @@
# 轻松实现TrackByFunction
`TrackByService` 是一个实用服务,为Angular模板中最常见的需求之一: `TrackByFunction` 提供简单的实现. 在继续下面的内容之前,请参先阅 [Angular 文档](https://angular.io/guide/template-syntax#ngfor-with-trackby).
## 入门
你不必在模块或组件级别提供 `TrackByService`,因为它已经在**根中提供了**. 你可以在组件中注入并开始使用它. 为了获得更好的类型支持,你可以将迭代项目的类型传递给它.
```js
import { TrackByService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
list: Item[];
constructor(public readonly track: TrackByService<Item>) {}
}
```
> 注意到 `track``public` 并且 `readonly` 了吗? 因为我们将看到一些在组件模板中直接使用 `TrackByService` 实例的方法示例. 可以把它看做反模式,但它有自身的优势,尤其是在利用组件继承时. 你始终可以使用公共组件属性.
**成员也被导出做为独立的函数.** 如果你不想注入 `TrackByService`, 你可以直接在类中导入并使用这些函数.
## 用法
有两种方法可用.
1. 你可以直接注入 `TrackByService` 到你的组件并且使用它的成员.
2. 你可以在直接在组件属性上使用导出的函数.
### 如何通过一个键跟踪项
你可以使用 `by` 获取一个 `TrackByFunction` , 该函数根据它的一个键来跟踪迭代的对象. 你可以将迭代类型传递给它获得类型支持.
```html
<!-- template of DemoComponent -->
<div *ngFor="let item of list; trackBy: track.by('id')">{%{{{ item.name }}}%}</div>
```
`by` 作为一个独立函数导出,命名为 `trackBy`.
```js
import { trackBy } from "@abp/ng.core";
@Component({
template: `
<div
*ngFor="let item of list; trackBy: trackById"
>
{%{{{ item.name }}}%}
</div>
`,
})
class DemoComponent {
list: Item[];
trackById = trackBy<Item>('id');
}
```
### 如何通过深度嵌套的键进行跟踪
你可以使用 `byDeep` 获取一个 `TrackByFunction` , 它根据深度嵌套的键跟踪迭代对象. 你可以将迭代类型传递给它获得类型支持.
```html
<!-- template of DemoComponent -->
<div
*ngFor="let item of list; trackBy: track.byDeep('tenant', 'account', 'id')"
>
{%{{{ item.tenant.name }}}%}
</div>
```
`byDeep` 作为一个独立函数导出,命名为 `trackByDeep`.
```js
import { trackByDeep } from "@abp/ng.core";
@Component({
template: `
<div
*ngFor="let item of list; trackBy: trackByTenantAccountId"
>
{%{{{ item.name }}}%}
</div>
`,
})
class DemoComponent {
list: Item[];
trackByTenantAccountId = trackByDeep<Item>('tenant', 'account', 'id');
}
```

8
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md

@ -12,18 +12,18 @@ ABP框架还向标准bootstrap组件添加了一些**实用的功能**.
这里是ABP框架包装的组件列表:
* [Alerts](Alerts.md)
* [Buttons](Buttons.md)
* [Cards](Cards.md)
* [Alerts](Alerts.md)
* [Tabs](Tabs.md)
* [Grids](Grids.md)
* [Modals](Modals.md)
* [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md)
* [Grids](Grids.md)
* [List Groups](List-Groups.md)
* [Modals](Modals.md)
* [Paginator](Paginator.md)
* [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md)
* [Tabs](Tabs.md)
* [Tooltips](Tooltips.md)
* ...

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

@ -17,7 +17,7 @@
## Demo
参阅[列表组demo页面](https://bootstrap-taghelpers.abp.io/Components/ListGroups)查看示例.
参阅[列表组demo页面](https://bootstrap-taghelpers.abp.io/Components/ListGroup)查看示例.
## Attributes

2
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Progress-Bars.md

@ -24,7 +24,7 @@
## Demo
参阅[进度条demo页面](https://bootstrap-taghelpers.abp.io/Components/Progress-Bars)查看示例.
参阅[进度条demo页面](https://bootstrap-taghelpers.abp.io/Components/Progressbars)查看示例.
## Attributes

273
docs/zh-Hans/UI/Common/Utils/Linked-List.md

@ -1,15 +1,21 @@
# 链表 (双向)
Core模块提供了称为[双链表](https://en.wikipedia.org/wiki/Doubly_linked_list)的实用数据结构. 简而言之双向链表是一系列记录(又称节点),这些记录具有上一个节点,下一个节点及其自身值(或数据)的信息.
@abp/utils包提供了称为[双链表](https://en.wikipedia.org/wiki/Doubly_linked_list)的实用数据结构. 简而言之双向链表是一系列记录(又称节点),这些记录具有上一个节点,下一个节点及其自身值(或数据)的信息.
## 入门
要创建一个双向链表,你需要做的就是导入和创建它的一个新的实例:
```js
import { LinkedList } from '@abp/ng.core';
import { LinkedList } from '@abp/utils';
const list = new LinkedList();
var list = new LinkedList();
```
MVC:
```js
var list = new abp.utils.common.LinkedList();
```
构造函数没有任何参数.
@ -24,7 +30,7 @@ const list = new LinkedList();
#### addHead(value)
```js
addHead(value: T): ListNode\<T\>
addHead(value: T): ListNode<T>
```
将给定值添加到链表的第一个节点:
@ -48,7 +54,7 @@ list.addHead('c');
#### addManyHead(values)
```js
addManyHead(values: T\[\]): ListNode\<T\>\[\]
addManyHead(values: T[]): ListNode<T>[]
```
将给定的多个值添加到链表的第一个节点:
@ -68,7 +74,7 @@ list.addManyHead(['x', 'y', 'z']);
#### addTail(value)
```js
addTail(value: T): ListNode\<T\>
addTail(value: T): ListNode<T>
```
将给定值添加到链表的最后一个节点:
@ -92,7 +98,7 @@ list.addTail('c');
#### addManyTail(values)
```js
addManyTail(values: T\[\]): ListNode\<T\>\[\]
addManyTail(values: T[]): ListNode<T>[]
```
将给定多个值添加到链表的最后一个节点:
@ -107,10 +113,10 @@ list.addManyTail(['x', 'y', 'z']);
// "a" <-> "b" <-> "c" <-> "x" <-> "y" <-> "z"
```
#### addAfter(value, previousValue, compareFn)
#### addAfter(value, previousValue [, compareFn])
```js
addAfter(value: T, previousValue: T, compareFn = compare): ListNode\<T\>
addAfter(value: T, previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
```
添加给定值到previousValue节点后:
@ -151,10 +157,10 @@ list.addAfter(
> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数.
#### addManyAfter(values, previousValue, compareFn)
#### addManyAfter(values, previousValue [, compareFn])
```js
addManyAfter(values: T\[\], previousValue: T, compareFn = compare): ListNode\<T\>\[\]
addManyAfter(values: T[], previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
```
添加给定的多个值到previousValue节点后:
@ -188,10 +194,10 @@ list.addManyAfter(
> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数.
#### addBefore(value, nextValue, compareFn)
#### addBefore(value, nextValue [, compareFn])
```js
addBefore(value: T, nextValue: T, compareFn = compare): ListNode\<T\>
addBefore(value: T, nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
```
添加给值到previousValue节点前:
@ -231,10 +237,10 @@ list.addBefore(
#### addManyBefore(values, nextValue, compareFn)
#### addManyBefore(values, nextValue [, compareFn])
```js
addManyBefore(values: T\[\], nextValue: T, compareFn = compare): ListNode\<T\>\[\]
addManyBefore(values: T[], nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
```
@ -277,7 +283,7 @@ list.addManyBefore(
#### addByIndex(value, position)
```js
addByIndex(value: T, position: number): ListNode\<T\>
addByIndex(value: T, position: number): ListNode<T>
```
在链表的指定位置添加节点:
@ -313,7 +319,7 @@ list.addByIndex('x', -1);
#### addManyByIndex(values, position)
```js
addManyByIndex(values: T\[\], position: number): ListNode\<T\>\[\]
addManyByIndex(values: T[], position: number): ListNode<T>[]
```
添加多个节点到链表的指定位置:
@ -345,7 +351,7 @@ list.addManyByIndex(['x', 'y'], -1);
#### add(value).head()
```js
add(value: T).head(): ListNode\<T\>
add(value: T).head(): ListNode<T>
```
将添加的节点移动到链表头:
@ -373,7 +379,7 @@ list.add('c').head();
#### add(value).tail()
```js
add(value: T).tail(): ListNode\<T\>
add(value: T).tail(): ListNode<T>
```
将添加的节点移动到链表尾:
@ -398,10 +404,10 @@ list.add('c').tail();
#### add(value).after(previousValue, compareFn)
#### add(value).after(previousValue [, compareFn])
```js
add(value: T).after(previousValue: T, compareFn = compare): ListNode\<T\>
add(value: T).after(previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
```
将添加的节点移动到指定节点后:
@ -445,10 +451,10 @@ list
#### add(value).before(nextValue, compareFn)
#### add(value).before(nextValue [, compareFn])
```js
add(value: T).before(nextValue: T, compareFn = compare): ListNode\<T\>
add(value: T).before(nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>
```
将添加的节点移动到指定节点前:
@ -495,7 +501,7 @@ list
#### add(value).byIndex(position)
```js
add(value: T).byIndex(position: number): ListNode\<T\>
add(value: T).byIndex(position: number): ListNode<T>
```
将添加的节点移动到链表指定位置:
@ -537,7 +543,7 @@ list.add('x').byIndex(-1);
#### addMany(values).head()
```js
addMany(values: T\[\]).head(): ListNode\<T\>\[\]
addMany(values: T[]).head(): ListNode<T>[]
```
将添加的多个节点移动到链表头:
@ -561,7 +567,7 @@ list.addMany(['x', 'y', 'z']).head();
#### addMany(values).tail()
```js
addMany(values: T\[\]).tail(): ListNode\<T\>\[\]
addMany(values: T[]).tail(): ListNode<T>[]
```
将添加的多个节点移动到链表尾:
@ -582,10 +588,10 @@ list.addMany(['x', 'y', 'z']).tail();
#### addMany(values).after(previousValue, compareFn)
#### addMany(values).after(previousValue [, compareFn])
```js
addMany(values: T\[\]).after(previousValue: T, compareFn = compare): ListNode\<T\>\[\]
addMany(values: T[]).after(previousValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
```
将添加的多个节点移动到指定节点后:
@ -624,10 +630,10 @@ list
#### addMany(values).before(nextValue, compareFn)
#### addMany(values).before(nextValue [, compareFn])
```js
addMany(values: T\[\]).before(nextValue: T, compareFn = compare): ListNode\<T\>\[\]
addMany(values: T[]).before(nextValue: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
```
将添加的多个节点移动到指定节点前:
@ -669,7 +675,7 @@ list
#### addMany(values).byIndex(position)
```js
addMany(values: T\[\]).byIndex(position: number): ListNode\<T\>\[\]
addMany(values: T[]).byIndex(position: number): ListNode<T>[]
```
将添加的多个节点移动到链表的指定位置:
@ -712,7 +718,7 @@ list.addMany(['x', 'y']).byIndex(-1);
#### dropHead()
```js
dropHead(): ListNode\<T\> | undefined
dropHead(): ListNode<T> | undefined
```
删除链表的第一个节点:
@ -732,7 +738,7 @@ list.dropHead();
#### dropManyHead(count)
```js
dropManyHead(count: number): ListNode\<T\>\[\]
dropManyHead(count: number): ListNode<T>[]
```
删除指定数量的链表的头节点:
@ -752,7 +758,7 @@ list.dropManyHead(2);
#### dropTail()
```js
dropTail(): ListNode\<T\> | undefined
dropTail(): ListNode<T> | undefined
```
删除链表的最后一个节点:
@ -772,7 +778,7 @@ list.dropTail();
#### dropManyTail(count)
```js
dropManyTail(count: number): ListNode\<T\>\[\]
dropManyTail(count: number): ListNode<T>[]
```
删除指定数量的链表的尾节点:
@ -792,7 +798,7 @@ list.dropManyTail(2);
#### dropByIndex(position)
```js
dropByIndex(position: number): ListNode\<T\> | undefined
dropByIndex(position: number): ListNode<T> | undefined
```
删除链表中给定位置的节点:
@ -825,7 +831,7 @@ list.dropByIndex(-2);
#### dropManyByIndex(count, position)
```js
dropManyByIndex(count: number, position: number): ListNode\<T\>\[\]
dropManyByIndex(count: number, position: number): ListNode<T>[]
```
删除链表中给定位置与数量的多个节点:
@ -856,10 +862,10 @@ list.dropManyByIndex(2, -2);
#### dropByValue(value, compareFn)
#### dropByValue(value [, compareFn])
```js
dropByValue(value: T, compareFn = compare): ListNode\<T\> | undefined
dropByValue(value: T, compareFn?: ListComparisonFn<T>): ListNode<T> | undefined
```
删除链表中含有给定值的第一个节点:
@ -894,10 +900,10 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### dropByValueAll(value, compareFn)
#### dropByValueAll(value [, compareFn])
```js
dropByValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
dropByValueAll(value: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
```
删除链表中含有给定值的所有节点:
@ -933,7 +939,7 @@ list.dropByValue(0, (value, searchedValue) => value.x === searchedValue);
#### drop().head()
```js
drop().head(): ListNode\<T\> | undefined
drop().head(): ListNode<T> | undefined
```
删除链表的头节点:
@ -957,7 +963,7 @@ list.drop().head();
#### drop().tail()
```js
drop().tail(): ListNode\<T\> | undefined
drop().tail(): ListNode<T> | undefined
```
删除链表的尾节点:
@ -981,7 +987,7 @@ list.drop().tail();
#### drop().byIndex(position)
```js
drop().byIndex(position: number): ListNode\<T\> | undefined
drop().byIndex(position: number): ListNode<T> | undefined
```
删除链表指定位置的节点:
@ -1016,10 +1022,10 @@ list.drop().byIndex(-2);
#### drop().byValue(value, compareFn)
#### drop().byValue(value [, compareFn])
```js
drop().byValue(value: T, compareFn = compare): ListNode\<T\> | undefined
drop().byValue(value: T, compareFn?: ListComparisonFn<T>): ListNode<T> | undefined
```
删除链表中含有给定值的第一个节点:
@ -1058,10 +1064,10 @@ list
#### drop().byValueAll(value, compareFn)
#### drop().byValueAll(value [, compareFn])
```js
drop().byValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
drop().byValueAll(value: T, compareFn?: ListComparisonFn<T>): ListNode<T>[]
```
删除链表中含有给定值的所有节点:
@ -1103,7 +1109,7 @@ list
#### dropMany(count).head()
```js
dropMany(count: number).head(): ListNode\<T\>\[\]
dropMany(count: number).head(): ListNode<T>[]
```
删除链表中指定数量的头节点:
@ -1127,7 +1133,7 @@ list.dropMany(2).head();
#### dropMany(count).tail()
```js
dropMany(count: number).tail(): ListNode\<T\>\[\]
dropMany(count: number).tail(): ListNode<T>[]
```
删除链表中指定数量的尾节点::
@ -1151,7 +1157,7 @@ list.dropMany(2).tail();
#### dropMany(count).byIndex(position)
```js
dropMany(count: number).byIndex(position: number): ListNode\<T\>\[\]
dropMany(count: number).byIndex(position: number): ListNode<T>[]
```
删除链表中指定位置和数量的节点:
@ -1190,12 +1196,34 @@ list.dropMany(2).byIndex(-2);
有几个方法找到链表特定节点.
#### head
```js
head: ListNode<T> | undefined;
```
链表中的第一个节点.
#### tail
```js
tail: ListNode<T> | undefined;
```
链表中的最后一个节点.
#### length
```js
length: number;
```
链表的节点总数.
#### find(predicate)
```js
find(predicate: ListIteratorFunction\<T\>): ListNode\<T\> | undefined
find(predicate: ListIteratorFunction<T>): ListNode<T> | undefined
```
从链表中找到与给定谓词匹配的第一个节点:
@ -1205,7 +1233,7 @@ list.addTailMany(['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"
@ -1214,12 +1242,10 @@ found.next.value === "b"
*/
```
#### findIndex(predicate)
```js
findIndex(predicate: ListIteratorFunction\<T\>): number
findIndex(predicate: ListIteratorFunction<T>): number
```
从链表中找到与给定谓词匹配的第一个节点的位置:
@ -1229,10 +1255,10 @@ list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
const i0 = list.findIndex(node => node.next && node.next.value === 'b');
const i1 = list.findIndex(node => node.value === 'b');
const i2 = list.findIndex(node => node.previous && node.previous.value === 'b');
const i3 = list.findIndex(node => node.value === 'x');
var i0 = list.findIndex(node => node.next && node.next.value === 'b');
var i1 = list.findIndex(node => node.value === 'b');
var i2 = list.findIndex(node => node.previous && node.previous.value === 'b');
var i3 = list.findIndex(node => node.value === 'x');
/*
i0 === 0
@ -1247,7 +1273,7 @@ i3 === -1
#### get(position)
```js
get(position: number): ListNode\<T\> | undefined
get(position: number): ListNode<T> | undefined
```
查找并返回链表中特定位置的节点:
@ -1257,7 +1283,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
const found = list.get(1);
var found = list.get(1);
/*
found.value === "b"
@ -1268,10 +1294,10 @@ found.next.value === "c"
#### indexOf(value, compareFn)
#### indexOf(value [, compareFn])
```js
indexOf(value: T, compareFn = compare): number
indexOf(value: T, compareFn?: ListComparisonFn<T>): number
```
在链表中找到匹配给定值的第一个节点位置:
@ -1281,10 +1307,10 @@ list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
const i0 = list.indexOf('a');
const i1 = list.indexOf('b');
const i2 = list.indexOf('c');
const i3 = list.indexOf('x');
var i0 = list.indexOf('a');
var i1 = list.indexOf('b');
var i2 = list.indexOf('c');
var i3 = list.indexOf('x');
/*
i0 === 0
@ -1303,11 +1329,11 @@ list.addTailMany([{ 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);
const i1 = indexOf(2, (value, searchedValue) => value.x === searchedValue);
const i2 = indexOf(3, (value, searchedValue) => value.x === searchedValue);
const i3 = indexOf(0, (value, searchedValue) => value.x === searchedValue);
const i4 = indexOf(4, (value, searchedValue) => value.x === searchedValue);
var i0 = indexOf(1, (value, searchedValue) => value.x === searchedValue);
var i1 = indexOf(2, (value, searchedValue) => value.x === searchedValue);
var i2 = indexOf(3, (value, searchedValue) => value.x === searchedValue);
var i3 = indexOf(0, (value, searchedValue) => value.x === searchedValue);
var i4 = indexOf(4, (value, searchedValue) => value.x === searchedValue);
/*
i0 === 0
@ -1330,10 +1356,10 @@ i4 === -1
#### forEach(callback)
#### forEach(iteratorFn)
```js
forEach(callback: ListIteratorFunction\<T\>): void
forEach(iteratorFn: ListIteratorFn<T>): void
```
从头到尾在链表中的所有节点上运行回调函数:
@ -1359,7 +1385,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
for(const node of list) {
for(var node of list) { /* ES6 for...of statement */
console.log(node.value);
}
@ -1373,7 +1399,7 @@ for(const node of list) {
#### toArray()
```js
toArray(): T\[\]
toArray(): T[]
```
转换链表值为数组:
@ -1383,7 +1409,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
const arr = list.toArray();
var arr = list.toArray();
/*
arr === ['a', 'b', 'c']
@ -1395,7 +1421,7 @@ arr === ['a', 'b', 'c']
#### toNodeArray()
```js
toNodeArray(): T\[\]
toNodeArray(): T[]
```
转换链表节点为数组:
@ -1405,7 +1431,7 @@ list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
const arr = list.toNodeArray();
var arr = list.toNodeArray();
/*
arr[0].value === 'a'
@ -1414,12 +1440,10 @@ arr[2].value === 'a'
*/
```
#### toString()
#### toString(mapperFn)
```js
toString(): string
toString(mapperFn: ListMapperFn<T> = JSON.stringify): string
```
将链表转换为节点及其关系的字符串表示形式:
@ -1429,7 +1453,7 @@ list.addTailMany(['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"}'
@ -1443,12 +1467,89 @@ list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }]).tail();
// {"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'
*/
```
## 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` 是存储在 `LinkedList` 中的每个记录的节点.
- `value` value是存储在节点中的值,通过构造函数传递.
- `next` 引用列表中的下一个节点.
- `previous`引用列表中的上一个节点.
```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;
```
该函数在 `toString` 方法中用于在生成列表的字符串形式之前映射节点值.
#### ListComparisonFn
```js
type ListComparisonFn<T = any> = (nodeValue: T, comparedValue: any) => boolean;
```
该函数用于根据比较值添加,删除和查找节点.
#### ListIteratorFn
```js
type ListIteratorFn<T = any, R = boolean> = (
node: ListNode<T>,
index?: number,
list?: LinkedList,
) => R;
```
该函数在遍历列表时使用,可以对每个节点执行某些操作,也可以查找某个节点.

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

@ -308,6 +308,14 @@
{
"text": "Angular",
"items": [
{
"text": "服务代理",
"path": "UI/Angular/Service-Proxies.md"
},
{
"text": "HTTP请求",
"path": "UI/Angular/HTTP-Requests.md"
},
{
"text": "本地化",
"path": "UI/Angular/Localization.md"
@ -316,9 +324,33 @@
"text": "权限管理",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "配置状态",
"path": "UI/Angular/Config-State.md"
},
{
"text": "替换组件",
"path": "UI/Angular/Component-Replacement.md"
},
{
"text": "自定义设置页",
"path": "UI/Angular/Custom-Setting-Page.md"
},
{
"text": "懒加载Scripts与Styles",
"path": "UI/Angular/Lazy-Load-Service.md"
},
{
"text": "DomInsertionService",
"path": "UI/Angular/Dom-Insertion-Service.md"
},
{
"text": "ContentProjectionService",
"path": "UI/Angular/Content-Projection-Service.md"
},
{
"text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md"
}
]
},
@ -326,8 +358,13 @@
"text": "通用",
"items": [
{
"text": "链表 (双向)",
"path": "UI/Common/Utils/Linked-List.md"
"text": "Utilities",
"items": [
{
"text": "链表 (双向)",
"path": "UI/Common/Utils/Linked-List.md"
}
]
}
]
}

BIN
docs/zh-Hans/images/bookstore-home.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/zh-Hans/images/bookstore-login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/zh-Hans/images/db-migrator-output.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
docs/zh-Hans/images/rn-environment-local-ip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/zh-Hans/images/rn-expo-interface.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/zh-Hans/images/rn-host-local-ip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
docs/zh-Hans/images/rn-login-iphone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/zh-Hans/images/rn-tiered-local-ip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
docs/zh-Hans/images/swagger-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

BIN
docs/zh-Hans/images/vs-app-solution-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/ru.json

@ -0,0 +1,12 @@
{
"culture": "ru",
"texts": {
"GivenTenantIsNotAvailable": "Данный арендатор недоступен: {0}",
"Tenant": "Арендатор",
"Switch": "переключиться",
"Name": "Имя",
"SwitchTenantHint": "Оставьте поле Имя пустым, чтобы переключиться на администратора.",
"SwitchTenant": "Сменить арендатора",
"NotSelected": "Не выбрано"
}
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Core/CoreScriptContributor.cs

@ -1,8 +1,11 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Utils;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Core
{
[DependsOn(typeof(UtilsScriptContributor))]
public class CoreScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs

@ -12,6 +12,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2
{
//TODO: Add select2.full.min.js or localize!
context.Files.AddIfNotContains("/libs/select2/js/select2.min.js");
context.Files.AddIfNotContains("/libs/select2/js/select2-bootstrap-modal-patch.js");
}
}
}

13
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Utils/UtilsScriptContributor.cs

@ -0,0 +1,13 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Utils
{
public class UtilsScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/abp/utils/abp-utils.umd.min.js");
}
}
}

5
framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreTestBaseModule.cs

@ -1,10 +1,13 @@
using Volo.Abp.Http.Client;
using Volo.Abp.Autofac;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.TestBase
{
[DependsOn(typeof(AbpHttpClientModule))]
[DependsOn(typeof(AbpAspNetCoreModule))]
[DependsOn(typeof(AbpTestBaseModule))]
[DependsOn(typeof(AbpAutofacModule))]
public class AbpAspNetCoreTestBaseModule : AbpModule
{

20
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs

@ -3,19 +3,27 @@
public static class CliUrls
{
#if DEBUG
public const string WwwAbpIo = "https://localhost:44328/";
public const string WwwAbpIo = WwwAbpIoDevelopment;
public const string AccountAbpIo = "https://localhost:44333/";
public const string AccountAbpIo = AccountAbpIoDevelopment;
public const string NuGetRootPath = "https://localhost:44373/";
public const string NuGetRootPath = NuGetRootPathDevelopment;
#else
public const string WwwAbpIo = "https://abp.io/";
public const string WwwAbpIo = WwwAbpIoProduction;
public const string AccountAbpIo = "https://account.abp.io/";
public const string AccountAbpIo = AccountAbpIoProduction;
public const string NuGetRootPath = "https://nuget.abp.io/";
public const string NuGetRootPath = NuGetRootPathProduction;
#endif
public const string WwwAbpIoProduction = "https://abp.io/";
public const string AccountAbpIoProduction = "https://account.abp.io/";
public const string NuGetRootPathProduction = "https://nuget.abp.io/";
public const string WwwAbpIoDevelopment = "https://localhost:44328/";
public const string AccountAbpIoDevelopment = "https://localhost:44333/";
public const string NuGetRootPathDevelopment = "https://localhost:44373/";
public static string GetNuGetServiceIndexUrl(string apiKey)
{
return $"{NuGetRootPath}{apiKey}/v3/index.json";

16
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

@ -58,7 +58,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
string latestVersion;
#if DEBUG
latestVersion = GetCurrentVersionFromAssembly();
latestVersion = await GetLatestSourceCodeVersionAsync(name, type, $"{CliUrls.WwwAbpIoProduction}api/download/{type}/get-version/");
#else
latestVersion = await GetLatestSourceCodeVersionAsync(name, type);
#endif
@ -133,16 +133,12 @@ namespace Volo.Abp.Cli.ProjectBuilding
return new TemplateFile(fileContent, version, latestVersion, nugetVersion);
}
private static string GetCurrentVersionFromAssembly()
private async Task<string> GetLatestSourceCodeVersionAsync(string name, string type, string url = null)
{
var assembly = Assembly.GetExecutingAssembly();
var fullVersion = assembly.GetName().Version.ToString(); //eg: 2.6.0.0
return fullVersion.Substring(0, fullVersion.LastIndexOf('.')); //eg: 2.6.0
}
private async Task<string> GetLatestSourceCodeVersionAsync(string name, string type)
{
var url = $"{CliUrls.WwwAbpIo}api/download/{type}/get-version/";
if (url == null)
{
url = $"{CliUrls.WwwAbpIo}api/download/{type}/get-version/";
}
try
{

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

Loading…
Cancel
Save