Browse Source

Merge branch 'rel-4.1' into auto-merge/rel-4-0/37

pull/7018/head
maliming 5 years ago
committed by GitHub
parent
commit
d62b3f0a44
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .github/ISSUE_TEMPLATE
  2. 18
      .github/workflows/auto-pr.yml
  3. 1
      .gitignore
  4. 29
      Directory.Build.props
  5. 26
      README.md
  6. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  7. 156
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/es.json
  8. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
  9. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  10. 56
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/es.json
  11. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  12. 56
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json
  13. 12
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/de-DE.json
  14. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  15. 166
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/es.json
  16. 10
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  17. 362
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json
  18. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
  19. 1
      common.DotSettings
  20. 12
      common.props
  21. 2
      docs/en/Blob-Storing.md
  22. 12
      docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md
  23. 87
      docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/POST.md
  24. BIN
      docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/abp-contribution-graph-4-years.png
  25. BIN
      docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/ddd-implementation-guide-sample.png
  26. 202
      docs/en/Blog-Posts/2020-12-18 v4_1_Preview/POST.md
  27. BIN
      docs/en/Blog-Posts/2020-12-18 v4_1_Preview/abp-commercial-public-website.png
  28. BIN
      docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-identityserver-ui.png
  29. BIN
      docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-organization-units.png
  30. BIN
      docs/en/Blog-Posts/2020-12-18 v4_1_Preview/event-list-ui.png
  31. BIN
      docs/en/Blog-Posts/2020-12-18 v4_1_Preview/spanish-commercial-translation.png
  32. BIN
      docs/en/Blog-Posts/2020-12-18 v4_1_Preview/type-ahead.png
  33. 5
      docs/en/CLI.md
  34. 3
      docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md
  35. 48
      docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md
  36. 2
      docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md
  37. 12
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md
  38. 936
      docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md
  39. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-create-ui.png
  40. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-detail-ui.png
  41. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-list-ui.png
  42. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/index-title.png
  43. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-all.png
  44. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-create.png
  45. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-upcoming.png
  46. 110
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/POST.md
  47. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/both-example-result.png
  48. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/cover-image.png
  49. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-app-contract.png
  50. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-application.png
  51. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-blazor.png
  52. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/initial-project.png
  53. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/sample-appointment.gif
  54. 534
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md
  55. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/automated-nuget-feed-setup.png
  56. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/final-result.jpg
  57. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/sample-application.jpg
  58. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-blazor-component-1.jpg
  59. BIN
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-progress-bar.gif
  60. 1581
      docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/POST.md
  61. BIN
      docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-crud.gif
  62. BIN
      docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-with-books.gif
  63. BIN
      docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-create.gif
  64. BIN
      docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-delete.gif
  65. BIN
      docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-edit.gif
  66. BIN
      docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-list.gif
  67. 50
      docs/en/Customizing-Application-Modules-Guide.md
  68. 104
      docs/en/Module-Entity-Extensions.md
  69. 4
      docs/en/Object-Extensions.md
  70. 1
      docs/en/Road-Map.md
  71. 14
      docs/en/Samples/Index.md
  72. 4
      docs/en/Text-Templating.md
  73. 6
      docs/en/Tutorials/Part-7.md
  74. 327
      docs/en/UI/Angular/Data-Table-Column-Extensions.md
  75. 325
      docs/en/UI/Angular/Dynamic-Form-Extensions.md
  76. 442
      docs/en/UI/Angular/Entity-Action-Extensions.md
  77. 66
      docs/en/UI/Angular/Form-Validation.md
  78. 13
      docs/en/UI/Angular/List-Service.md
  79. 420
      docs/en/UI/Angular/Page-Toolbar-Extensions.md
  80. BIN
      docs/en/UI/Angular/images/user-action-extension-click-me-ng.png
  81. BIN
      docs/en/UI/Angular/images/user-page-toolbar-extension-click-me-ng.png
  82. BIN
      docs/en/UI/Angular/images/user-page-toolbar-extension-custom-click-me-ng.png
  83. BIN
      docs/en/UI/Angular/images/user-prop-extension-date-of-birth-field-ng.png
  84. BIN
      docs/en/UI/Angular/images/user-prop-extension-name-column-ng.png
  85. 28
      docs/en/UI/AspNetCore/Data-Tables.md
  86. 163
      docs/en/UI/AspNetCore/Page-Toolbar-Extensions.md
  87. 11
      docs/en/UI/AspNetCore/Tag-Helpers/Modals.md
  88. 25
      docs/en/UI/Blazor/Components/SubmitButton.md
  89. 47
      docs/en/UI/Blazor/Global-Scripts-Styles.md
  90. 2
      docs/en/UI/Blazor/Theming.md
  91. BIN
      docs/en/_resources/ddd-microservice-simple.psd
  92. BIN
      docs/en/_resources/ui-db-options.psd
  93. 27
      docs/en/docs-nav.json
  94. BIN
      docs/en/images/extension-navigation-property-form.png
  95. BIN
      docs/en/images/extension-navigation-property-table.png
  96. BIN
      docs/en/images/page-toolbar-button.png
  97. BIN
      docs/en/images/page-toolbar-custom-component.png
  98. 4
      docs/zh-Hans/Blob-Storing.md
  99. 5
      docs/zh-Hans/CLI.md
  100. 1979
      docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md

3
.github/ISSUE_TEMPLATE

@ -10,6 +10,7 @@ If you're creating a bug/problem report, please include followings:
* Your **ABP Framework version**.
* Your **User Interface** type (Angular/MVC/React... etc.) if the issue is related to a specific UI
* Your database provider(EF Core/MongoDB)
* Exception message and **stack trace** if available (check the logs).
* Steps needed to **reproduce** the problem.
@ -21,4 +22,4 @@ Please use Stack Overflow for your questions about using the framework, template
https://stackoverflow.com/questions/tagged/abp
Use **abp** tag in your questions.
Use **abp** tag in your questions.

18
.github/workflows/auto-pr.yml

@ -1,24 +1,24 @@
name: Merge branch rel-4.1 with rel-4.0
name: Merge branch dev with rel-4.1
on:
push:
branches:
- rel-4.0
- rel-4.1
jobs:
merge-rel-4-1-with-rel-4-0:
merge-dev-with-rel-4-1:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: rel-4.1
ref: dev
- name: Reset promotion branch
run: |
git fetch origin rel-4.0:rel-4.0
git reset --hard rel-4.0
git fetch origin $GITHUB_REF:$GITHUB_REF
git reset --hard $GITHUB_REF
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
branch: auto-merge/rel-4-0/${{github.run_number}}
title: Merge branch rel-4.1 with ${{github.ref}}
body: This PR generated automatically to merge rel-4.1 with rel-4.0. Please review the changed files before merging to prevent any errors that may occur.
branch: auto-merge/rel-4-1/${{github.run_number}}
title: Merge branch dev with rel-4.1
body: This PR generated automatically to merge dev with rel-4.1. Please review the changed files before merging to prevent any errors that may occur.
reviewers: ${{github.actor}}
token: ${{ github.token }}

1
.gitignore

@ -153,6 +153,7 @@ PublishScripts/
# NuGet Packages
*.nupkg
*.snupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.

29
Directory.Build.props

@ -0,0 +1,29 @@
<Project>
<PropertyGroup>
<!-- All Microsoft packages -->
<MicrosoftPackageVersion>5.0.*</MicrosoftPackageVersion>
<!-- Microsoft.NET.Test.Sdk https://www.nuget.org/packages/Microsoft.NET.Test.Sdk -->
<MicrosoftNETTestSdkPackageVersion>16.8.3</MicrosoftNETTestSdkPackageVersion>
<!-- NSubstitute https://www.nuget.org/packages/NSubstitute -->
<NSubstitutePackageVersion>4.2.2</NSubstitutePackageVersion>
<!-- Shouldly https://www.nuget.org/packages/Shouldly -->
<ShouldlyPackageVersion>4.0.1</ShouldlyPackageVersion>
<!-- xunit https://www.nuget.org/packages/xUnit -->
<xUnitPackageVersion>2.4.1</xUnitPackageVersion>
<!-- xunit.extensibility.execution https://www.nuget.org/packages/xunit.extensibility.execution -->
<xUnitExtensibilityExecutionPackageVersion>2.4.1</xUnitExtensibilityExecutionPackageVersion>
<!-- xunit.runner.visualstudio https://www.nuget.org/packages/xunit.runner.visualstudio -->
<xUnitRunnerVisualstudioPackageVersion>2.4.3</xUnitRunnerVisualstudioPackageVersion>
<!-- Mongo2Go https://www.nuget.org/packages/Mongo2Go -->
<Mongo2GoPackageVersion>2.2.14</Mongo2GoPackageVersion>
</PropertyGroup>
</Project>

26
README.md

@ -9,8 +9,8 @@ ABP Framework is a complete **infrastructure** based on the **ASP.NET Core** to
## Getting Started
- [Getting Started Guide](https://docs.abp.io/en/abp/4.0/Getting-Started) is the easiest way to start a new web application with the ABP Framework.
- [Web Application Development Tutorial](https://docs.abp.io/en/abp/4.0/Tutorials/Part-1) is a complete tutorial to develop a full stack web application.
- [Getting Started Guide](https://docs.abp.io/en/abp/latest/Getting-Started) is the easiest way to start a new web application with the ABP Framework.
- [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1) is a complete tutorial to develop a full stack web application.
### Quick Start
@ -44,32 +44,32 @@ ABP provides a **full stack developer experience**.
<img src="docs/en/images/ddd-microservice-simple.png">
ABP offers a complete, **modular** and **layered** software architecture based on **[Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design)** principles and patterns. It also provides the necessary infrastructure and guiding to [implement this architecture](https://docs.abp.io/en/abp/4.0/Domain-Driven-Design-Implementation-Guide).
ABP offers a complete, **modular** and **layered** software architecture based on **[Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design)** principles and patterns. It also provides the necessary infrastructure and guiding to [implement this architecture](https://docs.abp.io/en/abp/latest/Domain-Driven-Design-Implementation-Guide).
ABP Framework is suitable for **[microservice solutions](https://docs.abp.io/en/abp/latest/Microservice-Architecture)** as well as monolithic applications.
### Infrastructure
There are a lot of features provided by the ABP Framework to achieve real world scenarios easier, like [Event Bus](https://docs.abp.io/en/abp/4.0/Event-Bus), [Background Job System](https://docs.abp.io/en/abp/4.0/Background-Jobs), [Audit Logging](https://docs.abp.io/en/abp/4.0/Audit-Logging), [BLOB Storing](https://docs.abp.io/en/abp/4.0/Blob-Storing), [Data Seeding](https://docs.abp.io/en/abp/4.0/Data-Seeding), [Data Filtering](https://docs.abp.io/en/abp/4.0/Data-Filtering), etc.
There are a lot of features provided by the ABP Framework to achieve real world scenarios easier, like [Event Bus](https://docs.abp.io/en/abp/latest/Event-Bus), [Background Job System](https://docs.abp.io/en/abp/latest/Background-Jobs), [Audit Logging](https://docs.abp.io/en/abp/latest/Audit-Logging), [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing), [Data Seeding](https://docs.abp.io/en/abp/latest/Data-Seeding), [Data Filtering](https://docs.abp.io/en/abp/latest/Data-Filtering), etc.
### Cross Cutting Concerns
ABP also simplifies (and even automates wherever possible) cross cutting concerns and common non-functional requirements like [Exception Handling](https://docs.abp.io/en/abp/4.0/Exception-Handling), [Validation](https://docs.abp.io/en/abp/4.0/Validation), [Authorization](https://docs.abp.io/en/abp/4.0/Authorization), [Localization](https://docs.abp.io/en/abp/4.0/Localization), [Caching](https://docs.abp.io/en/abp/4.0/Caching), [Dependency Injection](https://docs.abp.io/en/abp/4.0/Dependency-Injection), [Setting Management](https://docs.abp.io/en/abp/4.0/Settings), etc.
ABP also simplifies (and even automates wherever possible) cross cutting concerns and common non-functional requirements like [Exception Handling](https://docs.abp.io/en/abp/latest/Exception-Handling), [Validation](https://docs.abp.io/en/abp/latest/Validation), [Authorization](https://docs.abp.io/en/abp/latest/Authorization), [Localization](https://docs.abp.io/en/abp/latest/Localization), [Caching](https://docs.abp.io/en/abp/latest/Caching), [Dependency Injection](https://docs.abp.io/en/abp/latest/Dependency-Injection), [Setting Management](https://docs.abp.io/en/abp/latest/Settings), etc.
### Application Modules
ABP is a modular framework and the Application Modules provide **pre-built application functionalities**;
- [**Account**](https://docs.abp.io/en/abp/4.0/Modules/Account): Provides UI for the account management and allows user to login/register to the application.
- **[Identity](https://docs.abp.io/en/abp/4.0/Modules/Identity)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library.
- [**IdentityServer**](https://docs.abp.io/en/abp/4.0/Modules/IdentityServer): Integrates to IdentityServer4.
- [**Tenant Management**](https://docs.abp.io/en/abp/4.0/Modules/Tenant-Management): Manages tenants for a [multi-tenant](https://docs.abp.io/en/abp/4.0/Multi-Tenancy) (SaaS) application.
- [**Account**](https://docs.abp.io/en/abp/latest/Modules/Account): Provides UI for the account management and allows user to login/register to the application.
- **[Identity](https://docs.abp.io/en/abp/latest/Modules/Identity)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library.
- [**IdentityServer**](https://docs.abp.io/en/abp/latest/Modules/IdentityServer): Integrates to IdentityServer4.
- [**Tenant Management**](https://docs.abp.io/en/abp/latest/Modules/Tenant-Management): Manages tenants for a [multi-tenant](https://docs.abp.io/en/abp/latest/Multi-Tenancy) (SaaS) application.
See the [Application Modules](https://docs.abp.io/en/abp/4.0/Modules/Index) document for all pre-built modules.
See the [Application Modules](https://docs.abp.io/en/abp/latest/Modules/Index) document for all pre-built modules.
### Startup Templates
The [Startup templates](https://docs.abp.io/en/abp/4.0/Startup-Templates/Index) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
The [Startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Index) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
## ABP Community
@ -83,11 +83,11 @@ Follow the [ABP Blog](https://blog.abp.io/) to learn the latest happenings in th
### Samples
See the [sample projects](https://docs.abp.io/en/abp/4.0/Samples/Index) built with the ABP Framework.
See the [sample projects](https://docs.abp.io/en/abp/latest/Samples/Index) built with the ABP Framework.
### Want to Contribute?
ABP is a community-driven open source project. See [the contribution guide](https://docs.abp.io/en/abp/4.0/Contribution/Index) if you want to be a part of this project.
ABP is a community-driven open source project. See [the contribution guide](https://docs.abp.io/en/abp/latest/Contribution/Index) if you want to be a part of this project.
## Official Links

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

@ -194,6 +194,9 @@
"CoverImage": "Cover Image",
"RemoveCacheConfirmationMessage": "Are you sure you remove the cache for \"{0}\" article?",
"SuccessfullyRemoved": "Successfully cleared",
"RemoveCache": "Remove Cache"
"RemoveCache": "Remove Cache",
"Language": "Language",
"Optional": "Optional",
"CreateArticleLanguageInfo": "The language in which the article is written"
}
}

156
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/es.json

@ -116,84 +116,84 @@
"UsernameOrEmailPlaceholder": "Usuario o email...",
"Member": "Miembro",
"PurchaseOrderNo": "Número de orden de compra",
"QuotationDate": "",
"CompanyName": "",
"CompanyAddress": "",
"Price": "",
"DiscountText": "",
"DiscountQuantity": "",
"DiscountPrice": "",
"Quotation": "",
"ExtraText": "",
"ExtraAmount": "",
"DownloadQuotation": "",
"Invoice": "",
"TaxNumber": "",
"InvoiceNumber": "",
"InvoiceDate": "",
"InvoiceNote": "",
"Quantity": "",
"AddProduct": "",
"AddProductWarning": "",
"TotalPrice": "",
"Generate": "",
"MissingQuantityField": "",
"MissingPriceField": "",
"CodeUsageStatus": "",
"Country": "",
"DeveloperCount": "",
"RequestCode": "",
"WebSite": "",
"GithubUsername": "",
"PhoneNumber": "",
"ProjectDescription": "",
"Referrer": "",
"DiscountRequests": "",
"Copylink": "",
"Disable": "",
"Enable": "",
"EnableSendEmail": "",
"SendEmail": "",
"SuccessfullyDisabled": "",
"SuccessfullyEnabled": "",
"EmailSent": "",
"SuccessfullySent": "",
"SuccessfullyDeleted": "",
"DiscountRequestDeletionWarningMessage": "",
"BusinessType": "",
"TotalQuestionCount": "",
"RemainingQuestionCount": "",
"TotalQuestionMustBeGreaterWarningMessage": "",
"QuestionCountsMustBeGreaterThanZero": "",
"UnlimitedQuestionCount": "",
"Notes": "",
"Menu:Community": "",
"Menu:Articles": "",
"Wait": "",
"Approve": "",
"Reject": "",
"Details": "",
"Url": "",
"Title": "",
"ContentSource": "",
"Status": "",
"ReadArticle": "",
"ArticleHasBeenWaiting": "",
"ArticleHasBeenApproved": "",
"ArticleHasBeenRejected": "",
"Permission:Community": "",
"Permission:CommunityArticle": "",
"Link": "",
"Enum:ContentSource:0": "",
"Enum:ContentSource:1": "",
"Enum:Status:0": "",
"Enum:Status:1": "",
"Enum:Status:2": "",
"Summary": "",
"AuthorName": "",
"CoverImage": "",
"RemoveCacheConfirmationMessage": "",
"SuccessfullyRemoved": "",
"QuotationDate": "Fecha de presupuesto",
"CompanyName": "Nombre de empresa",
"CompanyAddress": "Dirección de empresa",
"Price": "Precio",
"DiscountText": "Texto de descuento",
"DiscountQuantity": "Cantidad de descuento",
"DiscountPrice": "Precio de descuento",
"Quotation": "Presupuesto",
"ExtraText": "Texto extra",
"ExtraAmount": "Cantidad extra",
"DownloadQuotation": "Descarga el presupuesto",
"Invoice": "Factura",
"TaxNumber": "Identificación fiscal",
"InvoiceNumber": "Número de factura",
"InvoiceDate": "Fecha de factura",
"InvoiceNote": "Nota de factura",
"Quantity": "Cantidad",
"AddProduct": "Añadir producto",
"AddProductWarning": "Tu necesitas añadir un producto!",
"TotalPrice": "Precio total",
"Generate": "Generar",
"MissingQuantityField": "El campo cantidad es requerido!",
"MissingPriceField": "El campo precio es requerido!",
"CodeUsageStatus": "Estado",
"Country": "País",
"DeveloperCount": "Cuentas de desarrollo",
"RequestCode": "Solicitud de código",
"WebSite": "Sitio web",
"GithubUsername": "Nombre de usuario Github",
"PhoneNumber": "Número de teléfono",
"ProjectDescription": "Descripción del proyecto",
"Referrer": "Referente",
"DiscountRequests": "Solicitud de descuento",
"Copylink": "Copiar Link",
"Disable": "Deshabilitar",
"Enable": "Habilitar",
"EnableSendEmail": "Habilitar el envío de Email",
"SendEmail": "Enviar Email",
"SuccessfullyDisabled": "Deshabilitado correctamente",
"SuccessfullyEnabled": "Habilitado correctamente",
"EmailSent": "Email enviado",
"SuccessfullySent": "Enviado correctamente",
"SuccessfullyDeleted": "Borrado correctamente",
"DiscountRequestDeletionWarningMessage": "La solicitud de descuento será borrada",
"BusinessType": "Tipo de negocio",
"TotalQuestionCount": "Número total de preguntas",
"RemainingQuestionCount": "Número de preguntas restantes",
"TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount debe ser mayor que RemainingQuestionCount !",
"QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount y RemainingQuestionCount debe ser cero o más grande que cero !",
"UnlimitedQuestionCount": "Número de preguntas ilimitadas",
"Notes": "Notas",
"Menu:Community": "Comunidad",
"Menu:Articles": "Artículos",
"Wait": "Esperar",
"Approve": "Aprobar",
"Reject": "Rechazar",
"Details": "Detalles",
"Url": "Url",
"Title": "Título",
"ContentSource": "Fuente de contenido",
"Status": "Estado",
"ReadArticle": "Leer artículo",
"ArticleHasBeenWaiting": "El artículo ha sido puesto en espera",
"ArticleHasBeenApproved": "El artículo ha sido aprobado",
"ArticleHasBeenRejected": "El artículo ha sido rechazado",
"Permission:Community": "Comunidad",
"Permission:CommunityArticle": "Artículo",
"Link": "Link",
"Enum:ContentSource:0": "Github",
"Enum:ContentSource:1": "Externo",
"Enum:Status:0": "En espera",
"Enum:Status:1": "Rechazado",
"Enum:Status:2": "Aprobado",
"Summary": "Resumen",
"AuthorName": "Nombre del autor",
"CoverImage": "Imagen de portada",
"RemoveCacheConfirmationMessage": "¿Estás seguro que quieres borrar la caché para \"{0}\" artículo?",
"SuccessfullyRemoved": "Borrada correctamente",
"RemoveCache": ""
}
}

5
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json

@ -156,6 +156,9 @@
"RemainingQuestionCount": "Kalan soru sayısı",
"TotalQuestionMustBeGreaterWarningMessage": "Toplam soru sayısı kalan soru sayısından büyük olmalıdır!",
"QuestionCountsMustBeGreaterThanZero": "Toplam soru sayısı ve kalan soru sayısı sıfır veya sıfırdan daha büyük olmalıdır!",
"UnlimitedQuestionCount": "Sınırsız soru sayısı"
"UnlimitedQuestionCount": "Sınırsız soru sayısı",
"Language": "Dil",
"Optional": "Opsiyonel",
"CreateArticleLanguageInfo": "Makalenin yazıldığı dil"
}
}

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

@ -27,6 +27,8 @@
"Blog": "Blog",
"Commercial": "Commercial",
"MyAccount": "My account",
"Permission:License": "License",
"Permission:UserInfo": "Usere info",
"SeeDocuments": "See Documents",
"Samples": "Samples"
}

56
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/es.json

@ -1,33 +1,33 @@
{
"culture": "es",
"texts": {
"Volo.AbpIo.Domain:010004": "",
"Volo.AbpIo.Domain:010005": "",
"Volo.AbpIo.Domain:010006": "",
"Volo.AbpIo.Domain:010007": "",
"Volo.AbpIo.Domain:010008": "",
"Volo.AbpIo.Domain:010009": "",
"Volo.AbpIo.Domain:010010": "",
"Volo.AbpIo.Domain:010011": "",
"Volo.AbpIo.Domain:010012": "",
"Volo.AbpIo.Domain:020001": "",
"Volo.AbpIo.Domain:020002": "",
"Volo.AbpIo.Domain:020003": "",
"Volo.AbpIo.Domain:020004": "",
"WantToLearn?": "",
"ReadyToGetStarted?": "",
"JoinOurCommunity": "",
"GetStartedUpper": "",
"ForkMeOnGitHub": "",
"Features": "",
"GetStarted": "",
"Documents": "",
"Community": "",
"ContributionGuide": "",
"Blog": "",
"Commercial": "",
"MyAccount": "",
"SeeDocuments": "",
"Samples": ""
"Volo.AbpIo.Domain:010004": "Número máximo de miembros alcanzado!",
"Volo.AbpIo.Domain:010005": "Número máximo de propietarios alcanzado!",
"Volo.AbpIo.Domain:010006": "Este usuario ya es un propietario de esta organización!",
"Volo.AbpIo.Domain:010007": "Este usuario ya es un desarrollador en este organización!",
"Volo.AbpIo.Domain:010008": "Número de desarrolladores permitido no puede ser menor que el número actual de desarrolladores!",
"Volo.AbpIo.Domain:010009": "El número de desarrolladores no puede ser menor que cero!",
"Volo.AbpIo.Domain:010010": "Número máximo de dirección mac excedido!",
"Volo.AbpIo.Domain:010011": "Una licencia personal no puede tener más de un desarrollador!",
"Volo.AbpIo.Domain:010012": "La licencia no puede ser extendida un mes despues de que expire!",
"Volo.AbpIo.Domain:020001": "Este paquete NPM no pudo ser borrado porque \"{NugetPackages}\" paquetes Nuget son dependientes de este paquete.",
"Volo.AbpIo.Domain:020002": "Este paquete NPM no pudo ser borrado porque \"{Modules}\" modulos están usando este paquete.",
"Volo.AbpIo.Domain:020003": "Este paquete NPM no pudo ser borrado porque \"{Modules}\" modulos están usando este paquete.y \"{NugetPackages}\" paquetes Nuget son dependientes de este paquete.",
"Volo.AbpIo.Domain:020004": "Este paquete Nuget no pudo ser borrado porque \"{Modules}\" modulos están usando este paquete.",
"WantToLearn?": "¿Quieres aprender?",
"ReadyToGetStarted?": "¿Preparado para comenzar?",
"JoinOurCommunity": "Unete a nuestra comunidad",
"GetStartedUpper": "COMENZAR",
"ForkMeOnGitHub": "Fork en GitHub",
"Features": "Características",
"GetStarted": "Comenzar",
"Documents": "Documentos",
"Community": "Comunidad",
"ContributionGuide": "Guia de contribución",
"Blog": "Blog",
"Commercial": "Comercial",
"MyAccount": "Mi cuenta",
"SeeDocuments": "Ver documentos",
"Samples": "Ejemplos"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json

@ -32,6 +32,8 @@
"MyProfile": "My profile",
"EmailNotValid": "Please enter a valid email address.",
"JoinOurMarketingNewsletter": "Join our marketing newsletter",
"WouldLikeToReceiveMarketingMaterials": "I would like to receive marketing materials like product deals & special offers."
"WouldLikeToReceiveMarketingMaterials": "I would like to receive marketing materials like product deals & special offers.",
"StartUsingYourLicenseNow": "Start using your license now!",
"WelcomePage": "Welcome Page"
}
}

56
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json

@ -1,35 +1,35 @@
{
"culture": "es",
"texts": {
"OrganizationManagement": "",
"OrganizationList": "",
"Volo.AbpIo.Commercial:010003": "",
"OrganizationNotFoundMessage": "",
"DeveloperCount": "",
"QuestionCount": "",
"Unlimited": "",
"Owners": "",
"AddMember": "",
"AddOwner": "",
"AddDeveloper": "",
"UserName": "",
"Name": "",
"EmailAddress": "",
"Developers": "",
"OrganizationManagement": "Gestión de la organización",
"OrganizationList": "Lista organización",
"Volo.AbpIo.Commercial:010003": "Tu no eres el propietario de esta organización!",
"OrganizationNotFoundMessage": "Organización no encontrada!",
"DeveloperCount": "Total desarrolladores asignados",
"QuestionCount": "Total de preguntas restantes",
"Unlimited": "Ilimitado",
"Owners": "Propietarios",
"AddMember": "Añadir miembro",
"AddOwner": "Añadir propietario",
"AddDeveloper": "Añadir desarrollador",
"UserName": "Nombre de usuario",
"Name": "Nombre",
"EmailAddress": "Dirección de Email",
"Developers": "Desarrolladores",
"LicenseType": "Tipo de licencia",
"Manage": "",
"StartDate": "",
"EndDate": "",
"Manage": "Gestionar",
"StartDate": "Fecha de inicio",
"EndDate": "Fecha de fin",
"Modules": "Módulos",
"LicenseExtendMessage": "",
"LicenseUpgradeMessage": "",
"LicenseAddDeveloperMessage": "",
"Volo.AbpIo.Commercial:010004": "",
"MyOrganizations": "",
"ApiKey": "",
"UserNameNotFound": "",
"SuccessfullyAddedToNewsletter": "",
"MyProfile": "",
"EmailNotValid": ""
"LicenseExtendMessage": "Tu fecha de finalización de tu licencia ha sido extendido a {0}",
"LicenseUpgradeMessage": "Tu licencia esta actualizada a {0}",
"LicenseAddDeveloperMessage": "{0} desarrolladores añadidos a tu licencia",
"Volo.AbpIo.Commercial:010004": "No se pudo encontrar el usuario especificado. El usuario debe estar ya registrado.",
"MyOrganizations": "Mis organizaciones",
"ApiKey": "API Key",
"UserNameNotFound": "No hay un usuario con el nombre de usuario {0}",
"SuccessfullyAddedToNewsletter": "Gracias por suscribirte a nuestro boletín de noticias!",
"MyProfile": "Mi perfil",
"EmailNotValid": "Por favor, introduce una dirección de email válida."
}
}

12
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/de-DE.json

@ -43,13 +43,13 @@
"GoToTheArticle": "Gehe zum Beitrag",
"Contribute": "Beitragen",
"OverallProgress": "Gesamtfortschritt",
"Done": "Ferig",
"Done": "Fertig",
"Open": "Offen",
"Closed": "Geschlossen",
"LatestQuestionOnThe": "Letzte Frage zum",
"Stackoverflow": "Stackoverflow",
"Votes": "Stimmen",
"Answer": "Antworten",
"Answer": "Antwort",
"Views": "Ansichten",
"Answered": "Beantwortet",
"WaitingForYourAnswer": "Warten auf Ihre Antwort",
@ -60,9 +60,9 @@
"QuestionItemErrorMessage": "Die neuesten Fragendetails konnten von Stackoverflow nicht abgerufen werden.",
"Oops": "Hoppla!",
"CreateArticleSuccessMessage": "Der Beitrag wurde erfolgreich eingereicht. Er wird nach einer Überprüfung durch den Site-Administrator veröffentlicht.",
"ChooseCoverImage": "Wählen Sie ein Titelbild ...",
"ChooseCoverImage": "Ein Titelbild auswählen...",
"CoverImage": "Titelbild",
"ShareYourExperiencesWithTheABPFramework": "Teilen Sie Ihre Erfahrungen mit dem ABP Framework!",
"ShareYourExperiencesWithTheABPFramework": "Ihre Erfahrungen mit dem ABP Framework teilen!",
"Optional": "Optional",
"UpdateUserWebSiteInfo": "Beispiel: https://johndoe.com",
"UpdateUserTwitterInfo": "Beispiel: johndoe",
@ -83,8 +83,8 @@
"LatestBlogPost": "Letzter Blog-Beitrag",
"Edit": "Bearbeiten",
"ProfileImageChange": "Ändern Sie das Profilbild",
"BlogItemErrorMessage": "Die neuesten Blogpost-Details konnten von ABP nicht abgerufen werden.",
"BlogItemErrorMessage": "Die neuesten Blogpost-Details konnten nicht abgerufen werden.",
"PlannedReleaseDate": "Geplantes Erscheinungsdatum",
"CommunityArticleRequestErrorMessage": "Die Anfrage nach den neuesten Beiträgen von Github konnte nicht abgerufen werden."
}
}
}

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

@ -99,6 +99,8 @@
"Marketing": "Marketing",
"CommunityPrivacyPolicyConfirmation": "I agree to the Terms & Conditions and <a href=\"https://commercial.abp.io/Privacy\">Privacy Policy</a>.",
"ArticleRequestMessageTitle": "<a href=\"https://github.com/abpframework/abp/issues/new\">Open an issue</a> on the GitHub to request an article/tutorial you want to see on this web site.",
"ArticleRequestMessageBody": "Here, the list of the requested articles by the community. Do you want to write a requested article? Please click to the request and join to the discussion."
"ArticleRequestMessageBody": "Here, the list of the requested articles by the community. Do you want to write a requested article? Please click to the request and join to the discussion.",
"Language": "Language",
"CreateArticleLanguageInfo": "The language in which the article is written"
}
}

166
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/es.json

@ -1,90 +1,90 @@
{
"culture": "es",
"texts": {
"Permission:CommunityArticle": "",
"Permission:Edit": "",
"Waiting": "",
"Approved": "",
"Rejected": "",
"Wait": "",
"Approve": "",
"Reject": "",
"ReadArticle": "",
"Status": "",
"ContentSource": "",
"Details": "",
"Url": "",
"Title": "",
"CreationTime": "",
"Permission:CommunityArticle": "Artículo de comunidad",
"Permission:Edit": "Editar",
"Waiting": "Esperando",
"Approved": "Aprobado",
"Rejected": "Rechazado",
"Wait": "Esperar",
"Approve": "Aprobar",
"Reject": "Rechazar",
"ReadArticle": "Leer artículo",
"Status": "Estado",
"ContentSource": "Fuente de contenido",
"Details": "Detalles",
"Url": "Url",
"Title": "Título",
"CreationTime": "Fecha de creación",
"Save": "Guardar",
"SameUrlAlreadyExist": "",
"UrlIsNotValid": "",
"UrlNotFound": "",
"UrlContentNotFound": "",
"Summary": "",
"MostRead": "",
"Latest": "",
"ContributeAbpCommunity": "",
"SubmitYourArticle": "",
"ContributionGuide": "",
"BugReport": "",
"SeeAllArticles": "",
"WelcomeToABPCommunity!": "",
"MyProfile": "",
"MyOrganizations": "",
"EmailNotValid": "",
"FeatureRequest": "",
"CreateArticleTitleInfo": "",
"CreateArticleUrlInfo": "",
"CreateArticleSummaryInfo": "",
"CreateArticleCoverInfo": "",
"ThisExtensionIsNotAllowed": "",
"TheFileIsTooLarge": "",
"GoToTheArticle": "",
"Contribute": "",
"OverallProgress": "",
"Done": "",
"Open": "",
"Closed": "",
"LatestQuestionOnThe": "",
"Stackoverflow": "",
"Votes": "",
"Answer": "",
"Views": "",
"Answered": "",
"WaitingForYourAnswer": "",
"Asked": "",
"AllQuestions": "",
"NextVersion": "",
"MilestoneErrorMessage": "",
"QuestionItemErrorMessage": "",
"Oops": "",
"CreateArticleSuccessMessage": "",
"ChooseCoverImage": "",
"CoverImage": "",
"ShareYourExperiencesWithTheABPFramework": "",
"Optional": "",
"UpdateUserWebSiteInfo": "",
"UpdateUserTwitterInfo": "",
"UpdateUserGithubInfo": "",
"UpdateUserLinkedinInfo": "",
"UpdateUserCompanyInfo": "",
"UpdateUserJobTitleInfo": "",
"UserName": "",
"Company": "",
"PersonalWebsite": "",
"RegistrationDate": "",
"Social": "",
"Biography": "",
"HasNoPublishedArticlesYet": "",
"Author": "",
"LatestGithubAnnouncements": "",
"SeeAllAnnouncements": "",
"SameUrlAlreadyExist": "La url ya existe si tu quieres añadir este artículo, tu debes cambiar la url!",
"UrlIsNotValid": "Url no es valida",
"UrlNotFound": "Url no encontrada",
"UrlContentNotFound": "Contenido de la Url no encontrado",
"Summary": "Resumen",
"MostRead": "Más leído",
"Latest": "Últimos",
"ContributeAbpCommunity": "Contribuye a la comunidad ABP",
"SubmitYourArticle": "Envía tu artículo",
"ContributionGuide": "Guía de contribución",
"BugReport": "Informe de errores",
"SeeAllArticles": "Ver todos los artículos",
"WelcomeToABPCommunity!": "Bienvenido a la comunidad ABP",
"MyProfile": "Mi perfil",
"MyOrganizations": "Mis organizaciones",
"EmailNotValid": "Por favor entra una dirección de email válida.",
"FeatureRequest": "Solucitud de característica",
"CreateArticleTitleInfo": "Título del artículo para ser mostrado en la lista de artículos.",
"CreateArticleUrlInfo": "Url original del artículo GitHub/ Externo",
"CreateArticleSummaryInfo": "Un pequeño resumen del artículo para ser mostrado en la lista de artículos.",
"CreateArticleCoverInfo": "Para crear un artículo eficaz, agregue una foto de portada. Cargue imágenes con una relación de aspecto de 16: 9 para obtener la mejor vista.",
"ThisExtensionIsNotAllowed": "Esta extensión no está permitida.",
"TheFileIsTooLarge": "El fichero es demasiado grande.",
"GoToTheArticle": "Ir a el artículo",
"Contribute": "Contribuir",
"OverallProgress": "Progreso general",
"Done": "Hecho",
"Open": "Abrir",
"Closed": "Cerrado",
"LatestQuestionOnThe": "Última pregunta en la",
"Stackoverflow": "Stackoverflow",
"Votes": "Votos",
"Answer": "Respuesta",
"Views": "Vistas",
"Answered": "Respondido",
"WaitingForYourAnswer": "Esperando tu respuesta",
"Asked": "Preguntado",
"AllQuestions": "Todas las preguntas",
"NextVersion": "Siguiente versión",
"MilestoneErrorMessage": "No se pudieron obtener los detalles del hito actual en Github.",
"QuestionItemErrorMessage": "no se pudieron obtener los detalles de pregunta actual en Stackoverflow.",
"Oops": "Oops!",
"CreateArticleSuccessMessage": "El artículo se ha enviado correctamente. Se publicará después de una revisión del administrador del sitio.",
"ChooseCoverImage": "Elige una imagen de portada...",
"CoverImage": "Imagen de portada",
"ShareYourExperiencesWithTheABPFramework": "Comparte tus experiencias con el ABP Framework!",
"Optional": "Opcional",
"UpdateUserWebSiteInfo": "Ejemplo: https://johndoe.com",
"UpdateUserTwitterInfo": "Ejemplo: johndoe",
"UpdateUserGithubInfo": "Ejemplo: johndoe",
"UpdateUserLinkedinInfo": "Ejemplo: https://www.linkedin.com/...",
"UpdateUserCompanyInfo": "Ejemplo: Volosoft",
"UpdateUserJobTitleInfo": "Ejemplo: desarrollador de software",
"UserName": "Nombre de usuario",
"Company": "Empresa",
"PersonalWebsite": "Sitio web personal",
"RegistrationDate": "Fecha de registro",
"Social": "Social",
"Biography": "Biografía",
"HasNoPublishedArticlesYet": "No has publicado articules todavía",
"Author": "Autor",
"LatestGithubAnnouncements": "Últimas notificaciones de Github",
"SeeAllAnnouncements": "Ver todos las notificaciones",
"LatestBlogPost": "",
"Edit": "",
"ProfileImageChange": "",
"BlogItemErrorMessage": "",
"PlannedReleaseDate": "",
"CommunityArticleRequestErrorMessage": ""
"Edit": "Editar",
"ProfileImageChange": "Cambiar la imagen de perfil",
"BlogItemErrorMessage": "No se pudo obtener los detalles del último blog desde ABP.",
"PlannedReleaseDate": "Fecha de entrega planificada",
"CommunityArticleRequestErrorMessage": "No se pudo obtener la última petición de artículo desde Github"
}
}

10
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -184,6 +184,14 @@
"ABPCLIExamplesInfo": "<strong>new</strong> command creates a <strong>layered MVC application</strong> with <strong>Entity Framework Core</strong> as the database provider. However, it has additional options. Examples:",
"SeeCliDocumentForMoreInformation": "See the <a href=\"{0}\">ABP CLI document</a> for more options or select the \"Direct Download\" tab above.",
"Optional": "Optional",
"LocalFrameworkRef": "Keep local project reference for the framework packages."
"LocalFrameworkRef": "Keep local project reference for the framework packages.",
"BlobStoring": "BLOB Storing",
"BlobStoringExplanation": "BLOB Storing system provides an abstraction to work with BLOBs. ABP provides some pre-built storage provider integrations (Azure, AWS, File System, Database, etc.) that you can easily use in your applications.",
"TextTemplating": "Text Templating",
"TextTemplatingExplanation": "Text templating is used to dynamically render contents based on a template and a model (a data object). For example, you can use it to create dynamic email contents with a pre-built template.",
"MultipleUIOptions": "Multiple UI Options",
"MultipleDBOptions": "Multiple Database Providers",
"MultipleUIOptionsExplanation": "The core framework is designed as UI independent and can work with any type of UI system, while there are multiple pre-built and integrated options are provided out of the box.",
"MultipleDBOptionsExplanation": "The framework can work with any data source, while the following providers are officially developed and supported;"
}
}

362
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json

@ -1,189 +1,189 @@
{
"culture": "es",
"texts": {
"GetStarted": "",
"Create": "",
"NewProject": "",
"DirectDownload": "",
"ProjectName": "",
"ProjectType": "",
"DatabaseProvider": "",
"NTier": "",
"IncludeUserInterface": "",
"CreateNow": "",
"TheStartupProject": "",
"Tutorial": "",
"UsingCLI": "",
"SeeDetails": "",
"AbpShortDescription": "",
"SourceCodeUpper": "",
"LatestReleaseLogs": "",
"Infrastructure": "",
"Architecture": "",
"Modular": "",
"DontRepeatYourself": "",
"DeveloperFocused": "",
"FullStackApplicationInfrastructure": "",
"GetStarted": "Comencemos - Plantillas de inicio",
"Create": "Crear",
"NewProject": "Nuevo proyecto",
"DirectDownload": "Descarga directa",
"ProjectName": "Nombre de proyecto",
"ProjectType": "Tipo de proyecto",
"DatabaseProvider": "Proveedor de base de datos",
"NTier": "N-Capas",
"IncludeUserInterface": "Incluir interface de usuario",
"CreateNow": "Crear ahora",
"TheStartupProject": "El proyecto de inicio",
"Tutorial": "Tutorial",
"UsingCLI": "Usando CLI",
"SeeDetails": "Ver detalles",
"AbpShortDescription": "ABP Framework es una completa infraestructure para crear modernas aplicaciones web que sigue las mejores prácticas y convenciones de desarrollo de software.",
"SourceCodeUpper": "CÓDIGO FUENTE",
"LatestReleaseLogs": "Últimos registros de lanzamiento",
"Infrastructure": "infraestructura",
"Architecture": "Arquitectura",
"Modular": "Modular",
"DontRepeatYourself": "No te repitas tu mismo",
"DeveloperFocused": "Pensado para el desarrollador",
"FullStackApplicationInfrastructure": "infraestructura de aplicación Full stack",
"DomainDrivenDesign": "",
"DomainDrivenDesignExplanation": "",
"Authorization": "",
"AuthorizationExplanation": "",
"MultiTenancy": "",
"MultiTenancyExplanationShort": "",
"CrossCuttingConcerns": "",
"CrossCuttingConcernsExplanationShort": "",
"BuiltInBundlingMinification": "",
"BuiltInBundlingMinificationExplanation": "",
"VirtualFileSystem": "",
"VirtualFileSystemExplanation": "",
"Theming": "",
"ThemingExplanationShort": "",
"BootstrapTagHelpersDynamicForms": "",
"DomainDrivenDesignExplanation": "Diseñado y desarrollado basandose en los patrones y principios de DDD. Promorciona una capa de modelo para tus aplicaciones.",
"Authorization": "Autorización",
"AuthorizationExplanation": "Sistema de autorización avanzado con usuarios, roles y permisos de granularidad-fina. Construido sobre la librería Microsoft Identity.",
"MultiTenancy": "Multi-inquilino",
"MultiTenancyExplanationShort": "Aplicaciones SaaS sencillas!, integradas con multi-inquilino desde la base de datos hasta la UI.",
"CrossCuttingConcerns": "Requerimientos comunes",
"CrossCuttingConcernsExplanationShort": "Completa infraestructura de autorización, validación, manejo de excepciones, caching, auditoría, gestión de transacciones y más.",
"BuiltInBundlingMinification": "Empaquetado y minificado integrado",
"BuiltInBundlingMinificationExplanation": "No se necesitan usar herramientas externas para el empaquetado y minificado. ABP ofrece un camino simple, dinámico, potente, modular e integrado.",
"VirtualFileSystem": "Sistema de ficheros virtual",
"VirtualFileSystemExplanation": "Incrusta vistas, scripts, estilos, imágenes en paquetes/ librerias y reusalos en diferentes aplicaciones.",
"Theming": "Temas",
"ThemingExplanationShort": "Usa y personaliza la UI estandar basada en bootstrap o crea tu propio tema.",
"BootstrapTagHelpersDynamicForms": "Boostrap Tag Helpers y formularios dinámicos",
"BootstrapTagHelpersDynamicFormsExplanation": "",
"HTTPAPIsDynamicProxies": "",
"HTTPAPIsDynamicProxiesExplanation": "",
"CompleteArchitectureInfo": "",
"DomainDrivenDesignBasedLayeringModelExplanation": "",
"DomainDrivenDesignBasedLayeringModelExplanationCont": "",
"MicroserviceCompatibleModelExplanation": "",
"MicroserviceCompatibleModelExplanationCont": "",
"ModularInfo": "",
"PreBuiltModulesThemes": "",
"PreBuiltModulesThemesExplanation": "",
"NuGetNPMPackages": "",
"NuGetNPMPackagesExplanation": "",
"ExtensibleReplaceable": "",
"ExtensibleReplaceableExplanation": "",
"CrossCuttingConcernsExplanation2": "",
"CrossCuttingConcernsExplanation3": "",
"AuthenticationAuthorization": "",
"ExceptionHandling": "",
"Validation": "",
"DatabaseConnection": "",
"TransactionManagement": "",
"AuditLogging": "",
"Caching": "",
"Multitenancy": "",
"DataFiltering": "",
"ConventionOverConfiguration": "",
"ConventionOverConfigurationExplanation": "",
"ConventionOverConfigurationExplanationList1": "",
"ConventionOverConfigurationExplanationList2": "",
"ConventionOverConfigurationExplanationList3": "",
"ConventionOverConfigurationExplanationList4": "",
"ConventionOverConfigurationExplanationList5": "",
"ConventionOverConfigurationExplanationList6": "",
"BaseClasses": "",
"BaseClassesExplanation": "",
"DeveloperFocusedExplanation": "",
"DeveloperFocusedExplanationCont": "",
"SeeAllFeatures": "",
"CLI_CommandLineInterface": "",
"CLI_CommandLineInterfaceExplanation": "",
"StartupTemplates": "",
"StartupTemplatesExplanation": "",
"BasedOnFamiliarTools": "",
"BasedOnFamiliarToolsExplanation": "",
"ORMIndependent": "",
"ORMIndependentExplanation": "",
"Features": "",
"ABPCLI": "",
"Modularity": "",
"BootstrapTagHelpers": "",
"DynamicForms": "",
"BundlingMinification": "",
"HTTPAPIsDynamicProxies": "HTTP APIs y proxies dinámicos",
"HTTPAPIsDynamicProxiesExplanation": "Expon servicios de aplicación como REST HTPP APIs y consumelos dinámicamente desde proxies JavaScript y C#.",
"CompleteArchitectureInfo": "Arquitectura moderna para crear soluciones software sostenibles.",
"DomainDrivenDesignBasedLayeringModelExplanation": "Ayuda a implementar ",
"DomainDrivenDesignBasedLayeringModelExplanationCont": "Promorciona plantillas de inicio, clases base, servicios, documentación y guías de ayuda para desarrollar tu aplicación basada en patrones y principios DDD.",
"MicroserviceCompatibleModelExplanation": "El núcleo del framework y los modules pre-construidos están diseñados con una arquitectura microservicios en mente.",
"MicroserviceCompatibleModelExplanationCont": "Proporciona la infraestructura, integraciones, ejemplos y documentación para implementar soluciones microservicios más facilmente, mientras no trae complejidad adicional si quieres una aplicación monolítica.",
"ModularInfo": "ABP proporciona un sistema de modulos que te permite desarrollar modulos de aplicacion resusables. Conectado en el ciclo de eventos de aplicación y mínimas dependencias entre las partes principales de tu sistema.",
"PreBuiltModulesThemes": "Modulos y temas integrados",
"PreBuiltModulesThemesExplanation": "Módulos y temas de código abierto y comerciales preparados para usar en tu aplicación de negocio.",
"NuGetNPMPackages": "Paquetes Nuget y NPM",
"NuGetNPMPackagesExplanation": "Paquetes distribuidos a través de Nuget y NPM para instalar y actualizar.",
"ExtensibleReplaceable": "Extensible/ Reemplazable",
"ExtensibleReplaceableExplanation": "Todos los serivios y módulos están diseñados para ser extensibles. Tu puedes reemplazar servicios, páginas, estilos y componentes.",
"CrossCuttingConcernsExplanation2": "Mantiene tu código base más pequeño y te permite enforcate en el código que es especifico en tu negocio.",
"CrossCuttingConcernsExplanation3": "No pierdas tiempo implementando requerimientos comunes de aplicación en múltiples proyectos.",
"AuthenticationAuthorization": "Autenticación y autorización",
"ExceptionHandling": "Manejo de excepciones.",
"Validation": "Validación",
"DatabaseConnection": "Conexión a la base de datos",
"TransactionManagement": "Gestión de transacciones",
"AuditLogging": "Registro de auditoría",
"Caching": "Caché",
"Multitenancy": "Multi-inquilino",
"DataFiltering": "Filtrado de datos",
"ConventionOverConfiguration": "Convención sobre la configuración",
"ConventionOverConfigurationExplanation": "ABP implementa comunes convenciones de aplicación por defecto con una mínima o nula configuración.",
"ConventionOverConfigurationExplanationList1": "Los registradores conocen los servicios para la inyección de dependencias.",
"ConventionOverConfigurationExplanationList2": "Expone servicios de aplicación como APIs HTTP mediante convenciones de nomenclatura.",
"ConventionOverConfigurationExplanationList3": "Crea clientes proxies HTTP dinámicos para C# y JavaScript.",
"ConventionOverConfigurationExplanationList4": "Proporciona repositorios por defecto para tus entidades.",
"ConventionOverConfigurationExplanationList5": "Maneja Unit of Work por cada petición web o metodo de servicio de aplicación.",
"ConventionOverConfigurationExplanationList6": "Publica, crea, actualiza y borra eventos para tus entidades.",
"BaseClasses": "Clase base",
"BaseClassesExplanation": "Clases base integradas para patrones comunes de aplicación.",
"DeveloperFocusedExplanation": "ABP es para desarrolladores.",
"DeveloperFocusedExplanationCont": "Su objetivo es simplificar su desarrollo diario de software sin restringirle la escritura de código de bajo nivel.",
"SeeAllFeatures": "Ver todas las características",
"CLI_CommandLineInterface": "CLI (Command Line Interface)",
"CLI_CommandLineInterfaceExplanation": "Incluye un CLI que te ayuda a automatizar la creación de nuevos proyectos y añadir nuevos módulos.",
"StartupTemplates": "Plantillas de inicio",
"StartupTemplatesExplanation": "Varias plantillas de inicio proporcionan una solución completamente configurada lista para comenzar el desarrollo.",
"BasedOnFamiliarTools": "Basado en herramientas familiares",
"BasedOnFamiliarToolsExplanation": "Construido y diseñado con herramientas populares que tu ya conoces. Baja curva de aprendizaje, facil adaptación y desarrollo confortable.",
"ORMIndependent": "Independiente del ORM",
"ORMIndependentExplanation": "El núcleo del core es independiente del ORM/ base de datos y puedes trabajar con cualquier fuente de datos. Entity Framework Core y MongoDB están actualmente disponibles.",
"Features": "Explora las características de ABP Framework",
"ABPCLI": "ABP CLI",
"Modularity": "Modularidad",
"BootstrapTagHelpers": "Bootstrap Tag Helpers",
"DynamicForms": "Formularios dinámicos",
"BundlingMinification": "Empaquetado y Minimificación",
"BackgroundJobs": "",
"BackgroundJobsExplanation": "",
"DDDInfrastructure": "",
"DomainDrivenDesignInfrastructure": "",
"AutoRESTAPIs": "",
"DynamicClientProxies": "",
"DistributedEventBus": "",
"DistributedEventBusWithRabbitMQIntegration": "",
"TestInfrastructure": "",
"AuditLoggingEntityHistories": "",
"ObjectToObjectMapping": "",
"ObjectToObjectMappingExplanation": "",
"EmailSMSAbstractions": "",
"EmailSMSAbstractionsWithTemplatingSupport": "",
"Localization": "",
"SettingManagement": "",
"ExtensionMethods": "",
"ExtensionMethodsHelpers": "",
"AspectOrientedProgramming": "",
"DependencyInjection": "",
"DependencyInjectionByConventions": "",
"ABPCLIExplanation": "",
"ModularityExplanation": "",
"MultiTenancyExplanation": "",
"MultiTenancyExplanation2": "",
"MultiTenancyExplanation3": "",
"MultiTenancyExplanation4": "",
"BootstrapTagHelpersExplanation": "",
"DynamicFormsExplanation": "",
"AuthenticationAuthorizationExplanation": "",
"CrossCuttingConcernsExplanation": "",
"DatabaseConnectionTransactionManagement": "",
"CorrelationIdTracking": "",
"BundlingMinificationExplanation": "",
"VirtualFileSystemnExplanation": "",
"ThemingExplanation": "",
"DomainDrivenDesignInfrastructureExplanation": "",
"Specification": "",
"Repository": "",
"DomainService": "",
"ValueObject": "",
"ApplicationService": "",
"DataTransferObject": "",
"AggregateRootEntity": "",
"AutoRESTAPIsExplanation": "",
"DynamicClientProxiesExplanation": "",
"DistributedEventBusWithRabbitMQIntegrationExplanation": "",
"TestInfrastructureExplanation": "",
"AuditLoggingEntityHistoriesExplanation": "",
"EmailSMSAbstractionsWithTemplatingSupportExplanation": "",
"LocalizationExplanation": "",
"SettingManagementExplanation": "",
"ExtensionMethodsHelpersExplanation": "",
"AspectOrientedProgrammingExplanation": "",
"DependencyInjectionByConventionsExplanation": "",
"DataFilteringExplanation": "",
"PublishEvents": "",
"HandleEvents": "",
"AndMore": "",
"Code": "",
"Result": "",
"SeeTheDocumentForMoreInformation": "",
"IndexPageHeroSection": "",
"UiFramework": "",
"EmailAddress": "",
"Mobile": "",
"ReactNative": "",
"Strong": "",
"Complete": "",
"BasedLayeringModel": "",
"Microservice": "",
"Compatible": "",
"MeeTTheABPCommunityInfo": "",
"JoinTheABPCommunityInfo": "",
"AllArticles": "",
"SubmitYourArticle": "",
"DynamicClientProxyDocument": "",
"EmailSMSAbstractionsDocument": "",
"CreateProjectWizard": "",
"TieredOption": "",
"SeparateIdentityServerOption": "",
"UseslatestPreVersion": "",
"ReadTheDocumentation": "",
"Documentation": "",
"GettingStartedTutorial": "",
"ApplicationDevelopmentTutorial": "",
"TheStartupTemplate": "",
"InstallABPCLIInfo": "",
"DifferentLevelOfNamespaces": "",
"ABPCLIExamplesInfo": "",
"SeeCliDocumentForMoreInformation": "",
"Optional": "",
"LocalFrameworkRef": ""
"BackgroundJobsExplanation": "Defina clases simples para ejecutar trabajos en segundo plano como en cola. Utilice el administrador de trabajos integrado o integre el suyo propio. <a href=\"{0}\">Hangfire</a> & <a href=\"{1}\">RabbitMQ</a> integraciones están actualmente disponibles.",
"DDDInfrastructure": "infraestructura DSS",
"DomainDrivenDesignInfrastructure": "infraestructura Domain Driven Design",
"AutoRESTAPIs": "Auto REST APIs",
"DynamicClientProxies": "Clientes proxies dinámicos",
"DistributedEventBus": "Bus de eventos distribuido",
"DistributedEventBusWithRabbitMQIntegration": "Bus de eventos distribuido con la integración RabbitMQ ",
"TestInfrastructure": "infraestructura de Test",
"AuditLoggingEntityHistories": "Registro de auditoría y historial de entidades",
"ObjectToObjectMapping": "Mapeado objeto a objeto",
"ObjectToObjectMappingExplanation": "<a href=\"{0}\">Mapeado objeto a objeto</a> abstracción integrada con AutoMapper.",
"EmailSMSAbstractions": "Abstracciones para Email y SMS",
"EmailSMSAbstractionsWithTemplatingSupport": "Abstracciones para Email y SMS con soporte para plantillas",
"Localization": "Localización",
"SettingManagement": "Gestión de la configuración",
"ExtensionMethods": "Extension Methods",
"ExtensionMethodsHelpers": "Extension Methods & Helpers",
"AspectOrientedProgramming": "Programación orientada a aspectos",
"DependencyInjection": "Inyección de dependencias",
"DependencyInjectionByConventions": "Inyección de dependencias por convenciones",
"ABPCLIExplanation": "ABP CLI (Command Line Interface) es una herramienta en línea de comandos que proporciona ciertas tareas comunes para soluciones basadas en ABP.",
"ModularityExplanation": "ABP proporciona una infraestructura completa para construir tu propios módulos de aplicación que pueden contener entidades, servicios, base de datos integradas, APIs, componentes de UI y otros...",
"MultiTenancyExplanation": "ABP framework no sólo soporta el desarrollo de aplicaciones multi-inquilino, si no también hace que tu código no tenga que preocuparse de multi-inquilino.",
"MultiTenancyExplanation2": "Puedes determinar automaticamente el inquilino actual, y aislar los datos de diferentes inquilinos entre sí. ",
"MultiTenancyExplanation3": "Soporta base de datos única, base de datos por inquilino y enfoques híbridos.",
"MultiTenancyExplanation4": "Enfocate en tu código y deja al framework que maneje multi-inquilino en tu nombre.",
"BootstrapTagHelpersExplanation": "En vez de escribir repetidamente los detalle de componentes bootstrap, usa ABP's tag helpers para simplificarlo y obtener ventaja de intellisence. Tu puedes definitivamente usar Bootstrp cuando tu lo necesites.",
"DynamicFormsExplanation": "Los formularios dinámicos y tag helpers de entrada pueden crear el formulario completo a partir de una clase de C# como modelo. ",
"AuthenticationAuthorizationExplanation": "Autenticación enriquecidad y opciones de autorización integradas con ASP.NET Core Identity & IdentityServer4. Proporciona un extensible y detallado sistema de permisos.",
"CrossCuttingConcernsExplanation": "No te repitas tu mismo al implementar todas esas cosas comunes una y otra vez. Enfocate en tu negocio y permite a ABP automatizarlos por convención.",
"DatabaseConnectionTransactionManagement": "Conexión de base de datos y gestión de transacciones",
"CorrelationIdTracking": "Correlation-Id de seguimiento",
"BundlingMinificationExplanation": "ABP ofrece un sistema simple, dinámico, potente, modular y empaquetado & minificación ",
"VirtualFileSystemnExplanation": "El sistema de archivos virtuales permite administrar archivos que no existen físicamente en el sistema de archivos (disco). Se utiliza principalmente para incrustar (js, css, image, cshtml...) en ensamblados y utilizarlos como archivos físicos en tiempo de ejecución.",
"ThemingExplanation": "El sistema de temas permite desarrollar el tema de su aplicación y módulos independiente mediante la definición de un conjunto de bibliotecas y diseños base comunes, basado en el último marco de Trabajo bootstrap.",
"DomainDrivenDesignInfrastructureExplanation": "Una infraestructura completa para crear aplicaciones en capas basadas en los patrones y principios DDD",
"Specification": "Especificación",
"Repository": "Repositorios",
"DomainService": "Servicio de dominio",
"ValueObject": "Objetos de valor",
"ApplicationService": "Servicio de aplicación",
"DataTransferObject": "Objetos de transferencia de datos",
"AggregateRootEntity": "Agregado ráiz, Entidad",
"AutoRESTAPIsExplanation": "ABP puede configurar automáticamente los servicios de aplicación como controladores de API por convención.",
"DynamicClientProxiesExplanation": "Consuma fácilmente sus API de clientes de JavaScript y C#",
"DistributedEventBusWithRabbitMQIntegrationExplanation": "Publique y consuma eventos distribuidos fácilmente mediante el bus de eventos distribuido integrado con la integración RabbitMQ disponible.",
"TestInfrastructureExplanation": "El framework se ha desarrollado unitariamente y pruebas de integración. Proporciona clases base para que sea más fácil. Las plantillas de inicio vienen preconfiguradas para las pruebas.",
"AuditLoggingEntityHistoriesExplanation": "Registro de auditoría integrado para aplicaciones críticas para el negocio. Registro de auditorías de solicitud, servicio, nivel de método e historiales de entidades con detalles de nivel de propiedad.",
"EmailSMSAbstractionsWithTemplatingSupportExplanation": "Las abstracciones IEmailSender e ISmsSender desacoplan la lógica de la aplicación de la infraestructura. El sistema avanzado de plantillas de correo electrónico permite crear y localizar plantillas de correo electrónico y utilizarlas fácilmente cuando sea necesario.",
"LocalizationExplanation": "El sistema de localización permite crear recursos en archivos JSON sin formato y utilizarlos para localizar la interfaz de usuario. Admite escenarios avanzados como herencia, extensiones e integración de JavaScript, mientras que es totalmente compatible con el sistema de localización de AspNet Core.",
"SettingManagementExplanation": "Defina la configuración de la aplicación y obtenga valores en tiempo de ejecución en función de la configuración actual, el inquilino y el usuario.",
"ExtensionMethodsHelpersExplanation": "No te repitas ni siquiera para partes de código triviales. Extensiones y ayudantes para tipos estándar hace que su código sea mucho más limpio y fácil de escribir.",
"AspectOrientedProgrammingExplanation": "Proporciona una infraestructura cómoda para crear servidores proxy dinámicos e implementar la programación orientada a aspectos. Interceptar cualquier clase y ejecutar el código antes y después de cada ejecución del método.",
"DependencyInjectionByConventionsExplanation": "No es necesario registrar las clases en la inserción de dependencias manualmente. Registra automáticamente los tipos de servicio comunes por convención. Para otro tipo de servicios, puede utilizar interfaces y atributos para que sea más fácil y in situ.",
"DataFilteringExplanation": "Definir y usar filtros de datos que se aplican automáticamente al consultar entidades desde la base de datos. Los filtros Soft Delete & MultiTenant se proporcionan de fábrica cuando se implementan interfaces sencillas.",
"PublishEvents": "Publicar eventos",
"HandleEvents": "Manejar eventos",
"AndMore": "y más...",
"Code": "Código",
"Result": "Resultado",
"SeeTheDocumentForMoreInformation": "Ver el <a href=\"{1}\">{0} documento</a> para más información.",
"IndexPageHeroSection": "<span class=\"first-line shine\"><strong>Código fuente abierto</strong></span><span class=\"second-line text-uppercase\">Web Application<br />Framework </span><span class=\"third-line shine2\"><strong>para asp.net core</strong></span>",
"UiFramework": "UI Framework",
"EmailAddress": "Dirección de correo",
"Mobile": "Móvil",
"ReactNative": "React Nativo",
"Strong": "Fuerte",
"Complete": "Completar",
"BasedLayeringModel": "Módelo basado en capas",
"Microservice": "Microservicio",
"Compatible": "Compatible",
"MeeTTheABPCommunityInfo": "Nuestra misión es crear un entorno en el que los desarrolladores puedan ayudarse entre sí con artículos, tutoriales, estudios de casos, etc. y conocer personas de ideas afines.",
"JoinTheABPCommunityInfo": "¡Participe en una comunidad vibrante y conviértase en colaborador del Marco ABP!",
"AllArticles": "Todos los artículos",
"SubmitYourArticle": "Envía tu artículo",
"DynamicClientProxyDocument": "Ver la documentación del cliente proxy dinámico para <a href=\"{0}\">JavaScript</a> & <a href=\"{1}\">C#</a>.",
"EmailSMSAbstractionsDocument": "Ver los documentos de <a href=\"{0}\">emailing</a> y <a href=\"{1}\">envío SMS </a> para más información.",
"CreateProjectWizard": "Este asistente crea un nuevo proyecto a partir de la plantilla de inicio que está correctamente configurado para comenzar con su proyecto.",
"TieredOption": "Crea una solución por niveles en la que las capas de API Web y HTTP están separadas físicamente. Si no se marca, crea una solución en capas que es menos compleja y adecuada para la mayoría de los escenarios.",
"SeparateIdentityServerOption": "Separa el lado del servidor en dos aplicaciones: la primera es para el servidor de identidad y la segunda es para la API HTTP del lado del servidor.",
"UseslatestPreVersion": "Usar la última versión pre-release",
"ReadTheDocumentation": "<span class=\"text-primary\">Leer</span><span class=\"text-success\">La Documentación</span>",
"Documentation": "Documentación",
"GettingStartedTutorial": "Tutorial para iniciarse",
"ApplicationDevelopmentTutorial": "Tutorial de desarrollo de aplicación",
"TheStartupTemplate": "La Plantilla de Inicio",
"InstallABPCLIInfo": "ABP CLI es la forma más rápida de iniciar una nueva solución con el marco ABP. Instale la CLI de ABP mediante una ventana de línea de comandos:",
"DifferentLevelOfNamespaces": "Tu puedes usar diferentes niveles de espacio de nombres; ej. BookStore, Acme.BookStore or Acme.Retail.BookStore.",
"ABPCLIExamplesInfo": "<strong>nuevo</strong> comando crea una <strong>aplicación MVC por capas</strong> con <strong>Entity Framework Core</strong> como proveedor de base de datos. Sin embargo, tiene distintas opciones. Ejemplos:",
"SeeCliDocumentForMoreInformation": "Ver el <a href=\"{0}\">documento ABP CLI </a> para más opciones o selecciona la \"Direct Download\" pestaña de arriba.",
"Optional": "Opcional",
"LocalFrameworkRef": "Mantén la referencia al proyecto local para los paquetes del framework."
}
}

9
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json

@ -184,6 +184,13 @@
"ABPCLIExamplesInfo": "<strong>new</strong>命令创建一个 <strong>分层的MVC应用程序</strong> 使用 <strong>Entity Framework Core</strong> 做为数据库提供程序. 它还有其他选项. 示例:",
"SeeCliDocumentForMoreInformation": "参阅 <a href=\"{0}\">ABP CLI 文档</a> 获得更多选项或选择上方的 \"直接下载\" 标签.",
"Optional": "可选的",
"LocalFrameworkRef": "保留框架包的本地项目引用."
"LocalFrameworkRef": "保留框架包的本地项目引用.",
"BlobStoring": "BLOB存储",
"BlobStoringExplanation": "BLOB存储系统提供了BLOB的抽象. ABP提供了一些预构建的存储提供程序集成(Azure,AWS,文件系统,数据库等),你可以轻松的在你的应用程序中使用它们.",
"TextTemplating": "文本模板",
"TextTemplatingExplanation": "文本模板是基于模板和模型(数据对象)使用动态渲染内容. 例如你可以使用预构建的模板来创建动态的电子邮件内容.",
"MultipleUIOptions": "多个UI选项",
"MultipleDBOptions": "多个数据库提供程序",
"MultipleUIOptionsExplanation": "核心框架设计为独立与UI,可以和任何类型的UI系统一起使用. 同时提供了多个开箱即用的预构建集成选项."
}
}

1
common.DotSettings

@ -29,6 +29,7 @@
<s:String x:Key="/Default/Environment/InlayHints/CSharpTypeNameHintsOptions/ShowTypeNameHintsForLambdaExpressionParameters/@EntryValue">Never</s:String>
<s:String x:Key="/Default/Environment/InlayHints/CSharpTypeNameHintsOptions/ShowTypeNameHintsForLinqQueryRangeVariables/@EntryValue">Never</s:String>
<s:String x:Key="/Default/Environment/InlayHints/CSharpTypeNameHintsOptions/ShowTypeNameHintsForPatternMatchingExpressions/@EntryValue">Never</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002EDaemon_002EInlayHints_002ETypeNameHints_002ECppTypeNameHintsOptionsMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002EDaemon_002ETypeNameHints_002ECppTypeNameHintsOptionsMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ETypeNameHints_002ECSharpTypeNameHintsOptionsMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/HideTypeNameHintsWhenTypeNameIsEvidentFromVariableName/@EntryValue">False</s:Boolean>

12
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>4.0.2</Version>
<Version>4.1.0-rc.2</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>
@ -9,8 +9,16 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/abpframework/abp/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- https://github.com/dotnet/sourcelink -->
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0-beta-20204-02">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

2
docs/en/Blob-Storing.md

@ -21,7 +21,7 @@ The ABP Framework has already the following storage provider implementations;
* [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
* [Aliyun](Blob-Storing-Aliyun.md): Stores BLOBs on the [Aliyun Storage Service](https://help.aliyun.com/product/31815.html).
* [Minio](Blob-Storing-Minio.md): Stores BLOBs on the [MinIO Object storage](https://min.io/).
* [Aws](Blob-Storing-Aws.md): Stores BLOBs on the[Amazon Simple Storage Service](https://min.io/).
* [Aws](Blob-Storing-Aws.md): Stores BLOBs on the[Amazon Simple Storage Service](https://aws.amazon.com/s3/).
More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](Blob-Storing-Custom-Provider.md) and [contribute](Contribution/Index.md) to the ABP Framework.

12
docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md

@ -1,23 +1,23 @@
# ABP Framework 4.0 RC Has Been Published based on .NET 5.0!
Today, we have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) `4.0.0-rc.1` that is based on the **.NET 5.0**. This blog post introduces the new features and important changes in the new version.
Today, we have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) 4.0.0 RC that is based on the **.NET 5.0**. This blog post introduces the new features and important changes in the new version.
> **The planned release date for the [4.0.0 final](https://github.com/abpframework/abp/milestone/45) version is November 26, 2020**.
## Get Started with the 4.0 RC.1
## Get Started with the 4.0 RC
If you want to try the version `4.0.0-rc.1` today, follow the steps below;
If you want to try the version `4.0.0` today, follow the steps below;
1) **Upgrade** the ABP CLI to the version `4.0.0-rc.1` using a command line terminal:
1) **Upgrade** the ABP CLI to the version `4.0.0-rc.3` using a command line terminal:
````bash
dotnet tool update Volo.Abp.Cli -g --version 4.0.0-rc.1
dotnet tool update Volo.Abp.Cli -g --version 4.0.0-rc.3
````
**or install** if you haven't installed before:
````bash
dotnet tool install Volo.Abp.Cli -g --version 4.0.0-rc.1
dotnet tool install Volo.Abp.Cli -g --version 4.0.0-rc.3
````
2) Create a **new application** with the `--preview` option:

87
docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/POST.md

@ -0,0 +1,87 @@
# ABP.IO Platform 4.0 with .NET 5.0 in the 4th Year!
Today, we are extremely happy to release ABP Framework 4.0 with **.NET 5.0 support**!
## 4 Years of Work
As a nice coincidence, today is the **4th year** since the first commit made in the [abp repository](https://github.com/abpframework/abp)! So, we can say "*Happy Birthday ABP Framework!*".
![abp-contribution-graph-4-years](abp-contribution-graph-4-years.png)
### Some Statistics
ABP.IO Platform and the ABP Community is growing. Here, a summary of these 4 years.
From GitHub, only from the main [abp repository](https://github.com/abpframework/abp);
* **15,297 commits** done.
* **3,764 issues** are closed.
* **2,133 pull requests** are merged.
* **158 contributors**.
* **88 releases** published.
* **5.2K stars** on GitHub.
From NuGet & NPM;
* **220 NuGet** packages & **52 NPM** packages.
* **1,000,000 downloads** only for the core NuGet package.
From Website;
* **200,000 visitors**.
* **1,000,000+ sessions**.
## What's New With 4.0?
Since all the new features are already explained in details with the [4.0 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-v4.0-RC-Has-Been-Released-based-on-.NET-5.0), I will not repeat all the details again. Please read [the RC post](https://blog.abp.io/abp/ABP.IO-Platform-v4.0-RC-Has-Been-Released-based-on-.NET-5.0) for **new feature and changes** you may need to do for your solution while upgrading to the version 4.0.
Here, a brief list of major features and changes;
* Migrated to **.NET 5.0**.
* Stable **Blazor** UI.
* Moved to **System.Text.Json**.
* Upgraded to **IdentityServer** version 4.0.
* **WPF** startup template.
## Creating New Solutions
You can create a new solution with the ABP Framework version 4.0 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started).
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details.
## How to Upgrade an Existing Solution
This is a **major version** and requires some **manual work**, especially related to **.NET 5.0** and **IdentityServer** 4.0 upgrades.
* See the [MIGRATION GUIDE](https://docs.abp.io/en/abp/latest/Migration-Guides/Abp-4_0) that covers all the details about the upgrade progress.
* You can also see the [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading).
## New Guides / Documents
We are constantly improving the documentation. Our purpose is not only document the ABP Framework, but also write architectural and practical guides for developers.
### Implementing Domain Driven Design
[Implementing Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design-Implementation-Guide) is a practical guide for they want to implement the DDD principles in their solutions. While the implementation details rely on the ABP Framework infrastructure, core concepts, principles and patterns are applicable in any kind of solution, even if it is not a .NET solution.
![ddd-implementation-guide-sample](ddd-implementation-guide-sample.png)
### Testing
The new [Testing document](https://docs.abp.io/en/abp/latest/Testing) discusses different kind of automated tests and explains how you can write tests for your ABP based solutions.
### UI Documents
We've created a lot of documents for the [MVC](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Overall), [Blazor](https://docs.abp.io/en/abp/latest/UI/Blazor/Overall) and the [Angular](https://docs.abp.io/en/abp/latest/UI/Angular/Quick-Start) UI.
## About the Next Version
The next versions 4.1 will mostly focus on;
* Improving current features.
* Complete module features for the Blazor UI.
* Improve developer experience and productivity.
* More documentation and examples.
Planned preview date for the version **4.1 is December 17, 2020**. See the [Road Map](https://docs.abp.io/en/abp/latest/Road-Map) document and [GitHub Milestones](https://github.com/abpframework/abp/milestones) to learn what's planned for the next versions. We are trying to be clear about the coming features and the next release dates.

BIN
docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/abp-contribution-graph-4-years.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/ddd-implementation-guide-sample.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

202
docs/en/Blog-Posts/2020-12-18 v4_1_Preview/POST.md

@ -0,0 +1,202 @@
# ABP Framework 4.1 RC Has Been Published
Today, we have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) 4.1.0 RC. This blog post introduces the new features and important changes in this new version.
> **The planned release date for the [4.1.0 final](https://github.com/abpframework/abp/milestone/47) version is January 4, 2021**.
## Get Started with the 4.1 RC
If you want to try the version `4.1.0` today, follow the steps below;
1) **Upgrade** the ABP CLI to the version `4.1.0-rc.1` using a command line terminal:
````bash
dotnet tool update Volo.Abp.Cli -g --version 4.1.0-rc.1
````
**or install** if you haven't installed before:
````bash
dotnet tool install Volo.Abp.Cli -g --version 4.1.0-rc.1
````
2) Create a **new application** with the `--preview` option:
````bash
abp new BookStore --preview
````
See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options.
> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**.
## Breaking Changes
This version has a minor breaking change if you'd injected a repository by class. This is not a problem for 99% of the applications. However, see [#6677](https://github.com/abpframework/abp/issues/6677) for the solution if that's a breaking change for you.
## What's new with the ABP Framework 4.1
### Module Entity Extensions
Module Entity Extension system provides a simple way of adding new properties to an existing entity defined by a module that is used by your application. This feature is now available also for the open source modules (identity and tenant-management). [The documentation](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) has been moved into the ABP Framework's documentation.
**Example: Add "SocialSecurityNumber" property to the `IdentityUser` entity**
````csharp
ObjectExtensionManager.Instance.Modules()
.ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<string>( //property type: string
"SocialSecurityNumber", //property name
property =>
{
//validation rules
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(
new StringLengthAttribute(64) {
MinimumLength = 4
}
);
//...other configurations for this property
}
);
});
});
````
The new property becomes available on the UI, API and the database. You can even define navigation properties. This provides an easy way to extend existing modules while using them as NuGet packages. See [the document](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) for details.
### Blazor UI Improvements
Since the Blazor UI is relatively new in the ABP Framework, we continue to add features and make enhancements to fill the gap between other supported UI types.
#### Bundling & Minification
In the version 4.1, we had introduced the `abp bundle` command for the Blazor UI to add global script and style files of the depended modules into the `index.html`. It was a preparation for a real bundling & minification system. With the version 4.2, this command has been completed.
Whenever you add a new module to your Blazor application, just type the `abp bundle` command in a command line terminal;
* It finds all the global script/style files in your application and the modules your application directly or indirectly depends on, ordered by the module dependencies.
* Bundles all the scripts into a single file and minified the file (same for the styles).
* Add the single bundle file to the `index.html` file.
Added a configuration into the `appsettings.json` file in the Blazor application in the application startup template to control the bundling mode:
````js
{
"AbpCli": {
"Bundle": {
"Mode": "BundleAndMinify"
}
}
}
````
Possible values are;
* `BundleAndMinify`: Bundle all the files into a single file and minify the content.
* `Bundle`: Bundle all files into a single file, but not minify.
* `None`: Add files individually, do not bundle.
See the [Global Scripts & Styles](https://docs.abp.io/en/abp/4.1/UI/Blazor/Global-Scripts-Styles) document for details.
#### SubmitButton
`SubmitButton` is a new component that simplifies to save a form:
````html
<SubmitButton Clicked="UpdateEntityAsync" />
````
The main advantages of using this component instead of a standard `Button` with submit type is; It automatically blocks the submit button until the save operation has fully completed. This prevents multiple clicks by user. And it is shorter than doing all manually. See the [document](https://docs.abp.io/en/abp/4.1/UI/Blazor/SubmitButton).
#### Other Blazor UI highlights
* Implemented some **animations** (like opening/closing modals and dropdowns).
* Automatically **focus** to the first input when you open a modal form.
Module extensibility system (mentioned above) for the Blazor UI is under development and not available yet.
## What's new with the ABP Commercial 4.1
### Blazor UI Improvements
We continue to complete missing modules and functionalities for the Blazor UI.
#### Organization Unit Management
Organization Management UI has been implemented for the Blazor UI. Example screenshot:
![blazor-organization-units](blazor-organization-units.png)
#### IdentityServer UI
IdentityServer Management UI is also available for the Blazor UI now:
![blazor-identityserver-ui](blazor-identityserver-ui.png)
### Suite: Navigation Property Selection with Typeahead
We had introduced auto-complete select style navigation property selection. With this release, it is fully supported by all the UI options. So, when you create an CRUD page with ABP Suite for entity that has 1 to Many relation to another entity, you can simply select the target entity with a typeahead style select component. Example screenshot:
![type-ahead](type-ahead.png)
### Spanish Language Translation
We continue to add new language supports for the UI. In this version, translated the UI to **Spanish** language.
![spanish-commercial-translation](spanish-commercial-translation.png)
### Coming: Public Website with Integrated CMS Features
In the next version, the application startup template will come with a public website application option. CMS Kit module will be installed in the website by default, that means newsletter, contact form, comments and some other new features will be directly usable in your applications.
An early screenshot from the public website application home page:
![abp-commercial-public-website](abp-commercial-public-website.png)
## Other News
### ABP Community Contents
A lot of new contents have been published in the ABP Community Web Site in the last two weeks:
* [How to Integrate the Telerik Blazor Components to the ABP Blazor UI](https://community.abp.io/articles/how-to-integrate-the-telerik-blazor-components-to-the-abp-blazor-ui-q8g31abb) by [EngincanV](https://github.com/EngincanV)
* [Using DevExpress Blazor UI Components With the ABP Framework](https://community.abp.io/articles/using-devexpress-blazor-ui-components-with-the-abp-framework-wrpoa8rw) by [@berkansasmaz](https://github.com/berkansasmaz)
* [Creating a new UI theme by copying the Basic Theme (for MVC UI)](https://community.abp.io/articles/creating-a-new-ui-theme-by-copying-the-basic-theme-for-mvc-ui-yt9b18io) by [@ebubekirdinc](https://github.com/ebubekirdinc)
* [Using Angular Material Components With the ABP Framework](https://community.abp.io/members/muhammedaltug) by [@muhammedaltug](https://github.com/muhammedaltug)
* [How to export Excel files from the ABP framework](https://community.abp.io/articles/how-to-export-excel-files-from-the-abp-framework-wm7nnw3n) by [bartvanhoey](https://github.com/bartvanhoey)
* [Creating an Event Organizer Application with the ABP Framework & Blazor UI](https://community.abp.io/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z) by [@hikalkan](https://github.com/hikalkan)
Thanks to all of the contributors. We are waiting for your contributions too. If you want to create content for the ABP Community, please visit [community.abp.io](https://community.abp.io/) website and submit your article.
#### Be a Superhero on Day 1 with ABP.IO
Thanks to [@lprichar](http://github.com/lprichar) prepared an awesome introduction video for the ABP.IO Platform: "[Be a Superhero on Day 1 with ABP.IO](https://www.youtube.com/watch?v=ea0Zx9DLcGA)".
#### New Sample Application: Event Organizer
This is a new example application developed using the ABP Framework and the Blazor UI. See [this article](https://community.abp.io/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z) for a step by step implementation guide.
![event-list-ui](event-list-ui.png)
### Github Discussions
We enabled the [GitHub Discussions for the abp repository](https://github.com/abpframework/abp/discussions) as another place to discuss ideas or get help for the ABP Framework. The ABP core team is spending time participating in discussions and answering to questions as much as possible.
## About the Next Release(s)
Beginning from the next version (4.2.0), we are starting to spend more effort on the **CMS Kit module**. The purpose of this module is to provide CMS primitives (e.g. **comments, tags, reactions, contents**...) and features (e.g. **blog, pages, surveys**) as pre-built and reusable components. Current blog module will be a part of the CMS Kit module.
We will continue to prepare documents, guides, tutorials and examples. And surely, we will continue to make enhancements and optimizations on the current features.
> The planned preview release date for the version 4.2.0 is January 14, 2021 and the final (stable) version release date is January 28, 2021.
Follow the [GitHub milestones](https://github.com/abpframework/abp/milestones) for all the planned ABP Framework version release dates.
## Feedback
Please check out the ABP Framework 4.1.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. **The planned release date for the [4.1.0 final](https://github.com/abpframework/abp/milestone/45) version is January 4, 2021**.

BIN
docs/en/Blog-Posts/2020-12-18 v4_1_Preview/abp-commercial-public-website.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

BIN
docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-identityserver-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-organization-units.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
docs/en/Blog-Posts/2020-12-18 v4_1_Preview/event-list-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

BIN
docs/en/Blog-Posts/2020-12-18 v4_1_Preview/spanish-commercial-translation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

BIN
docs/en/Blog-Posts/2020-12-18 v4_1_Preview/type-ahead.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

5
docs/en/CLI.md

@ -129,6 +129,7 @@ abp update [options]
* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
* `--check-all`: Check the new version of each package separately. Default is `false`.
* `--version` or `-v`: Specifies the version to use for update. If not specified, latest version is used.
### add-package
@ -161,7 +162,7 @@ abp add-package Volo.Abp.MongoDB
Adds a [multi-package application module](Modules/Index) to a solution by finding all packages of the module, finding related projects in the solution and adding each package to the corresponding project in the solution.
It can also create a new module for your solution and add it to your solution. See `--new-template` option.
It can also create a new module for your solution and add it to your solution. See `--new` option.
> A business module generally consists of several packages (because of layering, different database provider options or other reasons). Using `add-module` command dramatically simplifies adding a module to a solution. However, each module may require some additional configurations which is generally indicated in the documentation of the related module.
@ -418,4 +419,6 @@ abp bundle [options]
* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a Blazor project file.
* ```--force``` or ```-f```: Forces to build project before generating references.
`bundle` command reads the `appsettings.json` file inside the Blazor project for bundling options. For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md)
For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md)

3
docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md

@ -71,7 +71,8 @@ public async override Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetE
?? provider;
return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens()
AuthenticationTokens = auth.Properties.GetTokens(),
AuthenticationProperties = auth.Properties
};
}
````

48
docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md

@ -6,9 +6,9 @@ Adding Azure Active Directory is pretty straightforward in ABP framework. Couple
Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage.
1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.
1. ~~**AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.~~ Now marked **Obsolete** (see https://github.com/aspnet/Announcements/issues/439).
2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections.
3. **AddMicrosoftIdentityWebAppAuthentication:** This approach uses newly introduced [Microsoft.Identity.Web nuget package](https://www.nuget.org/packages/Microsoft.Identity.Web/) to replace AddAzureAD.
> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings.
>
@ -134,13 +134,50 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi
And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers.
## 3. AddMicrosoftIdentityWebAppAuthentication
With .Net 5.0, AzureAd is marked [obsolete](https://github.com/dotnet/aspnetcore/issues/25807) and will not be supported in the near future. However its expanded functionality is available in [microsoft-identity-web](https://github.com/AzureAD/microsoft-identity-web/wiki) packages.
Add (or replace with) the new nuget package Microsoft.Identity.Web nuget package](https://www.nuget.org/packages/Microsoft.Identity.Web/).
In your **.Web** project; you update the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following while having the AzureAd appsettings section as defined before:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "Acme.BookStore";
});
context.Services.AddMicrosoftIdentityWebAppAuthentication(
configuration: configuration,
configSectionName: "AzureAd",
openIdConnectScheme:"AzureAD",
cookieScheme:null);
}
````
And that's all to add new Microsoft-Identity-Web.
> **Don't forget to:**
>
> * Pass **cookieScheme** parameter as **null** or your [*GetExternalLoginInfoAsync* method will always return null](https://github.com/AzureAD/microsoft-identity-web/issues/133#).
Keep in mind that [Microsoft-Identity-Web](https://github.com/AzureAD/microsoft-identity-web) is relatively new and keeps getting new enhancements, features and documentation.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization).
# FAQ
* Help! `GetExternalLoginInfoAsync` returns `null`!
* Help! `GetExternalLoginInfoAsync` returns `null`! (Using obsolute **AddAzureAD**)
* There can be 2 reasons for this;
@ -158,6 +195,11 @@ You can find the source code of the completed example [here](https://github.com/
````
* Help! `GetExternalLoginInfoAsync` returns `null`! (Using **AddMicrosoftIdentityWebAppAuthentication**)
* Pass cookieScheme parameter as **null**. (See [this issue](https://github.com/AzureAD/microsoft-identity-web/issues/133)).
* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error!

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

@ -22,6 +22,8 @@ Then add a string property called `Title`, as an example property.
### Create AppUserDto
_Note that, creating `AppUserDto` is not necessary after ABP v4.X_
ABP Suite needs a DTO for the target entity (user, in this case) in order to define a navigation property.
To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`.

12
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md

@ -1,22 +1,22 @@
# How to Setup Azure Active Directory and Integrate Abp Angular Application
# How to Setup Azure Active Directory and Integrate ABP Angular Application
This guide demonstrates how to register an application to Azure Active Directory and integrate AzureAD to an ABP angular application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**.
This guide demonstrates how to register an application to Azure Active Directory and integrate AzureAD to an ABP Angular application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**.
## Authentication Flow
Abp angular applications use **Authentication Code with PKCE** (specs [here](https://tools.ietf.org/html/rfc7636)) which is the most suitable flow for spa applications by the time this article is written since implicit flow is deprecated.
ABP Angular application uses **Authentication Code with PKCE** (specs [here](https://tools.ietf.org/html/rfc7636)) which is the most suitable flow for SPA applications by the time this article is written since implicit flow is deprecated.
The most common question is;
> Where to put OpenId connection code in angular project?
> Where to put OpenId connection code in the Angular project?
The answer is, **you don't**. Abp angular application is integrated with backend code (HttpApi.Host project) where it loads the configurations, **permissions** etc. For none-tiered angular applications, **HttpApi.Host** project also has IdentityServer4 embedded; also serving as **Authorization Server**. Angular application authentication flow is shown below.
The answer is, **you don't**. ABP Angular application is integrated with the backend (HttpApi.Host project) where it loads the configurations, **permissions** etc. For none-tiered angular applications, **HttpApi.Host** project also has IdentityServer4 embedded; also serving as **Authorization Server**. Angular application authentication flow is shown below.
<img src="auth-diagram.jpeg" alt="auth-diagram" style="zoom:50%;" />
> What if I want Azure AD as my authorization server and not IdentityServer?
This means your application will be using AzureAD user store for authentication. By registering both angular app and HttpApi to AzureAD, authentication might work but **authorization won't**. Users need to be registered to Abp identity system for auditing, permissions etc. So the flow should be 3rd party registration.
This means your application will be using AzureAD user store for authentication. By registering both Angular app and HttpApi to AzureAD, authentication might work but **authorization won't**. Users need to be registered to ABP identity system for auditing, permissions etc. So the flow should be 3rd party registration.
## Setting up OpenId Connection

936
docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md

@ -0,0 +1,936 @@
# Creating an Event Organizer Application with the ABP Framework & Blazor UI.
## Introduction
In this article, we will create an example application that is a simple **meeting/event organizer**: People create events and other people registers to the event.
The application has been developed with **Blazor** as the UI framework and **MongoDB** as the database provider.
> This tutorial is based on my notes that I'd created to implement this application in a workshop. It shows the necessary steps to build the application rather than detailed explanations.
### Source Code
Source code of the completed application is [available on GitHub](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer).
### Screenshots
Here, the pages of the final application.
**Home Page - Event List**
![event-list-ui](images/event-list-ui.png)
**Creating a new Event**
![event-create-ui](images/event-create-ui.png)
**Event Detail Page**
![event-detail-ui](images/event-detail-ui.png)
## Requirements
The following tools are needed to be able to run the solution.
* .NET 5.0 SDK
* Visual Studio 2019 16.8.0+ or another compatible IDE
* MongoDB Server (with MongoDB Compass)
## Development
### Creating a new Application
* Use the following ABP CLI command:
````bash
abp new EventOrganizer -u blazor -d mongodb
````
### Open & Run the Application
* Open the solution in Visual Studio (or your favorite IDE).
* Run the `EventOrganizer.DbMigrator` application to seed the initial data.
* Run the `EventOrganizer.HttpApi.Host` application that starts the server side.
* Run the `EventOrganizer.Blazor` application to start the UI.
### Apply the Custom Styles
* Add styles to `wwwroot/main.css`:
````css
body.abp-application-layout {
background-color: #222 !important;
font-size: 18px;
}
nav#main-navbar.bg-dark {
background-color: #222 !important;
box-shadow: none !important;
}
.event-pic {
width: 100%;
border-radius: 12px;
box-shadow: 5px 5px 0px 0px rgba(0,0,0,.5);
margin-bottom: 10px;
}
.event-link:hover, .event-link:hover *{
text-decoration: none;
}
.event-link:hover .event-pic {
box-shadow: 5px 5px 0px 0px #ffd800;
}
.event-form {
background-color: #333 !important;
box-shadow: 5px 5px 0px 0px rgba(0,0,0,.5);
border-radius: 12px;
}
.table {
background: #fff;
border-radius: 12px;
box-shadow: 5px 5px 0px 0px rgba(0,0,0,.5);
}
.table th{
border: 0 !important;
}
.modal {
color: #333;
}
.page-item:first-child .page-link {
margin-left: 0;
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
}
.page-item:last-child .page-link {
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
}
.btn {
border-radius: 8px;
}
.att-list {
list-style: none;
padding: 0;
}
.att-list li {
padding: 4px 0 0 0;
}
````
* `wwwroot/index.html`: Remove `bg-light` class from the `body` tag and add `bg-dark text-light`.
### Domain Layer
* Add the following `Event` aggregate (with `EventAttendee`) to the solution:
**Event**
````csharp
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing;
namespace EventOrganizer.Events
{
public class Event : FullAuditedAggregateRoot<Guid>
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsFree { get; set; }
public DateTime StartTime { get; set; }
public ICollection<EventAttendee> Attendees { get; set; }
public Event()
{
Attendees = new List<EventAttendee>();
}
}
}
````
**EventAttendee**
```csharp
using System;
using Volo.Abp.Auditing;
namespace EventOrganizer.Events
{
public class EventAttendee : IHasCreationTime
{
public Guid UserId { get; set; }
public DateTime CreationTime { get; set; }
}
}
```
### MongoDB Mapping
* Add the following property to the `EventOrganizerMongoDbContext`:
````csharp
public IMongoCollection<Event> Events => Collection<Event>();
````
### Clean Index.razor & Add the Header & "Create Event" button
* Clean the `Index.razor` file.
* Replace the content with the following code:
````html
@page "/"
@inherits EventOrganizerComponentBase
<Row Class="mb-4">
<Column Class="text-left">
<h1>Upcoming Events</h1>
</Column>
<Column Class="text-right">
@if (CurrentUser.IsAuthenticated)
{
<a class="btn btn-primary" href="/create-event">
<i class="fa fa-plus"></i> @L["CreateEvent"]
</a>
}
</Column>
</Row>
````
* Open `Localization/EventOrganizer/en.json` in the `EventOrganizer.Domain.Shared` project and add the following entry:
````json
"CreateEvent": "Create a new event!"
````
The Result (run the `EventOrganizer.Blazor` application to see):
![index-title](images/index-title.png)
### Event Creation
* Create the Initial `IEventAppService` with the `CreateAsync` method:
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace EventOrganizer.Events
{
public interface IEventAppService : IApplicationService
{
Task<Guid> CreateAsync(EventCreationDto input);
}
}
````
* Add `EventCreationDto` class:
````csharp
using System;
using System.ComponentModel.DataAnnotations;
namespace EventOrganizer.Events
{
public class EventCreationDto
{
[Required]
[StringLength(100)]
public string Title { get; set; }
[Required]
[StringLength(2000)]
public string Description { get; set; }
public bool IsFree { get; set; }
public DateTime StartTime { get; set; }
}
}
````
* Implement the `EventAppService`:
````csharp
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Domain.Repositories;
namespace EventOrganizer.Events
{
public class EventAppService : EventOrganizerAppService, IEventAppService
{
private readonly IRepository<Event, Guid> _eventRepository;
public EventAppService(IRepository<Event, Guid> eventRepository)
{
_eventRepository = eventRepository;
}
[Authorize]
public async Task<Guid> CreateAsync(EventCreationDto input)
{
var eventEntity = ObjectMapper.Map<EventCreationDto, Event>(input);
await _eventRepository.InsertAsync(eventEntity);
return eventEntity.Id;
}
}
}
````
* Add AutoMapper mapping to the `EventOrganizerApplicationAutoMapperProfile` class:
````csharp
using AutoMapper;
using EventOrganizer.Events;
namespace EventOrganizer
{
public class EventOrganizerApplicationAutoMapperProfile : Profile
{
public EventOrganizerApplicationAutoMapperProfile()
{
CreateMap<EventCreationDto, Event>();
}
}
}
````
This will automatically create the HTTP (REST) API for the application service (run the `EventOrganizer.HttpApi.Host` application to see it on the Swagger UI):
![swagger-event-create](images/swagger-event-create.png)
* Create the `CreateEvent.razor` file:
````csharp
@page "/create-event"
@inherits EventOrganizerComponentBase
<Heading Size="HeadingSize.Is3" Margin="Margin.Is5.FromTop.Is4.FromBottom" Class="text-center">Create Event</Heading>
<Row>
<Column ColumnSize="ColumnSize.Is6.Is3.WithOffset">
<div class="p-lg-5 p-md-3 event-form">
<EditForm Model="@Event" OnValidSubmit="Create">
<Field>
<FieldLabel>@L["Title"]</FieldLabel>
<TextEdit @bind-Text="@Event.Title" />
</Field>
<Field>
<FieldLabel>@L["Description"]</FieldLabel>
<MemoEdit @bind-Text="@Event.Description" />
</Field>
<Field>
<Check TValue="bool" @bind-Checked="@Event.IsFree">@L["Free"]</Check>
</Field>
<Field>
<FieldLabel>@L["StartTime"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@Event.StartTime" />
</Field>
<Button Type="@ButtonType.Submit" Block="true" Color="@Color.Primary" Size="Size.Large">@L["Save"]</Button>
</EditForm>
</div>
</Column>
</Row>
````
* Create a partial `CreateEvent` class in the same folder, with the `CreateEvent.razor.cs` as the file name:
````csharp
using System.Threading.Tasks;
using EventOrganizer.Events;
using Microsoft.AspNetCore.Components;
namespace EventOrganizer.Blazor.Pages
{
public partial class CreateEvent
{
private EventCreationDto Event { get; set; } = new EventCreationDto();
private readonly IEventAppService _eventAppService;
private readonly NavigationManager _navigationManager;
public CreateEvent(
IEventAppService eventAppService,
NavigationManager navigationManager)
{
_eventAppService = eventAppService;
_navigationManager = navigationManager;
}
private async Task Create()
{
var eventId = await _eventAppService.CreateAsync(Event);
_navigationManager.NavigateTo("/events/" + eventId);
}
}
}
````
The final UI is (run the `EventOrganizer.Blazor` application and click to the "Create Event" button):
![event-create-ui](images/event-create-ui.png)
### Upcoming Events (Home Page)
* Open the `IEventAppService` and add a `GetUpcomingAsync` method to get the list of upcoming events:
````csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace EventOrganizer.Events
{
public interface IEventAppService : IApplicationService
{
Task<Guid> CreateAsync(EventCreationDto input);
Task<List<EventDto>> GetUpcomingAsync();
}
}
````
* Add a `EventDto` class:
````csharp
using System;
using Volo.Abp.Application.Dtos;
namespace EventOrganizer.Events
{
public class EventDto : EntityDto<Guid>
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsFree { get; set; }
public DateTime StartTime { get; set; }
public int AttendeesCount { get; set; }
}
}
````
* Implement the `GetUpcomingAsync` in the `EventAppService` class:
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Domain.Repositories;
namespace EventOrganizer.Events
{
public class EventAppService : EventOrganizerAppService, IEventAppService
{
private readonly IRepository<Event, Guid> _eventRepository;
public EventAppService(IRepository<Event, Guid> eventRepository)
{
_eventRepository = eventRepository;
}
[Authorize]
public async Task<Guid> CreateAsync(EventCreationDto input)
{
var eventEntity = ObjectMapper.Map<EventCreationDto, Event>(input);
await _eventRepository.InsertAsync(eventEntity);
return eventEntity.Id;
}
public async Task<List<EventDto>> GetUpcomingAsync()
{
var events = await AsyncExecuter.ToListAsync(
_eventRepository
.Where(x => x.StartTime > Clock.Now)
.OrderBy(x => x.StartTime)
);
return ObjectMapper.Map<List<Event>, List<EventDto>>(events);
}
}
}
````
* Add the following line into the `EventOrganizerApplicationAutoMapperProfile` constructor:
````csharp
CreateMap<Event, EventDto>();
````
Run the `EventOrganizer.HttpApi.Host` application to see the new `upcoming` endpoint on the Swagger UI:
![swagger-event-upcoming](images/swagger-event-upcoming.png)
* Change the `Pages/Index.razor.cs` content in the `EventOrganizer.Blazor` project as shown below:
```csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using EventOrganizer.Events;
namespace EventOrganizer.Blazor.Pages
{
public partial class Index
{
private List<EventDto> UpcomingEvents { get; set; } = new List<EventDto>();
private readonly IEventAppService _eventAppService;
public Index(IEventAppService eventAppService)
{
_eventAppService = eventAppService;
}
protected override async Task OnInitializedAsync()
{
UpcomingEvents = await _eventAppService.GetUpcomingAsync();
}
}
}
```
* Change the `Pages/Index.razor` content in the `EventOrganizer.Blazor` project as shown below:
````html
@page "/"
@inherits EventOrganizerComponentBase
<Row Class="mb-4">
<Column Class="text-left">
<h1>Upcoming Events</h1>
</Column>
<Column Class="text-right">
@if (CurrentUser.IsAuthenticated)
{
<a class="btn btn-primary" href="/create-event">
<i class="fa fa-plus"></i> @L["CreateEvent"]
</a>
}
</Column>
</Row>
<Row>
@foreach (var upcomingEvent in UpcomingEvents)
{
<Column Class="col-12 col-lg-4 col-md-6">
<a class="mb-5 position-relative d-block event-link" href="/events/@upcomingEvent.Id">
<div class="position-absolute text-right w-100 px-3 py-2" style="left: 0; top: 2px;">
@if (upcomingEvent.IsFree)
{
<Badge Color="Color.Success" Class="mr-1">FREE</Badge>
}
<span class="badge badge-warning font-weight-normal">
<i class="fas fa-user-friends"></i>
<span class="font-weight-bold">@upcomingEvent.AttendeesCount</span>
</span>
</div>
<img src="https://picsum.photos/seed/@upcomingEvent.Id/400/300" class="event-pic"/>
<div class="px-3 py-1">
<small class="font-weight-bold text-warning my-2 d-block text-uppercase">@upcomingEvent.StartTime.ToLongDateString()</small>
<p class="h4 text-light d-block mb-2">@upcomingEvent.Title</p>
<p class="text-light" style="opacity: .65;">@upcomingEvent.Description.TruncateWithPostfix(150)</p>
</div>
</a>
</Column>
}
</Row>
````
The new home page is shown below:
![event-list-ui](images/event-list-ui.png)
### Event Detail Page
* Add `GetAsync`, `RegisterAsync`, `UnregisterAsync` and `DeleteAsync` methods to the `IEventAppService`:
````csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace EventOrganizer.Events
{
public interface IEventAppService : IApplicationService
{
Task<Guid> CreateAsync(EventCreationDto input);
Task<List<EventDto>> GetUpcomingAsync();
Task<EventDetailDto> GetAsync(Guid id);
Task RegisterAsync(Guid id);
Task UnregisterAsync(Guid id);
Task DeleteAsync(Guid id);
}
}
````
* Add `EventDetailDto` class:
````csharp
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;
namespace EventOrganizer.Events
{
public class EventDetailDto : CreationAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsFree { get; set; }
public DateTime StartTime { get; set; }
public List<EventAttendeeDto> Attendees { get; set; }
}
}
````
* Add `EventAttendeeDto` class:
````csharp
using System;
namespace EventOrganizer.Events
{
public class EventAttendeeDto
{
public Guid UserId { get; set; }
public string UserName { get; set; }
public DateTime CreationTime { get; set; }
}
}
````
* Implement the new methods in the `EventAppService`:
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EventOrganizer.Users;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
namespace EventOrganizer.Events
{
public class EventAppService : EventOrganizerAppService, IEventAppService
{
private readonly IRepository<Event, Guid> _eventRepository;
private readonly IRepository<AppUser, Guid> _userRepository;
public EventAppService(IRepository<Event, Guid> eventRepository, IRepository<AppUser, Guid> userRepository)
{
_eventRepository = eventRepository;
_userRepository = userRepository;
}
[Authorize]
public async Task<Guid> CreateAsync(EventCreationDto input)
{
var eventEntity = ObjectMapper.Map<EventCreationDto, Event>(input);
await _eventRepository.InsertAsync(eventEntity);
return eventEntity.Id;
}
public async Task<List<EventDto>> GetUpcomingAsync()
{
var events = await AsyncExecuter.ToListAsync(
_eventRepository
.Where(x => x.StartTime > Clock.Now)
.OrderBy(x => x.StartTime)
);
return ObjectMapper.Map<List<Event>, List<EventDto>>(events);
}
public async Task<EventDetailDto> GetAsync(Guid id)
{
var @event = await _eventRepository.GetAsync(id);
var attendeeIds = @event.Attendees.Select(a => a.UserId).ToList();
var attendees = (await AsyncExecuter.ToListAsync(_userRepository.Where(u => attendeeIds.Contains(u.Id))))
.ToDictionary(x => x.Id);
var result = ObjectMapper.Map<Event, EventDetailDto>(@event);
foreach (var attendeeDto in result.Attendees)
{
attendeeDto.UserName = attendees[attendeeDto.UserId].UserName;
}
return result;
}
[Authorize]
public async Task RegisterAsync(Guid id)
{
var @event = await _eventRepository.GetAsync(id);
if (@event.Attendees.Any(a => a.UserId == CurrentUser.Id))
{
return;
}
@event.Attendees.Add(new EventAttendee {UserId = CurrentUser.GetId(), CreationTime = Clock.Now});
await _eventRepository.UpdateAsync(@event);
}
[Authorize]
public async Task UnregisterAsync(Guid id)
{
var @event = await _eventRepository.GetAsync(id);
var removedItems = @event.Attendees.RemoveAll(x => x.UserId == CurrentUser.Id);
if (removedItems.Any())
{
await _eventRepository.UpdateAsync(@event);
}
}
[Authorize]
public async Task DeleteAsync(Guid id)
{
var @event = await _eventRepository.GetAsync(id);
if (CurrentUser.Id != @event.CreatorId)
{
throw new UserFriendlyException("You don't have the necessary permission to delete this event!");
}
await _eventRepository.DeleteAsync(id);
}
}
}
````
* Add the following mappings into the `EventOrganizerApplicationAutoMapperProfile`:
````csharp
CreateMap<Event, EventDetailDto>();
CreateMap<EventAttendee, EventAttendeeDto>();
````
Run the `EventOrganizer.HttpApi.Host` application to see the complete Event HTTP API in the Swagger UI:
![swagger-event-all](images/swagger-event-all.png)
* Create `EventDetail.razor` component with the following content:
````html
@page "/events/{id}"
@inherits EventOrganizerComponentBase
@if (Event != null)
{
<Row Class="mb-4">
<Column Class="text-left">
<h1>@Event.Title</h1>
</Column>
<Column Class="text-right pt-2">
<a href="/" Class="btn btn-dark"><i class="fa fa-arrow-left"></i> Back</a>
@if (CurrentUser.IsAuthenticated && CurrentUser.Id == Event.CreatorId)
{
<Button Color="Color.Danger" Clicked="Delete" Class="ml-1">Delete</Button>
}
</Column>
</Row>
<Row>
<Column Class="col-12 col-md-8">
<div class="position-relative">
<div class="position-absolute text-right w-100 px-3 py-2" style="left: 0; top: 2px;">
@if (Event.IsFree)
{
<Badge Color="Color.Success" Class="mr-1">FREE</Badge>
}
<span class="badge badge-warning font-weight-normal">
<i class="fas fa-user-friends"></i>
<span class="font-weight-bold">@Event.Attendees.Count</span>
</span>
</div>
<img src="https://picsum.photos/seed/@Event.Id/800/600" class="event-pic" />
<small class="font-weight-bold text-warning my-2 d-block text-uppercase">Start time: @Event.StartTime.ToLongDateString()</small>
<p style="opacity: .65;">@Event.Description</p>
</div>
</Column>
<Column Class="col-12 col-md-4">
<div class="p-4 event-form">
@if (CurrentUser.IsAuthenticated)
{
<div>
@if (!IsRegistered)
{
<Button Color="Color.Primary" Clicked="Register" Class="btn-block btn-lg">Register now!</Button>
}
else
{
<p>You are registered in this event</p>
<Button Color="Color.Secondary" Clicked="UnRegister" Class="btn-block">Cancel registration!</Button>
}
</div>
}
else
{
<a class="btn btn-primary" href="/authentication/login">
<i class="fa fa-sign-in-alt"></i> Login to attend!
</a>
}
</div>
<div class="mt-4 event-form p-4">
<span class="font-weight-bold"><i class="fas fa-user-friends"></i> Attendees <span class="float-right font-weight-normal" style="opacity:.65;">(@Event.Attendees.Count)</span></span>
<ul class="mt-1 mb-0 att-list">
@foreach (var attendee in Event.Attendees)
{
<li><i class="fa fa-check"></i> @attendee.UserName</li>
}
</ul>
</div>
</Column>
</Row>
}
````
* Create `EventDetail.razor.cs` file with the following content:
````csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using EventOrganizer.Events;
using Microsoft.AspNetCore.Components;
namespace EventOrganizer.Blazor.Pages
{
public partial class EventDetail
{
[Parameter]
public string Id { get; set; }
private EventDetailDto Event { get; set; }
private bool IsRegistered { get; set; }
private readonly IEventAppService _eventAppService;
private readonly NavigationManager _navigationManager;
public EventDetail(
IEventAppService eventAppService,
NavigationManager navigationManager)
{
_eventAppService = eventAppService;
_navigationManager = navigationManager;
}
protected override async Task OnInitializedAsync()
{
await GetEventAsync();
}
private async Task GetEventAsync()
{
Event = await _eventAppService.GetAsync(Guid.Parse(Id));
if (CurrentUser.IsAuthenticated)
{
IsRegistered = Event.Attendees.Any(a => a.UserId == CurrentUser.Id);
}
}
private async Task Register()
{
await _eventAppService.RegisterAsync(Guid.Parse(Id));
await GetEventAsync();
}
private async Task UnRegister()
{
await _eventAppService.UnregisterAsync(Guid.Parse(Id));
await GetEventAsync();
}
private async Task Delete()
{
if (!await Message.Confirm("This event will be deleted: " + Event.Title))
{
return;
}
await _eventAppService.DeleteAsync(Guid.Parse(Id));
_navigationManager.NavigateTo("/");
}
}
}
````
The resulting page is shown below:
![event-detail-ui](images/event-detail-ui.png)
### Integration Tests
Create an `EventAppService_Tests` class in the `EventOrganizer.Application.Tests` project:
````csharp
using System;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace EventOrganizer.Events
{
[Collection(EventOrganizerTestConsts.CollectionDefinitionName)]
public class EventAppService_Tests : EventOrganizerApplicationTestBase
{
private readonly IEventAppService _eventAppService;
public EventAppService_Tests()
{
_eventAppService = GetRequiredService<IEventAppService>();
}
[Fact]
public async Task Should_Create_A_Valid_Event()
{
// Create an event
var eventId = await _eventAppService.CreateAsync(
new EventCreationDto
{
Title = "My test event 1",
Description = "My test event description 1",
IsFree = true,
StartTime = DateTime.Now.AddDays(2)
}
);
eventId.ShouldNotBe(Guid.Empty);
// Get the event
var @event = await _eventAppService.GetAsync(eventId);
@event.Title.ShouldBe("My test event 1");
// Get upcoming events
var events = await _eventAppService.GetUpcomingAsync();
events.ShouldContain(x => x.Title == "My test event 1");
}
}
}
````
## Source Code
Source code of the completed application is [available on GitHub](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer).

BIN
docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-create-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-detail-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-list-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

BIN
docs/en/Community-Articles/2020-12-04-Event-Organizer/images/index-title.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-all.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-create.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-upcoming.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

110
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/POST.md

@ -0,0 +1,110 @@
## Using DevExpress Blazor UI Components With the ABP Framework
Hi, in this step by step article, I will show you how to integrate [DevExpress](https://demos.devexpress.com/blazor/) blazor UI components into ABP Framework-based applications.
![both-example-result](both-example-result.png)
*(A screenshot from the example application developed in this article)*
## Create the Project
> ABP Framework offers startup templates to get into business faster.
In this article, I will create a new startup template with EF Core as a database provider and Blazor for UI framework. But if you already have a project with Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project.
> If you already have a project with the Blazor UI, you can skip this section.
* Before starting to development, we will create a solution named `DevExpressSample` (or whatever you want). We will create a new startup template with EF Core as a database provider and Blazor for UI framework by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
````bash
abp new DevExpressSample -u blazor
````
![initial-project](initial-project.png)
* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE) and run the `DevExpressSample.DbMigrator` to create the database and seed initial data (which creates the admin user, admin role, permissions etc.)
* After database and initial data created,
* Run the `DevExpressSample.HttpApi.Host` to see our server side working and
* Run the `DevExpressSample.Blazor` to see our UI working properly.
> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_
## Install DevExpress
You can follow [this documentation](https://docs.devexpress.com/Blazor/401986/getting-started/install-components-and-create-an-application/without-devexpress-installer/microsoft-templates) to install DevExpress packages into your computer.
> Don't forget to add _"DevExpress NuGet Feed"_ to your **Nuget Package Sources**.
### Adding DevExpress NuGet Packages
Add the `DevExpress.Blazor` NuGet package to the `DevExpressSample.Blazor` project.
```
Install-Package DevExpress.Blazor
```
### Register DevExpress Resources
1. Add the following line to the HEAD section of the `wwwroot/index.html` file within the `DevExpressSample.Blazor` project:
```Razor
<head>
<!--...-->
<link href="_content/DevExpress.Blazor/dx-blazor.css" rel="stylesheet" />
</head>
```
2. In the `DevExpressSampleBlazorModule` class, call the `AddDevExpressBlazor()` method from your project's `ConfigureServices()` method:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var environment = context.Services.GetSingletonInstance<IWebAssemblyHostEnvironment>();
var builder = context.Services.GetSingletonInstance<WebAssemblyHostBuilder>();
// ...
builder.Services.AddDevExpressBlazor();
}
```
3. Register the **DevExpressSample.Blazor** namespace in the `_Imports.razor` file:
```Razor
@using DevExpress.Blazor
```
### Result
The installation step was done. You can use any DevExpress Blazor UI component in your application:
Example: A Scheduler:
![sample-appointment](sample-appointment.gif)
This example has been created by following [this documentation](https://demos.devexpress.com/blazor/SchedulerViewTypes).
## The Sample Application
We have created a sample application with [Data Grid](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxDataGrid-1) example.
### The Source Code
You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/DevExpress-Blazor).
The related files for this example are marked in the following screenshots.
![data-grid-app-contract](data-grid-app-contract.png)
![data-grid-application](data-grid-application.png)
![data-grid-web](data-grid-blazor.png)
### Additional Notes
#### Data Storage
I've used an in-memory list to store data for this example, instead of a real database. Because it is not related to DevExpress usage. There is a `SampleDataService.cs` file in `Data` folder at `DevExpressSample.Application.Contracts` project. All the data is stored here.
## Conclusion
In this article, I've explained how to use [DevExpress](https://www.devexpress.com/blazor/) components in your application. ABP Framework is designed so that it can work with any UI library/framework.

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/both-example-result.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-app-contract.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-application.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/initial-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/sample-appointment.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 KiB

534
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md

@ -0,0 +1,534 @@
# How to Integrate the Telerik Blazor Components to the ABP Blazor UI?
## Introduction
Hi, in this step by step article, we will see how we can integrate the Telerik Blazor Components to our Blazor UI.
## Creating the Solution
> ABP Framework offers startup templates to get into business faster.
In this article, I will create a new startup template with EF Core as a database provider and Blazor for UI framework. But if you already have a project with Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project.
> If you already have a project with the Blazor UI, you can skip this section.
* Before starting to development, we will create a solution named `TelerikComponents` (or whatever you want). We will create a new startup template with EF Core as a database provider and Blazor for UI framework by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
```bash
abp new TelerikComponents --ui blazor --database-provider ef
```
* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE) and run the `TelerikComponents.DbMigrator` to create the database and seed initial data (which creates the admin user, admin role, permissions, etc.)
* After the database and initial data created,
* Run the `TelerikComponents.HttpApi.Host` to see our server-side working and
* Run the `TelerikComponents.Blazor` to see our UI working.
> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_
## Starting the Development
### Pre-requisite
* First thing we need to do is downloading the [Progress Control Panel](https://www.telerik.com/download-trial-file/v2/control-panel?_ga=2.212029332.1667119438.1607582144-1944255175.1605161949) to get Telerik Blazor Components on our development machine.
* If you will use the Telerik Blazor Components for the first time or you don't have an active license you can click [here](https://www.telerik.com/login/v2/download-b?ReturnUrl=https%3a%2f%2fwww.telerik.com%2fdownload-trial-file%2fv2-b%2fui-for-blazor%3f_ga%3d2.212029332.1667119438.1607582144-1944255175.1605161949#register) to download free trial.
> You can find the more installation details from [here](https://docs.telerik.com/blazor-ui/getting-started/client-blazor?_ga=2.55603115.1667119438.1607582144-1944255175.1605161949&_gac=1.261851647.1607669357.CjwKCAiAq8f-BRBtEiwAGr3DgUDhBT25rs7hU0EQ8K-AfeUVxs3hSoIuIAuBOZ17CNPI4ZEArORPExoCyd4QAvD_BwE#step-0---download-the-components).
>**Notes:** To download Telerik Blazor packages via NuGet, we need to setup Telerik NuGet package source. We can state it in the installer as below. In this way, we can download the required Telerik Blazor packages via NuGet.
![setup-nuget-package-source](./automated-nuget-feed-setup.png)
### Step 1 (Configurations)
* We need to install the `Telerik.UI.for.Blazor` Nuget package to our Blazor project (`*.Blazor`). We need to choose package source to **telerik.com** for Visual Studio to see this package.
* If you use trial version of Telerik, you can download **Telerik.UI.for.Blazor.Trial** package via NuGet.
* After the installation finished, we need to open **index.html** (it's under *wwwroot* folder) to add css and js files in our application.
* Add the following lines just before the closing head tag (**/head**).
```html
...
<link rel="stylesheet" href="https:unpkg.com/@progress/kendo-theme-default@latest/dist/all.css" />
<script src="_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script>
<!-- For Trial licenses use
<script src="_content/Telerik.UI.for.Blazor.Trial/js/telerik-blazor.js" defer></script>
-->
</head>
```
* After that, we need to add the Telerik Blazor Components to our application's service collection. So just open the `TelerikComponentsBlazorModule` and update the `ConfigureServices` method with the following content.
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var environment = context.Services.GetSingletonInstance<IWebAssemblyHostEnvironment>();
var builder = context.Services.GetSingletonInstance<WebAssemblyHostBuilder>();
ConfigureAuthentication(builder);
ConfigureHttpClient(context, environment);
ConfigureBlazorise(context);
ConfigureRouter(context);
ConfigureUI(builder);
ConfigureMenu(context);
ConfigureAutoMapper(context);
//add this line to be able to use the components
builder.Services.AddTelerikBlazor();
}
```
* After the service added we will continue by opening **_Imports.razor** file and add the global using statements as below. This will bring our Telerik components into scope throughout the application.
![telerik-blazor-component-1](./telerik-blazor-component-1.jpg)
* After all of these steps, the Telerik UI Components are ready to be used anywhere in our application. We can use the Telerik Blazor Components by wrapping our components or pages between `<TelerikRootComponent>` and `</TelerikRootComponent>` tags.
### Step 2 - Checking Configurations
* We should check, have we done the right configurations or not. For that, we can open the **Index.razor** file and we can update the component with the following content.
```razor
@page "/"
@using Volo.Abp.MultiTenancy
@inherits TelerikComponentsComponentBase
@inject ICurrentTenant CurrentTenant
@inject AuthenticationStateProvider AuthenticationStateProvider
@using System.Timers
@implements IDisposable
<TelerikRootComponent>
<div class="progress-bar-wrapper">
<h5 class="progress-info-title">Telerik Progress Bar Component</h5>
<TelerikProgressBar Value="@ProgressValue" Max="100"></TelerikProgressBar>
</div>
</TelerikRootComponent>
@code {
private const int TimerInterval = 1000;
private const int TotalTime = 10 * TimerInterval;
private double ProgressValue = 0;
private int ProgressStep = 100 / (TotalTime / TimerInterval);
private Timer Timer { get; set; } = new Timer();
private void Dispose()
{
StopProgress();
Timer?.Close();
}
protected override void OnAfterRender(bool firstRender)
{
if (Timer.Enabled == false)
{
Timer.Interval = TimerInterval;
Timer.Elapsed -= OnTimerElapsed;
Timer.Elapsed += OnTimerElapsed;
Timer.AutoReset = true;
Timer.Start();
}
}
private void OnTimerElapsed(Object source, ElapsedEventArgs e)
{
if (ProgressValue < 100)
{
UpdateProgress();
}
else
{
StopProgress();
}
}
private void UpdateProgress()
{
ProgressValue += ProgressStep;
InvokeAsync(StateHasChanged);
}
private void StopProgress()
{
Timer?.Stop();
}
}
<style>
.progress-info-title {
font-weight: bold;
font-size: 1.4em;
}
</style>
```
* In here, we've just added the `TelerikProgressBar` component to check the integration configured properly.
* When we run `*.HttpApi.Host` and `*.Blazor` projects, we should see that the `TelerikProgressBar` component works and has similar view as the below gif.
![telerik-progress-bar](./telerik-progress-bar.gif)
* If you haven't seen this component like above, you should check the above configurations and be assure every step done as stated.
### Step 3 - Using The Telerik Blazor Components (Sample Application)
* Let's create a sample application for use other Telerik Blazor Components (like DataGrid).
* We will use [jsonplaceholder](https://jsonplaceholder.typicode.com/) as **mock data** to the listing, adding, updating and deleting posts.
* Firstly, we can create a folder named `Posts` and inside this folder, we can create the classes which are highlighted in the following screenshot.
![sample-application](./sample-application.jpg)
* After classes created we can fill the classes with the following contents.
**Post.cs**
```csharp
using System;
namespace TelerikComponents.Posts
{
[Serializable]
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public int UserId { get; set; }
}
}
```
**Comment.cs**
```csharp
using System;
namespace TelerikComponents.Posts
{
[Serializable]
public class Comment
{
public int PostId { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Body { get; set; }
}
}
```
**IPostAppService.cs**
```csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace TelerikComponents.Posts
{
public interface IPostAppService : IApplicationService
{
Task<List<Post>> GetPostsAsync();
Task<Post> AddPostAsync(Post post);
Task<Post> UpdatePostAsync(int postId, Post post);
Task DeletePostAsync(int postId);
Task<Comment> GetFirstCommentByPostIdAsync(int postId);
}
}
```
* In here, we basically created two class (which are **Post** and **Comment**). These classes are used to hold data returned as JSON.
* After that, we need to implement `IPostAppService`. For achieve this, we can create a folder named `Posts` in **\*.Application** layer and inside this folder we can create a class named `PostAppService` with the following content.
**PostAppService.cs**
```csharp
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace TelerikComponents.Posts
{
public class PostAppService : ApplicationService, IPostAppService
{
private JsonSerializerOptions _options = new JsonSerializerOptions
{
IncludeFields = true,
PropertyNameCaseInsensitive = true
};
public async Task<List<Post>> GetPostsAsync()
{
var url = "https://jsonplaceholder.typicode.com/posts";
List<Post> _posts = new List<Post>();
using (var client = new HttpClient())
{
var result = await client.GetAsync(url);
if (result.IsSuccessStatusCode)
{
var content = await result.Content.ReadAsStringAsync();
var deserializedPosts = JsonSerializer.Deserialize<List<Post>>(content, _options);
_posts = deserializedPosts;
}
}
return _posts;
}
public async Task<Post> AddPostAsync(Post post)
{
var url = "https://jsonplaceholder.typicode.com/posts";
Post addedPost = null;
using (var client = new HttpClient())
{
var serializePost = JsonSerializer.Serialize(post);
var content = new StringContent(serializePost, Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, content);
if (result.IsSuccessStatusCode)
{
var response = await result.Content.ReadAsStringAsync();
addedPost = JsonSerializer.Deserialize<Post>(response);
}
}
return addedPost;
}
public async Task<Post> UpdatePostAsync(int postId, Post post)
{
var url = $"https://jsonplaceholder.typicode.com/posts/{postId}";
Post updatedPost = null;
using (var client = new HttpClient())
{
var serializePost = JsonSerializer.Serialize(post);
var content = new StringContent(serializePost, Encoding.UTF8, "application/json");
var result = await client.PutAsync(url, content);
if (result.IsSuccessStatusCode)
{
var response = await result.Content.ReadAsStringAsync();
updatedPost = JsonSerializer.Deserialize<Post>(response);
}
}
return updatedPost;
}
public async Task DeletePostAsync(int postId)
{
var url = $"https://jsonplaceholder.typicode.com/posts/{postId}";
using (var client = new HttpClient())
{
await client.DeleteAsync(url);
}
}
public async Task<Comment> GetFirstCommentByPostIdAsync(int postId)
{
var url = $"https://jsonplaceholder.typicode.com/posts/{postId}/comments";
List<Comment> _comments = new List<Comment>();
using (var client = new HttpClient())
{
var result = await client.GetAsync(url);
if (result.IsSuccessStatusCode)
{
var content = await result.Content.ReadAsStringAsync();
var deserializedPosts = JsonSerializer.Deserialize<List<Comment>>(content, _options);
_comments = deserializedPosts;
}
}
return _comments[0];
}
}
}
```
* In here, we've implemented `IPostAppService` methods by using [jsonplaceholder](https://jsonplaceholder.typicode.co) API. These endpoints provide us basic crud functionallity.
* After the implemenation, we can start to create the user interface.
#### Blazor UI
* We can create **/Posts** page for listing, updating, deleting and creating our posts. So, create a razor page named `Posts.razor` under **Pages** folder in `*.Blazor` project.
**Posts.razor**
```razor
@page "/Posts"
@using TelerikComponents.Posts
@using IconName = Telerik.Blazor.IconName
@inject IPostAppService PostAppService
<h3>Posts</h3>
<TelerikRootComponent>
<TelerikGrid Data="@GridData"
OnUpdate="@UpdateHandler"
OnDelete="@DeleteHandler"
OnCreate="@CreateHandler"
@ref="@Grid"
Pageable="true"
Groupable="true"
Sortable="true"
FilterMode="GridFilterMode.FilterMenu"
Resizable="true"
Reorderable="true"
EditMode="GridEditMode.Popup"
SelectionMode="GridSelectionMode.Single"
PageSize="5"
Navigable="true">
<GridColumns>
<GridColumn Field="@nameof(Post.Title)"/>
<GridColumn Field="@nameof(Post.Body)"/>
<GridCommandColumn Width="190px">
<GridCommandButton Command="Save" Icon="save" ShowInEdit="true">Update</GridCommandButton>
<GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
<GridCommandButton Command="Delete" Icon="delete" Primary="true">Delete</GridCommandButton>
<GridCommandButton Icon="@IconName.Window" OnClick="(e) => PostDetailAsync(e)">Display Comment</GridCommandButton>
<GridCommandButton Command="Cancel" Icon="cancel" ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
</GridColumns>
<GridToolBar>
<GridCommandButton Command="Add" Icon="add">Add Post</GridCommandButton>
</GridToolBar>
</TelerikGrid>
@* Modal *@
<TelerikWindow Class="demo-window" Width="500px" Height="250px" Centered="true" @bind-Visible=@ModalVisible Modal="true">
<WindowTitle>
<strong>Comment</strong>
</WindowTitle>
<WindowActions>
<WindowAction Name="Close" />
</WindowActions>
<WindowContent>
<p><b>Email:</b> @Comment.Email</p>
<p>
<b>Message:</b> @Comment.Body
</p>
</WindowContent>
</TelerikWindow>
</TelerikRootComponent>
```
**Post.razor.cs**
```csharp
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Telerik.Blazor.Components;
using TelerikComponents.Posts;
namespace TelerikComponents.Blazor.Pages
{
public partial class Posts
{
private List<Post> GridData { get; set; }
private TelerikGrid<Post> Grid { get; set; }
private bool ModalVisible { get; set; } = false;
private Comment Comment { get; set; }
public Posts()
{
Comment = new Comment();
}
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
}
private async Task LoadDataAsync()
{
GridData = await PostAppService.GetPostsAsync();
}
private async Task UpdateHandler(GridCommandEventArgs args)
{
var post = (Post) args.Item;
await PostAppService.UpdatePostAsync(post.Id, post);
var matchingPost = GridData.FirstOrDefault(x => x.Id == post.Id);
if (matchingPost != null)
{
matchingPost.Body = post.Body;
matchingPost.Title = post.Title;
}
}
private async Task DeleteHandler(GridCommandEventArgs args)
{
var post = (Post) args.Item;
GridData.Remove(post);
}
private async Task CreateHandler(GridCommandEventArgs args)
{
var post = (Post) args.Item;
var addedPost = await PostAppService.AddPostAsync(post);
GridData.Insert(0, addedPost);
}
private async Task PostDetailAsync(GridCommandEventArgs args)
{
var post = (Post) args.Item;
Comment = await PostAppService.GetFirstCommentByPostIdAsync(post.Id);
ModalVisible = true;
}
}
}
```
* In here, we've used `TelerikGrid` component.
* The `Telerik Grid` is a powerful component, which allows you to visualize and edit data via its table representation. It provides a variety of options about how to present and perform operations over the underlying data, such as paging, sorting, filtering and editing.
* The Blazor UI Grid allows flexible customization of its items exposing rows, columns and edit templates for this purpose.
### Final Result
* After all of these steps, we can finally run our application.
* Run `*.HttpApi.Host` project for use the required endpoints,
* Run `*.Blazor` project for see the Blazor UI.
* When we navigate to `Posts` route, we should see the following screenshot in this page.
![final-result](./final-result.jpg)
## Conclusion
In this article, I've tried to explain how we can integrate [Telerik Blazor Component](https://www.telerik.com/blazor-ui) to our Blazor UI. ABP Framework designed as modular, so that it can work with any UI library/framework.

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/automated-nuget-feed-setup.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/final-result.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/sample-application.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-blazor-component-1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-progress-bar.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

1581
docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/POST.md

File diff suppressed because it is too large

BIN
docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-crud.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

BIN
docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-with-books.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

BIN
docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-create.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

BIN
docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-delete.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-edit.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-list.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 KiB

50
docs/en/Customizing-Application-Modules-Guide.md

@ -43,17 +43,59 @@ In any case, you can create a **separate solution** for the desired module and d
#### Publishing the Customized Module as Packages
One alternative scenario could be re-packaging the module source code (as NuGet/NPM packages) and using as package references. You can use a local private NuGet/NPM server for your company.
One alternative scenario could be re-packaging the module source code (as NuGet/NPM packages) and using as package references. You can use a local private NuGet/NPM server for your company, for example.
## Module Customization / Extending Approaches
This section suggests some approaches if you decided to use pre-built application modules as NuGet/NPM package references. The following documents explain how to customize/extend existing modules in different ways:
This section suggests some approaches if you decided to use pre-built application modules as NuGet/NPM package references. The following documents explain how to customize/extend existing modules in different ways.
### Module Entity Extension System
> Module entity extension system is the **main and high level extension system** that allows you to **define new properties** for existing entities of the depended modules. It automatically **adds properties to the entity, database, HTTP API and the user interface** in a single point.
See the [Module Entity Extensions document](Module-Entity-Extensions.md) to learn how to use it.
### Extending Entities
If you only need to get/set extra data on an existing entity, follow the [Extending Entities](Customizing-Application-Modules-Extending-Entities.md) document.
### Overriding Services/Components
In addition to the extensibility systems, you can partially or completely override any service or user interface page/component.
* [Extending Entities](Customizing-Application-Modules-Extending-Entities.md)
* [Overriding Services](Customizing-Application-Modules-Overriding-Services.md)
* [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md)
### See Also
### Additional UI Extensibility Points
There are some low level systems that you can control entity actions, table columns and page toolbar of a page defined by a module.
#### Entity Actions
Entity action extension system allows you to add a new action to the action menu for an entity on the user interface;
* [Entity Action Extensions for ASP.NET Core UI](UI/AspNetCore/Entity-Action-Extensions.md)
* [Entity Action Extensions for Angular](UI/Angular/Entity-Action-Extensions.md)
#### Data Table Column Extensions
Data table column extension system allows you to add a new column in the data table on the user interface;
* [Data Table Column Extensions for ASP.NET Core UI](UI/AspNetCore/Data-Table-Column-Extensions.md)
* [Data Table Column Extensions for Angular](UI/Angular/Data-Table-Column-Extensions.md)
#### Page Toolbar
Page toolbar system allows you to add components to the toolbar of a page;
* [Page Toolbar Extensions for ASP.NET Core UI](UI/AspNetCore/Page-Toolbar-Extensions.md)
* [Page Toolbar Extensions for Angular](UI/Angular/Page-Toolbar-Extensions.md)
#### Others
* [Dynamic Form Extensions for Angular](UI/Angular/Dynamic-Form-Extensions.md)
## See Also
Also, see the following documents:

104
docs/en/Module-Entity-Extensions.md

@ -1,5 +1,7 @@
# Module Entity Extensions
> This feature is not supported by the Blazor UI yet.
## Introduction
Module entity extension system is a **high level** extension system that allows you to **define new properties** for existing entities of the depended modules. It automatically **adds properties to the entity, database, HTTP API and the user interface** in a single point.
@ -325,6 +327,108 @@ One of the following names can be used as the localization key:
Localization system searches for the key with the given order. Localized text are used on the table and the create/edit forms.
### Navigation Properties / Foreign Keys
It is supported to add an extension property to an entity that is Id of another entity (foreign key).
#### Example: Associate a department to a user
````csharp
ObjectExtensionManager.Instance.Modules()
.ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<Guid>(
"DepartmentId",
property =>
{
property.UI.Lookup.Url = "/api/departments";
property.UI.Lookup.DisplayPropertyName = "name";
}
);
});
});
````
`UI.Lookup.Url` option takes a URL to get list of departments to select on edit/create forms. This endpoint can be a typical controller, an [auto API controller](API/Auto-API-Controllers.md) or any type of endpoint that returns a proper JSON response.
An example implementation that returns a fixed list of departments (in real life, you get the list from a data source):
````csharp
[Route("api/departments")]
public class DepartmentController : AbpController
{
[HttpGet]
public async Task<ListResultDto<DepartmentDto>> GetAsync()
{
return new ListResultDto<DepartmentDto>(
new[]
{
new DepartmentDto
{
Id = Guid.Parse("6267f0df-870f-4173-be44-d74b4b56d2bd"),
Name = "Human Resources"
},
new DepartmentDto
{
Id = Guid.Parse("21c7b61f-330c-489e-8b8c-80e0a78a5cc5"),
Name = "Production"
}
}
);
}
}
````
This API returns such a JSON response:
````json
{
"items": [{
"id": "6267f0df-870f-4173-be44-d74b4b56d2bd",
"name": "Human Resources"
}, {
"id": "21c7b61f-330c-489e-8b8c-80e0a78a5cc5",
"name": "Production"
}]
}
````
ABP can now show an auto-complete select component to pick the department while creating or editing a user:
![extension-navigation-property-form](images/extension-navigation-property-form.png)
And shows the department name on the data table:
![extension-navigation-property-form](images/extension-navigation-property-table.png)
#### Lookup Options
`UI.Lookup` has the following options to customize how to read the response returned from the `Url`:
* `Url`: The endpoint to get the list of target entities. This is used on edit and create forms.
* `DisplayPropertyName`: The property in the JSON response to read the display name of the target entity to show on the UI. Default: `text`.
* `ValuePropertyName`: The property in the JSON response to read the Id of the target entity. Default: `id`.
* `FilterParamName`: ABP allows to search/filter the entity list on edit/create forms. This is especially useful if the target list contains a lot of items. In this case, you can return a limited list (top 100, for example) and allow user to search on the list. ABP sends filter text to the server (as a simple query string) with the name of this option. Default: `filter`.
* `ResultListPropertyName`: By default, returned JSON result should contain the entity list in an `items` array. You can change the name of this field. Default: `items`.
#### Lookup Properties: How Display Name Works?
You may wonder how ABP shows the department name on the data table above.
It is easy to understand how to fill the dropdown on edit and create forms: ABP makes an AJAX request to the given URL. It re-requests whenever user types to filter the items.
However, for the data table, multiple items are shown on the UI and performing a separate AJAX call to get display name of the department for each row would not be so efficient.
Instead, the display name of the foreign entity is also saved as an extra property of the entity (see *Extra Properties* section of the [Entities](Entities.md) document) in addition to Id of the foreign entity. If you check the database, you can see the `DepartmentId_Text` in the `ExtraProperties` field in the database table:
````json
{"DepartmentId":"21c7b61f-330c-489e-8b8c-80e0a78a5cc5","DepartmentId_Text":"Production"}
````
So, this is a type of *data duplication*. If your target entity's name changes in the database later, there is no automatic synchronization system. The system works as expected, but you see the old name on the data tables. If that's a problem for you, you should care yourself to update this information when display name of your entity changes.
## Database Mapping
For relational databases, all extension property values are stored in a single field in the table:

4
docs/en/Object-Extensions.md

@ -410,3 +410,7 @@ ObjectExtensionManager.Instance
````
See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more.
## See Also
* [Module Entity Extensions](Module-Entity-Extensions.md)

1
docs/en/Road-Map.md

@ -2,6 +2,7 @@
You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. Here, a list of some major items in the backlog;
* CMS Kit: A set of reusable, extensible and composable Content Management System features.
* [#2882](https://github.com/abpframework/abp/issues/2882) / Providing a gRPC integration infrastructure (while it is [already possible](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) to create or consume gRPC endpoints for your application, we plan to create endpoints for the [standard application modules](https://docs.abp.io/en/abp/latest/Modules/Index))
* [#236](https://github.com/abpframework/abp/issues/236) Resource based authorization system
* [#6132](https://github.com/abpframework/abp/issues/6132) A New Theme alternative to the Basic Theme

14
docs/en/Samples/Index.md

@ -27,6 +27,9 @@ While there is no Razor Pages & MongoDB combination, you can check both document
### Other Samples
* **Event Organizer**: A sample application to create events (meetups) and allow others to register the events. Developed using EF Core and Blazor UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer)
* [Article](https://community.abp.io/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z)
* **Entity Framework Migrations**: A solution to demonstrate how to split your application into multiple databases each database contains different modules.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo)
* [EF Core database migrations document](../Entity-Framework-Core-Migrations.md)
@ -59,6 +62,17 @@ While there is no Razor Pages & MongoDB combination, you can check both document
* [Customize the SignIn Manager](https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753)
* **GRPC Demo**: Shows how to add a gRPC service to an ABP Framework based web application and consume it from a console application.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)
* **Telerik Blazor Integration**: Shows how to install and use Telerik Blazor components with the ABP Framework.
* [Article](https://community.abp.io/articles/how-to-integrate-the-telerik-blazor-components-to-the-abp-blazor-ui-q8g31abb)
* **Angular Material Integration**: Implemented the web application tutorial using the Angular Material library.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreAngularMaterial)
* [Article](https://community.abp.io/articles/using-angular-material-components-with-the-abp-framework-af8ft6t9)
* **DevExtreme Angular Component Integration**: How to install and use DevExtreme components in the ABP Framework Angular UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Angular)
* [Article](https://community.abp.io/articles/using-devextreme-angular-components-with-the-abp-framework-x5nyvj3i)
* **DevExtreme MVC / Razor Pages Component Integration**: How to install and use DevExtreme components in the ABP Framework MVC / Razor Pages UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Mvc)
* [Article](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv)
* **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with the ABP Framework installed.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)
* [Documentation](../Getting-Started-AspNetCore-Application.md)

4
docs/en/Text-Templating.md

@ -187,9 +187,9 @@ var result = await _templateRenderer.RenderAsync(
In this case, we haven't created a model class, but created an anonymous object as the model.
### PascalCase vs camelCase
### PascalCase vs snake_case
PascalCase property names (like `UserName`) is used as camelCase (like `userName`) in the templates.
PascalCase property names (like `UserName`) is used as snake_case (like `user_name`) in the templates.
## Localization

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

@ -150,7 +150,7 @@ namespace Acme.BookStore.Authors
}
````
* Inherited from the `EfCoreAuthorRepository`, so it inherits the standard repository method implementations.
* Inherited from the `EfCoreRepository`, so it inherits the standard repository method implementations.
* `WhereIf` is a shortcut extension method of the ABP Framework. It adds the `Where` condition only if the first condition meets (it filters by name, only if the filter was provided). You could do the same yourself, but these type of shortcut methods makes our life easier.
* `sorting` can be a string like `Name`, `Name ASC` or `Name DESC`. It is possible by using the [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet package.
@ -211,7 +211,7 @@ namespace Acme.BookStore.Authors
}
```
* Inherited from the `MongoDbAuthorRepository`, so it inherits the standard repository method implementations.
* Inherited from the `MongoDbRepository`, so it inherits the standard repository method implementations.
* `WhereIf` is a shortcut extension method of the ABP Framework. It adds the `Where` condition only if the first condition meets (it filters by name, only if the filter was provided). You could do the same yourself, but these type of shortcut methods makes our life easier.
* `sorting` can be a string like `Name`, `Name ASC` or `Name DESC`. It is possible by using the [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet package.
@ -221,4 +221,4 @@ namespace Acme.BookStore.Authors
## The Next Part
See the [next part](Part-8.md) of this tutorial.
See the [next part](Part-8.md) of this tutorial.

327
docs/en/UI/Angular/Data-Table-Column-Extensions.md

@ -0,0 +1,327 @@
# Data Table Column (or Entity Prop) Extensions for Angular UI
## Introduction
Entity prop extension system allows you to add a new column to the data table for an entity or change/remove an already existing one. A "Name" column was added to the user management page below:
![Entity Prop Extension Example: "Name" Column](images/user-prop-extension-name-column-ng.png)
You will have access to the current entity in your code and display its value, make the column sortable, perform visibility checks, and more. You can also render custom HTML in table cells.
## How to Set Up
In this example, we will add a "Name" column and display the value of the `name` field in the user management page of the [Identity Module](../../Modules/Identity.md).
### Step 1. Create Entity Prop Contributors
The following code prepares a constant named `identityEntityPropContributors`, ready to be imported and used in your root module:
```js
// entity-prop-contributors.ts
import { EntityProp, EntityPropList, ePropType } from '@abp/ng.theme.shared/extensions';
import { IdentityEntityPropContributors, IdentityUserDto } from '@volo/abp.ng.identity';
const nameProp = new EntityProp<IdentityUserDto>({
type: ePropType.String,
name: 'name',
displayName: 'AbpIdentity::Name',
sortable: true,
columnWidth: 250,
});
export function namePropContributor(propList: EntityPropList<IdentityUserDto>) {
propList.addAfter(
nameProp,
'userName',
(value, name) => value.name === name,
);
}
export const identityEntityPropContributors: IdentityEntityPropContributors = {
'Identity.UsersComponent': [namePropContributor],
};
```
The list of props, conveniently named as `propList`, is a **doubly linked list**. That is why we have used the `addAfter` method, which adds a node with given value after the first node that has the previous value. You may find [all available methods here](../Common/Utils/Linked-List.md).
> **Important Note 1:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `namePropContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details.
> **Important Note 2:** Please use one of the following if Ivy is not enabled in your project. Otherwise, you will get an "Expression form not supported." error.
```js
export const identityEntityPropContributors: IdentityEntityPropContributors = {
'Identity.UsersComponent': [ namePropContributor ],
};
/* OR */
const identityContributors: IdentityEntityPropContributors = {};
identityContributors[eIdentityComponents.Users] = [ namePropContributor ];
export const identityEntityPropContributors = identityContributors;
```
### Step 2. Import and Use Entity Prop Contributors
Import `identityEntityPropContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
```js
import { identityEntityPropContributors } from './entity-prop-contributors';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: 'identity',
loadChildren: () =>
import('@volo/abp.ng.identity').then(m =>
m.IdentityModule.forLazy({
entityPropContributors: identityEntityPropContributors,
}),
),
},
// other child routes
],
// other routes
}
];
```
That is it, `nameProp` entity prop will be added, and you will see the "Name" column next to the usernames on the grid in the users page (`UsersComponent`) of the `IdentityModule`.
## How to Render Custom HTML in Cells
You can use the `valueResolver` to render an HTML string in the table. Imagine we want to show a red times icon (❌) next to unconfirmed emails and phones, instead of showing a green check icon next to confirmed emails and phones. The contributors below would do that for you.
```js
// entity-prop-contributors.ts
import { EntityProp, EntityPropList, ePropType } from '@abp/ng.theme.shared/extensions';
import { IdentityUserDto } from '@volo/abp.ng.identity';
import { IdentityEntityPropContributors } from '@volo/abp.ng.identity/config';
export function emailPropContributor(propList: EntityPropList<IdentityUserDto>) {
const index = propList.indexOf('email', (value, name) => value.name === name);
const droppedNode = propList.dropByIndex(index);
const emailProp = new EntityProp<IdentityUserDto>({
...droppedNode.value,
valueResolver: data => {
const { email, emailConfirmed } = data.record;
const icon = email && !emailConfirmed ? `<i class="fa fa-times text-danger ml-1"></i>` : '';
return of((email || '') + icon); // should return an observable
},
});
propList.addByIndex(emailProp, index);
}
export function phonePropContributor(propList: EntityPropList<IdentityUserDto>) {
const index = propList.indexOf('phoneNumber', (value, name) => value.name === name);
const droppedNode = propList.dropByIndex(index);
const phoneProp = new EntityProp<IdentityUserDto>({
...droppedNode.value,
valueResolver: data => {
const { phoneNumber, phoneNumberConfirmed } = data.record;
const icon =
phoneNumber && !phoneNumberConfirmed ? `<i class="fa fa-times text-danger ml-1"></i>` : '';
return of((phoneNumber || '') + icon); // should return an observable
},
});
propList.addByIndex(phoneProp, index);
}
export const identityEntityPropContributors: IdentityEntityPropContributors = {
'Identity.UsersComponent': [emailPropContributor, phonePropContributor],
};
```
> The `valueResolver` method should return an observable. You can wrap your return values with `of` from RxJS for that.
## Object Extensions
Extra properties defined on an existing entity will be included in the table based on their configuration. The values will also be mapped to and from `extraProperties` automatically. They are available when defining custom contributors, so you can drop, modify, or reorder them. The `isExtra` identifier will be set to `true` for these properties and will define this automatic behavior.
## API
### PropData\<R = any\>
`PropData` is the shape of the parameter passed to all callbacks or predicates in an `EntityProp`.
It has the following properties:
- **record** is the row data, i.e. current value rendered in the table.
```js
{
type: ePropType.String,
name: 'name',
valueResolver: data => {
const name = data.record.name || '';
return of(name.toUpperCase());
},
}
```
- **index** is the table index where the record is at.
- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `ExtensibleTableComponent`, including, but not limited to, its parent component.
```js
{
type: ePropType.String,
name: 'name',
valueResolver: data => {
const restService = data.getInjected(RestService);
const usersComponent = data.getInjected(UsersComponent);
// Use restService and usersComponent public props and methods here
},
}
```
### PropCallback\<T, R = any\>
`PropCallback` is the type of the callback function that can be passed to an `EntityProp` as `prop` parameter. A prop callback gets a single parameter, the `PropData`. The return type may be anything, including `void`. Here is a simplified representation:
```js
type PropCallback<T, R = any> = (data?: PropData<T>) => R;
```
### PropPredicate\<T\>
`PropPredicate` is the type of the predicate function that can be passed to an `EntityProp` as `visible` parameter. A prop predicate gets a single parameter, the `PropData`. The return type must be `boolean`. Here is a simplified representation:
```js
type PropPredicate<T> = (data?: PropData<T>) => boolean;
```
### EntityPropOptions\<R = any\>
`EntityPropOptions` is the type that defines required and optional properties you have to pass in order to create an entity prop.
Its type definition is as follows:
```js
type EntityPropOptions<R = any> = {
type: ePropType;
name: string;
displayName?: string;
valueResolver?: PropCallback<R, Observable<any>>;
sortable?: boolean;
columnWidth?: number;
permission?: string;
visible?: PropPredicate<R>;
};
```
As you see, passing `type` and `name` is enough to create an entity prop. Here is what each property is good for:
- **type** is the type of the prop value. It is used for custom rendering in the table. (_required_)
- **name** is the property name (or key) which will be used to read the value of the prop. (_required_)
- **displayName** is the name of the property which will be localized and shown as column header. (_default:_ `options.name`)
- **valueResolver** is a callback that is called when the cell is rendered. It must return an observable. (_default:_ `data => of(data.record[options.name])`)
- **sortable** defines if the table is sortable based on this entity prop. Sort icons are shown based on it. (_default:_ `false`)
- **columnWidth** defines a minimum width for the column. Good for horizontal scroll. (_default:_ `undefined`)
- **permission** is the permission context which will be used to decide if a column for this entity prop should be displayed to the user or not. (_default:_ `undefined`)
- **visible** is a predicate that will be used to decide if this entity prop should be displayed on the table or not. (_default:_ `() => true`)
> Important Note: Do not use record in visibility predicates. First of all, the table header checks it too and the record will be `undefined`. Second, if some cells are displayed and others are not, the table will be broken. Use the `valueResolver` and render an empty cell when you need to hide a specific cell.
You may find a full example below.
### EntityProp\<R = any\>
`EntityProp` is the class that defines your entity props. It takes an `EntityPropOptions` and sets the default values to the properties, creating an entity prop that can be passed to an entity contributor.
```js
const options: EntityPropOptions<IdentityUserDto> = {
type: ePropType.String,
name: 'email',
displayName: 'AbpIdentity::EmailAddress',
valueResolver: data => {
const { email, emailConfirmed } = data.record;
return of(
(email || '') + (emailConfirmed ? `<i class="fa fa-check text-success ml-1"></i>` : ''),
);
},
sortable: true,
columnWidth: 250,
permission: 'AbpIdentity.Users.ReadSensitiveData', // hypothetical
visible: data => {
const store = data.getInjected(Store);
const selectSensitiveDataVisibility = ConfigState.getSetting(
'Abp.Identity.IsSensitiveDataVisible' // hypothetical
);
return store.selectSnapshot(selectSensitiveDataVisibility).toLowerCase() === 'true';
}
};
const prop = new EntityProp(options);
```
It also has two static methods to create its instances:
- **EntityProp.create\<R = any\>\(options: EntityPropOptions\<R\>\)** is used to create an instance of `EntityProp`.
```js
const prop = EntityProp.create(options);
```
- **EntityProp.createMany\<R = any\>\(options: EntityPropOptions\<R\>\[\]\)** is used to create multiple instances of `EntityProp` with given array of `EntityPropOptions`.
```js
const props = EntityProp.createMany(optionsArray);
```
### EntityPropList\<R = any\>
`EntityPropList` is the list of props passed to every prop contributor callback as the first parameter named `propList`. It is a **doubly linked list**. You may find [all available methods here](../Common/Utils/Linked-List.md).
The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this:
```js
export function reorderUserContributors(
propList: EntityPropList<IdentityUserDto>,
) {
// drop email node
const emailPropNode = propList.dropByValue(
'AbpIdentity::EmailAddress',
(prop, text) => prop.text === text,
);
// add it back after phoneNumber
propList.addAfter(
emailPropNode.value,
'phoneNumber',
(value, name) => value.name === name,
);
}
```
### EntityPropContributorCallback\<R = any\>
`EntityPropContributorCallback` is the type that you can pass as entity prop contributor callbacks to static `forLazy` methods of the modules.
```js
export function isLockedOutPropContributor(
propList: EntityPropList<IdentityUserDto>,
) {
// add isLockedOutProp as 2nd column
propList.add(isLockedOutProp).byIndex(1);
}
export const identityEntityPropContributors = {
[eIdentityComponents.Users]: [isLockedOutPropContributor],
};
```
## See Also
- [Customizing Application Modules Guide](../../Customizing-Application-Modules-Guide.md)

325
docs/en/UI/Angular/Dynamic-Form-Extensions.md

@ -0,0 +1,325 @@
# Dynamic Form (or Form Prop) Extensions for Angular UI
## Introduction
Form prop extension system allows you to add a new field to the create and/or edit forms for a form or change/remove an already existing one. A "Date of Birth" field was added to the user management page below:
![Form Prop Extension Example: "Date of Birth" Field](images/user-prop-extension-date-of-birth-field-ng.png)
You can validate the field, perform visibility checks, and do more. You will also have access to the current entity when creating a contibutor for an edit form.
## How to Set Up
In this example, we will add a "Date of Birth" field in the user management page of the [Identity Module](../../Modules/Identity.md) and validate it.
### Step 1. Create Form Prop Contributors
The following code prepares two constants named `identityCreateFormPropContributors` and `identityEditFormPropContributors`, ready to be imported and used in your root module:
```js
// form-prop-contributors.ts
import { Validators } from '@angular/forms';
import { ePropType, FormProp, FormPropList } from '@abp/ng.theme.shared/extensions';
import { IdentityCreateFormPropContributors, IdentityEditFormPropContributors, IdentityUserDto } from '@volo/abp.ng.identity';
const birthdayProp = new FormProp<IdentityUserDto>({
type: ePropType.Date,
name: 'birthday',
displayName: 'Date of Birth',
validators: () => [Validators.required],
});
export function birthdayPropContributor(propList: FormPropList<IdentityUserDto>) {
propList.addByIndex(birthdayProp, 4);
}
export const identityCreateFormPropContributors: IdentityCreateFormPropContributors = {
'Identity.UsersComponent': [birthdayPropContributor],
};
export const identityEditFormPropContributors: IdentityEditFormPropContributors = {
'Identity.UsersComponent': [birthdayPropContributor],
};
```
The list of props, conveniently named as `propList`, is a **doubly linked list**. That is why we have used the `addByIndex` method, which adds the given value to the specified index of the list. You may find [all available methods here](../Common/Utils/Linked-List.md).
> **Important Note 1:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `birthdayPropContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details.
> **Important Note 2:** Please use one of the following if Ivy is not enabled in your project. Otherwise, you will get an "Expression form not supported." error.
```js
export const identityCreateFormPropContributors: IdentityCreateFormPropContributors = {
'Identity.UsersComponent': [ birthdayPropContributor ],
};
/* OR */
const identityCreateContributors: IdentityCreateFormPropContributors = {};
identityCreateContributors[eIdentityComponents.Users] = [ birthdayPropContributor ];
export const identityCreateFormPropContributors = identityCreateContributors;
```
### Step 2. Import and Use Form Prop Contributors
Import `identityCreateFormPropContributors` and `identityEditFormPropContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
```js
import {
identityCreateFormPropContributors,
identityEditFormPropContributors,
} from './form-prop-contributors';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: 'identity',
loadChildren: () =>
import('@volo/abp.ng.identity').then(m =>
m.IdentityModule.forLazy({
createFormPropContributors: identityCreateFormPropContributors,
editFormPropContributors: identityEditFormPropContributors,
}),
),
},
// other child routes
],
// other routes
}
];
```
That is it, `birthdayProp` form prop will be added, and you will see the datepicker for the "Date of Birth" field right before the "Email address" in the forms of the users page in the `IdentityModule`.
## Object Extensions
Extra properties defined on an existing entity will be included in the create and edit forms and validated based on their configuration. The form values will also be mapped to and from `extraProperties` automatically. They are available when defining custom contributors, so you can drop, modify, or reorder them. The `isExtra` identifier will be set to `true` for these properties and will define this automatic behavior.
## API
### PropData\<R = any\>
`PropData` is the shape of the parameter passed to all callbacks or predicates in a `FormProp`.
It has the following properties:
- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `ExtensibleFormPropComponent`, including, but not limited to, its parent components.
```js
{
type: ePropType.Enum,
name: 'myField',
options: data => {
const restService = data.getInjected(RestService);
const usersComponent = data.getInjected(UsersComponent);
// Use restService and usersComponent public props and methods here
}
},
```
- **record** is the row data, i.e. current value of the selected item to edit. This property is _available only on edit forms_.
```js
{
type: ePropType.String,
name: 'myProp',
readonly: data => data.record.someOtherProp,
}
```
### PropCallback\<T, R = any\>
`PropCallback` is the type of the callback function that can be passed to a `FormProp` as `prop` parameter. A prop callback gets a single parameter, the `PropData`. The return type may be anything, including `void`. Here is a simplified representation:
```js
type PropCallback<T, R = any> = (data?: PropData<T>) => R;
```
### PropPredicate\<T\>
`PropPredicate` is the type of the predicate function that can be passed to a `FormProp` as `visible` parameter. A prop predicate gets a single parameter, the `PropData`. The return type must be `boolean`. Here is a simplified representation:
```js
type PropPredicate<T> = (data?: PropData<T>) => boolean;
```
### FormPropOptions\<R = any\>
`FormPropOptions` is the type that defines required and optional properties you have to pass in order to create a form prop.
Its type definition is as follows:
```js
type FormPropOptions<R = any> = {
type: ePropType;
name: string;
displayName?: string;
id?: string;
permission?: string;
visible?: PropPredicate<R>;
readonly?: PropPredicate<R>;
disabled?: PropPredicate<R>;
validators?: PropCallback<R, ValidatorFn[]>;
asyncValidators?: PropCallback<R, AsyncValidatorFn[]>;
defaultValue?: boolean | number | string | Date;
options?: PropCallback<R, Observable<ABP.Option<any>[]>>;
autocomplete?: string;
isExtra? boolean;
};
```
As you see, passing `type` and `name` is enough to create a form prop. Here is what each property is good for:
- **type** is the type of the prop value. It defines which input is rendered for the prop in the form. (_required_)
- **name** is the property name (or key) which will be used to read the value of the prop. (_required_)
- **displayName** is the name of the property which will be localized and shown as column header. (_default:_ `options.name`)
- **id** will be set as the `for` attribute of the label and the `id` attribute of the input for the field. (_default:_ `options.name`)
- **permission** is the permission context which will be used to decide if a column for this form prop should be displayed to the user or not. (_default:_ `undefined`)
- **visible** is a predicate that will be used to decide if this prop should be displayed on the form or not. (_default:_ `() => true`)
- **readonly** is a predicate that will be used to decide if this prop should be readonly or not. (_default:_ `() => false`)
- **disabled** is a predicate that will be used to decide if this prop should be disabled or not. (_default:_ `() => false`)
- **validators** is a callback that returns validators for the prop. (_default:_ `() => []`)
- **asyncValidators** is a callback that returns async validators for the prop. (_default:_ `() => []`)
- **defaultValue** is the initial value the field will have. (_default:_ `null`)
- **options** is a callback that is called when a dropdown is needed. It must return an observable. (_default:_ `undefined`)
- **autocomplete** will be set as the `autocomplete` attribute of the input for the field. Please check [possible values](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#Values). (_default:_ `'off'`)
- **isExtra** indicates this prop is an object extension. When `true`, the value of the field will be mapped from and to `extraProperties` of the entity. (_default:_ `undefined`)
> Important Note: Do not use `record` property of `PropData` in create form predicates and callbacks, because it will be `undefined`. You can use it on edit form contributors though.
You may find a full example below.
### FormProp\<R = any\>
`FormProp` is the class that defines your form props. It takes a `FormPropOptions` and sets the default values to the properties, creating a form prop that can be passed to a form contributor.
```js
const options: FormPropOptions<IdentityUserDto> = {
type: ePropType.Enum,
name: 'myProp',
displayName: 'Default::MyPropName',
id: 'my-prop',
permission: 'AbpIdentity.Users.ReadSensitiveData', // hypothetical
visible: data => {
const store = data.getInjected(Store);
const selectSensitiveDataVisibility = ConfigState.getSetting(
'Abp.Identity.IsSensitiveDataVisible' // hypothetical
);
return store.selectSnapshot(selectSensitiveDataVisibility).toLowerCase() === 'true';
},
readonly: data => data.record.someProp,
disabled: data => data.record.someOtherProp,
validators: () => [Validators.required],
asyncValidators: data => {
const http = data.getInjected(HttpClient);
function validate(control: AbstractControl): Observable<ValidationErrors | null> {
if (control.pristine) return of(null);
return http
.get('https://api.my-brand.io/hypothetical/endpoint/' + control.value)
.pipe(map(response => (response.valid ? null : { invalid: true })));
}
return [validate];
},
defaultValue: 0,
options: data => {
const service = data.getInjected(MyIdentityService);
return service.getMyPropOptions()
.pipe(
map(({items}) => items.map(
item => ({key: item.name, value: item.id })
)),
);
},
autocomplete: 'off',
isExtra: true,
};
const prop = new FormProp(options);
```
It also has two static methods to create its instances:
- **FormProp.create\<R = any\>\(options: FormPropOptions\<R\>\)** is used to create an instance of `FormProp`.
```js
const prop = FormProp.create(options);
```
- **FormProp.createMany\<R = any\>\(options: FormPropOptions\<R\>\[\]\)** is used to create multiple instances of `FormProp` with given array of `FormPropOptions`.
```js
const props = FormProp.createMany(optionsArray);
```
### FormPropList\<R = any\>
`FormPropList` is the list of props passed to every prop contributor callback as the first parameter named `propList`. It is a **doubly linked list**. You may find [all available methods here](../Common/Utils/Linked-List.md).
The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this:
```js
export function reorderUserContributors(
propList: FormPropList<IdentityUserDto>,
) {
// drop email node
const emailPropNode = propList.dropByValue(
'AbpIdentity::EmailAddress',
(prop, displayName) => prop.displayName === displayName,
);
// add it back after phoneNumber
propList.addAfter(
emailPropNode.value,
'phoneNumber',
(value, name) => value.name === name,
);
}
```
### CreateFormPropContributorCallback\<R = any\>
`CreateFormPropContributorCallback` is the type that you can pass as **create form** prop contributor callbacks to static `forLazy` methods of the modules.
```js
export function myPropCreateContributor(
propList: FormPropList<IdentityUserDto>,
) {
// add myProp as 2nd field from the start
propList.add(myProp).byIndex(1);
}
export const identityCreateFormPropContributors = {
[eIdentityComponents.Users]: [myPropCreateContributor],
};
```
### EditFormPropContributorCallback\<R = any\>
`EditFormPropContributorCallback` is the type that you can pass as **edit form** prop contributor callbacks to static `forLazy` methods of the modules.
```js
export function myPropEditContributor(
propList: FormPropList<IdentityUserDto>,
) {
// add myProp as 2nd field from the end
propList.add(myProp).byIndex(-1);
}
export const identityEditFormPropContributors = {
[eIdentityComponents.Users]: [myPropEditContributor],
};
```
## See Also
- [Customizing Application Modules Guide](../../Customizing-Application-Modules-Guide.md)

442
docs/en/UI/Angular/Entity-Action-Extensions.md

@ -0,0 +1,442 @@
# Entity Action Extensions for Angular UI
## Introduction
Entity action extension system allows you to add a new action to the action menu for an entity. A "Click Me" action was added to the user management page below:
![Entity Action Extension Example: "Click Me!" Action](images/user-action-extension-click-me-ng.png)
You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can access to the current entity in your code.
## How to Set Up
In this example, we will add a "Click Me!" action and alert the current row's `userName` in the user management page of the [Identity Module](../../Modules/Identity.md).
### Step 1. Create Entity Action Contributors
The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root module:
```js
// entity-action-contributors.ts
import { EntityAction, EntityActionList } from '@abp/ng.theme.shared/extensions';
import { IdentityEntityActionContributors, IdentityUserDto } from '@volo/abp.ng.identity';
const alertUserName = new EntityAction<IdentityUserDto>({
text: 'Click Me!',
action: data => {
// Replace alert with your custom code
alert(data.record.userName);
},
// See EntityActionOptions in API section for all options
});
export function alertUserNameContributor(
actionList: EntityActionList<IdentityUserDto>,
) {
actionList.addTail(alertUserName);
}
export const identityEntityActionContributors: IdentityEntityActionContributors = {
// enum indicates the page to add contributors to
[eIdentityComponents.Users]: [
alertUserNameContributor,
// You can add more contributors here
],
};
```
The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addTail` method, which adds the given value to the end of the list. You may find [all available methods here](../Common/Utils/Linked-List.md).
> **Important Note 1:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `alertUserNameContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details.
> **Important Note 2:** Please use one of the following if Ivy is not enabled in your project. Otherwise, you will get an "Expression form not supported." error.
```js
export const identityEntityActionContributors: IdentityEntityActionContributors = {
'Identity.UsersComponent': [ alertUserNameContributor ],
};
/* OR */
const identityContributors: IdentityEntityActionContributors = {};
identityContributors[eIdentityComponents.Users] = [ alertUserNameContributor ];
export const identityEntityActionContributors = identityContributors;
```
### Step 2. Import and Use Entity Action Contributors
Import `identityEntityActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
```js
import { identityEntityActionContributors } from './entity-action-contributors';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: 'identity',
loadChildren: () =>
import('@volo/abp.ng.identity').then(m =>
m.IdentityModule.forLazy({
entityActionContributors: identityEntityActionContributors,
}),
),
},
// other child routes
],
// other routes
}
];
```
That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the users page (`UsersComponent`) of the `IdentityModule`.
## How to Place a Custom Modal and Trigger It by Entity Actions
Incase you need to place a custom modal that will be triggered by an entity action, there are two ways to do it: A quick one and an elaborate one.
### The Quick Solution
1. Place your custom modal inside `AppComponent` template.
```html
<abp-modal [(visible)]="isModalOpen">
<ng-template #abpHeader>
<h3><!-- YOUR TITLE HERE --></h3>
</ng-template>
<ng-template #abpBody>
<!-- YOUR CONTENT HERE -->
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpIdentity::Cancel' | abpLocalization }}}%}
</button>
<!-- YOUR CONFIRMATION BUTTON HERE -->
</ng-template>
</abp-modal>
```
2. Add the following inside your `AppComponent` class:
```js
isModalOpen: boolean;
openModal(/* may take parameters */) {
/* and set things before showing the modal */
this.isModalOpen = true;
}
```
3. Add an entity action similar to this:
```js
const customModalAction = new EntityAction<IdentityUserDto>({
text: 'Custom Modal Action',
action: data => {
const component = data.getInjected(AppComponent);
component.openModal(/* you may pass parameters */);
},
});
```
That should work. However, there is a longer but lazy-loading solution, and we are going to use NGXS for it.
### The Elaborate Solution
Consider the modal will be displayed in the Identity module. How can we lazy-load it too?
1. Create a folder called `identity-extended` inside your app folder.
2. Create a file called `identity-popups.store.ts` in it.
3. Insert the following code in the new file:
```js
import { Action, Selector, State, StateContext } from '@ngxs/store';
export class ToggleIdentityPopup {
static readonly type = '[IdentityPopups] Toggle';
constructor(public readonly payload: boolean) {}
}
@State<IdentityPopupsStateModel>({
name: 'IdentityPopups',
defaults: {
isVisible: false,
},
})
export class IdentityPopupsState {
@Selector()
static isVisible(state: IdentityPopupsStateModel) {
return state.isVisible;
}
@Action(ToggleIdentityPopup)
toggleModal(
context: StateContext<IdentityPopupsStateModel>,
{ payload }: ToggleIdentityPopup,
) {
context.patchState({ isVisible: payload });
}
}
interface IdentityPopupsStateModel {
isVisible: boolean;
}
```
4. Create a file called `identity-extended.module.ts` in the same folder.
5. Insert the following code in the new file:
```js
import { CoreModule } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { Component, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { NgxsModule, Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { IdentityPopupsState, ToggleIdentityPopup } from './identity-popups.store';
@Component({
template: `
<router-outlet></router-outlet>
<router-outlet name="popup"></router-outlet>
`,
})
export class IdentityOutletComponent {}
@Component({
template: `
<abp-modal [visible]="isVisible$ | async" (disappear)="onDisappear()">
<ng-template #abpHeader>
<h3><!-- YOUR TITLE HERE --></h3>
</ng-template>
<ng-template #abpBody>
<!-- YOUR CONTENT HERE -->
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpIdentity::Cancel' | abpLocalization }}}%}
</button>
<!-- YOUR CONFIRMATION BUTTON HERE -->
</ng-template>
</abp-modal>
`,
})
export class IdentityPopupsComponent {
@Select(IdentityPopupsState.isVisible)
isVisible$: Observable<boolean>;
constructor(private store: Store) {}
onDisappear() {
this.store.dispatch(new ToggleIdentityPopup(false));
}
}
@NgModule({
declarations: [IdentityPopupsComponent, IdentityOutletComponent],
imports: [
CoreModule,
ThemeSharedModule,
NgxsModule.forFeature([IdentityPopupsState]),
RouterModule.forChild([
{
path: '',
component: IdentityOutletComponent,
children: [
{
path: '',
outlet: 'popup',
component: IdentityPopupsComponent,
},
{
path: '',
loadChildren: () => import('@volo/abp.ng.identity').then(m => m.IdentityModule),
},
],
},
]),
],
})
export class IdentityExtendedModule {}
```
6. Change the `identity` path in your `AppRoutingModule` to this:
```js
{
path: 'identity',
loadChildren: () =>
import('./identity-extended/identity-extended.module').then(m => m.IdentityExtendedModule),
},
```
7. Add an entity action similar to this:
```js
const customModalAction = new EntityAction<IdentityUserDto>({
text: 'Custom Modal Action',
action: data => {
const store = data.getInjected(Store);
store.dispatch(new ToggleIdentityPopup(true));
},
});
```
It should now be working well with lazy-loading. The files are compact in the description to make it quicker to explain. You may split the files as you wish.
## API
### ActionData\<R = any\>
`ActionData` is the shape of the parameter passed to all callbacks or predicates in an `EntityAction`.
It has the following properties:
- **record** is the row data, i.e. current value rendered in the table.
```js
{
text: 'Click Me!',
action: data => {
alert(data.record.userName);
},
}
```
- **index** is the table index where the record is at.
- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `GridActionsComponent`, including, but not limited to, its parent component.
```js
{
text: 'Click Me!',
action: data => {
const restService = data.getInjected(RestService);
// Use restService public props and methods here
},
visible: data => {
const usersComponent = data.getInjected(UsersComponent);
// Use usersComponent public props and methods here
},
}
```
### ActionCallback\<T, R = any\>
`ActionCallback` is the type of the callback function that can be passed to an `EntityAction` as `action` parameter. An action callback gets a single parameter, the `ActionData`. The return type may be anything, including `void`. Here is a simplified representation:
```js
type ActionCallback<T, R = any> = (data?: ActionData<T>) => R;
```
### ActionPredicate\<T\>
`ActionPredicate` is the type of the predicate function that can be passed to an `EntityAction` as `visible` parameter. An action predicate gets a single parameter, the `ActionData`. The return type must be `boolean`. Here is a simplified representation:
```js
type ActionPredicate<T> = (data?: ActionData<T>) => boolean;
```
### EntityActionOptions\<R = any\>
`EntityActionOptions` is the type that defines required and optional properties you have to pass in order to create an entity action.
Its type definition is as follows:
```js
type EntityActionOptions<R = any> = {
action: ActionCallback<R>,
text: string,
icon?: string,
permission?: string,
visible?: ActionPredicate<R>,
};
```
As you see, passing `action` and `text` is enough to create an entity action. Here is what each property is good for:
- **action** is a callback that is called when the grid action is clicked. (_required_)
- **text** is the button text which will be localized. (_required_)
- **icon** is the classes that define an icon to be placed before the text. (_default:_ `''`)
- **permission** is the permission context which will be used to decide if this type of grid action should be displayed to the user or not. (_default:_ `undefined`)
- **visible** is a predicate that will be used to decide if the current record should have this grid action or not. (_default:_ `() => true`)
You may find a full example below.
### EntityAction\<R = any\>
`EntityAction` is the class that defines your entity actions. It takes an `EntityActionOptions` and sets the default values to the properties, creating an entity action that can be passed to an entity contributor.
```js
const options: EntityActionOptions<IdentityUserDto> = {
action: data => {
const component = data.getInjected(UsersComponent);
component.unlock(data.record.id);
},
text: 'AbpIdentity::Unlock',
icon: 'fa fa-unlock',
permission: 'AbpIdentity.Users.Update',
visible: data => data.record.isLockedOut,
};
const action = new EntityAction(options);
```
It also has two static methods to create its instances:
- **EntityAction.create\<R = any\>\(options: EntityActionOptions\<R\>\)** is used to create an instance of `EntityAction`.
```js
const action = EntityAction.create(options);
```
- **EntityAction.createMany\<R = any\>\(options: EntityActionOptions\<R\>\[\]\)** is used to create multiple instances of `EntityAction` with given array of `EntityActionOptions`.
```js
const actions = EntityAction.createMany(optionsArray);
```
### EntityActionList\<R = any\>
`EntityActionList` is the list of actions passed to every action contributor callback as the first parameter named `actionList`. It is a **doubly linked list**. You may find [all available methods here](../Common/Utils/Linked-List.md).
The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this:
```js
export function reorderUserContributors(
actionList: EntityActionList<IdentityUserDto>,
) {
// drop "Unlock" button
const unlockActionNode = actionList.dropByValue(
'AbpIdentity::Unlock',
(action, text) => action.text === text,
);
// add it back to the head of the list
actionList.addHead(unlockActionNode.value);
}
```
### EntityActionContributorCallback\<R = any\>
`EntityActionContributorCallback` is the type that you can pass as entity action contributor callbacks to static `forLazy` methods of the modules.
```js
// lockUserContributor should have EntityActionContributorCallback<IdentityUserDto> type
export function lockUserContributor(
actionList: EntityActionList<IdentityUserDto>,
) {
// add lockUser as 3rd action
actionList.add(lockUser).byIndex(2);
}
export const identityEntityActionContributors = {
[eIdentityComponents.Users]: [lockUserContributor],
};
```
## See Also
- [Customizing Application Modules Guide](../../Customizing-Application-Modules-Guide.md)

66
docs/en/UI/Angular/Form-Validation.md

@ -6,23 +6,52 @@ Reactive forms in ABP Angular UI are validated by [ngx-validate](https://www.npm
## How to Add New Error Messages
You can add a new error message by providing the `VALIDATION_BLUEPRINTS` injection token from your root module.
You can add a new error message by passing validation options to the `ThemeSharedModule` in your root module.
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
@NgModule({
imports: [
ThemeSharedModule.forRoot({
validation: {
blueprints: {
uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]",
},
},
// rest of theme shared config
}),
// other imports
],
// rest of the module metadata
})
export class AppModule {}
```
Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root module. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work.
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
@NgModule({
providers: [
// other providers
{
provide: VALIDATION_BLUEPRINTS,
useValue: {
...DEFAULT_VALIDATION_BLUEPRINTS,
uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]",
},
},
// other providers
],
// rest of the module metadata
})
export class AppModule {}
```
@ -40,7 +69,7 @@ In this example;
## How to Change Existing Error Messages
You can overwrite an existing error message by providing `VALIDATION_BLUEPRINTS` injection token from your root module. Let's imagine you have a custom localization resource for required inputs.
You can overwrite an existing error message by passing validation options to the `ThemeSharedModule` in your root module. Let's imagine you have a custom localization resource for required inputs.
```json
"RequiredInput": "Oops! We need this input."
@ -50,19 +79,48 @@ To use this instead of the built-in required input message, all you need to do i
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
@NgModule({
imports: [
ThemeSharedModule.forRoot({
validation: {
blueprints: {
required: "::RequiredInput",
},
},
// rest of theme shared config
}),
// other imports
],
// rest of the module metadata
})
export class AppModule {}
```
Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root module. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work.
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
@NgModule({
providers: [
// other providers
{
provide: VALIDATION_BLUEPRINTS,
useValue: {
...DEFAULT_VALIDATION_BLUEPRINTS,
required: "::RequiredInput",
},
},
// other providers
],
// rest of the module metadata
})
export class AppModule {}
```

13
docs/en/UI/Angular/List-Service.md

@ -1,6 +1,6 @@
# Working with Lists
`ListService` is a utility service to provide an easy pagination, sorting, and search implementation.
`ListService` is a utility service to provide easy pagination, sorting, and search implementation.
@ -35,7 +35,10 @@ class BookComponent {
constructor(
public readonly list: ListService,
private bookService: BookService,
) {}
) {
// change ListService defaults here
this.list.maxResultCount = 20;
}
ngOnInit() {
// A function that gets query and returns an observable
@ -164,7 +167,7 @@ You may use observables in combination with [AsyncPipe](https://angular.io/guide
</ngx-datatable>
```
> We do not recommend using NGXS store for CRUD pages, unless your application needs to share list information between components or use it later on in another page.
> We do not recommend using the NGXS store for CRUD pages unless your application needs to share list information between components or use it later on in another page.
## How to Refresh Table on Create/Update/Delete
@ -186,7 +189,7 @@ You may use observables in combination with [AsyncPipe](https://angular.io/guide
this.store.dispatch(new DeleteBook(id)).subscribe(this.list.get);
```
> We donot recommend using NGXS store for CRUD pages, unless your application needs to share list information between components or use it later on in another page.
> We do not recommend using the NGXS store for CRUD pages unless your application needs to share list information between components or use it later on in another page.
## How to Implement Server-Side Search in a Table
@ -210,7 +213,7 @@ We had to modify the `ListService` to make it work with `ngx-datatable`. Previou
></abp-table>
```
As of v3.0, with ngx-datatable, the `page` property has to be set as `0` for inital page. Therefore, if you used `ListService` on your tables before and are going to keep `abp-table`, you need to make the following change:
As of v3.0, with ngx-datatable, the `page` property has to be set as `0` for the initial page. Therefore, if you used `ListService` on your tables before and are going to keep `abp-table`, you need to make the following change:
```html
<!-- other bindings are hidden in favor of brevity -->

420
docs/en/UI/Angular/Page-Toolbar-Extensions.md

@ -0,0 +1,420 @@
# Page Toolbar Extensions for Angular UI
## Introduction
Page toolbar extension system allows you to add a new action to the toolbar of a page. A "Click Me" action was added to the user management page below:
![Page Toolbar Extension Example: "Click Me!" Action](images/user-page-toolbar-extension-click-me-ng.png)
You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access to page data (the main record, usually an entity list) in your code. Additionally, you can pass in custom components instead of using the default button.
## How to Add an Action to Page Toolbar
In this example, we will add a "Click Me!" action and log `userName` of all users in the user management page of the [Identity Module](../../Modules/Identity.md) to the console.
### Step 1. Create Toolbar Action Contributors
The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module:
```js
// toolbar-action-contributors.ts
import { ToolbarActionList, ToolbarAction } from '@abp/ng.theme.shared/extensions';
import { IdentityToolbarActionContributors, IdentityUserDto } from '@volo/abp.ng.identity';
const logUserNames = new ToolbarAction<IdentityUserDto[]>({
text: 'Click Me!',
action: data => {
// Replace log with your custom code
data.record.forEach(user => console.log(user.userName));
},
// See ToolbarActionOptions in API section for all options
});
export function logUserNamesContributor(
actionList: ToolbarActionList<IdentityUserDto[]>
) {
actionList.addHead(logUserNames);
}
export const identityToolbarActionContributors: IdentityToolbarActionContributors = {
// enum indicates the page to add contributors to
[eIdentityComponents.Users]: [
logUserNamesContributor,
// You can add more contributors here
],
};
```
The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addHead` method, which adds the given value to the beginning of the list. You may find [all available methods here](../Common/Utils/Linked-List.md).
> **Important Note:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `logUserNamesContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details.
### Step 2. Import and Use Toolbar Action Contributors
Import `identityToolbarActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
```js
import { identityToolbarActionContributors } from './toolbar-action-contributors';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: 'identity',
loadChildren: () =>
import('@volo/abp.ng.identity').then(m =>
m.IdentityModule.forLazy({
toolbarActionContributors: identityToolbarActionContributors,
}),
),
},
// other child routes
],
// other routes
}
];
```
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `IdentityModule`.
## How to Add a Custom Component to Page Toolbar
In this example, we will add a custom "Click Me!" button and log `userName` of all users in the user management page of the [Identity Module](../../Modules/Identity.md) to the console.
### Step 1. Create A Custom Component
We need to have a component before we can pass it to the toolbar action contributors:
```js
// click-me-button.component.ts
import { Component, Inject } from '@angular/core';
import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.theme.shared/extensions';
import { IdentityUserDto } from '@volo/abp.ng.identity';
@Component({
selector: 'app-click-me-button',
template: `
<button class="btn btn-warning" (click)="handleClick()">Click Me!</button>
`,
})
export class ClickMeButtonComponent {
constructor(
@Inject(EXTENSIONS_ACTION_DATA)
private data: ActionData<IdentityUserDto[]>
) {}
handleClick() {
this.data.record.forEach(user => console.log(user.userName));
}
}
```
Here, `EXTENSIONS_ACTION_DATA` token provides us the context from the page toolbar. Therefore, we are able to reach the page data via `record`, which is an array of users, i.e. `IdentityUserDto[]`.
> We could also import `EXTENSIONS_ACTION_CALLBACK` from **@abp/ng.theme.shared/extensions** package, which is a higher order function that triggers the predefined `action` when called. It passes `ActionData` as the first parameter, so you do not have to pass it explicitly. In other words, `EXTENSIONS_ACTION_CALLBACK` can be called without any parameters and it will not fail.
### Step 2. Create Toolbar Action Contributors
The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module. When `ToolbarComponent` is used instead of `ToolbarAction`, we can pass a component in:
```js
// toolbar-action-contributors.ts
import { ToolbarActionList, ToolbarComponent } from '@abp/ng.theme.shared/extensions';
import { IdentityUserDto } from '@volo/abp.ng.identity';
import { IdentityToolbarActionContributors } from '@volo/abp.ng.identity/config';
import { ClickMeButtonComponent } from './click-me-button.component';
const logUserNames = new ToolbarComponent<IdentityUserDto[]>({
component: ClickMeButtonComponent,
// See ToolbarActionOptions in API section for all options
});
export function logUserNamesContributor(
actionList: ToolbarActionList<IdentityUserDto[]>
) {
actionList.addHead(logUserNames);
}
export const identityToolbarActionContributors: IdentityToolbarActionContributors = {
// enum indicates the page to add contributors to
[eIdentityComponents.Users]: [
logUserNamesContributor,
// You can add more contributors here
],
};
```
The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addHead` method, which adds the given value to the beginning of the list. You may find [all available methods here](../Common/Utils/Linked-List.md).
> **Important Note 1:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `logUserNamesContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details.
> **Important Note 2:** Please use one of the following if Ivy is not enabled in your project. Otherwise, you will get an "Expression form not supported." error.
```js
export const identityToolbarActionContributors: IdentityToolbarActionContributors = {
'Identity.UsersComponent': [ logUserNamesContributor ],
};
/* OR */
const identityContributors: IdentityToolbarActionContributors = {};
identityContributors[eIdentityComponents.Users] = [ logUserNamesContributor ];
export const identityToolbarActionContributors = identityContributors;
```
### Step 3. Import and Use Toolbar Action Contributors
Import `identityToolbarActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below. If Ivy is not enabled in your project, do not forget putting `ClickMeButtonComponent` into `entryComponents`:
```js
import { identityToolbarActionContributors } from './toolbar-action-contributors';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: 'identity',
loadChildren: () =>
import('@volo/abp.ng.identity').then(m =>
m.IdentityModule.forLazy({
toolbarActionContributors: identityToolbarActionContributors,
}),
),
},
// other child routes
],
// other routes
}
];
```
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `IdentityModule` and it will be triggered by a custom button, i.e. `ClickMeButtonComponent`. Please note that **component projection is not limited to buttons** and you may use other UI components.
![Page Toolbar Extension Example: Custom "Click Me!" Button](images/user-page-toolbar-extension-custom-click-me-ng.png)
## How to Place a Custom Modal and Trigger It by Toolbar Actions
Please check the same topic in [entity action extensions document](Entity-Action-Extensions.md) and replace entity action with a toolbar action.
## API
### ActionData\<R = any\>
`ActionData` is the shape of the parameter passed to all callbacks or predicates in a `ToolbarAction`.
It has the following properties:
- **record** is the page data, the main record on a page, usually an entity list (e.g. list of users).
```js
{
text: 'Click Me!',
action: data => {
data.record.forEach(user => {
console.lof(user.userName);
});
},
}
```
- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `PageToolbarComponent`, including, but not limited to, its parent component.
```js
{
text: 'Click Me!',
action: data => {
const restService = data.getInjected(RestService);
// Use restService public props and methods here
},
visible: data => {
const usersComponent = data.getInjected(UsersComponent);
// Use usersComponent public props and methods here
},
}
```
### ActionCallback\<T, R = any\>
`ActionCallback` is the type of the callback function that can be passed to a `ToolbarAction` as `action` parameter. An action callback gets a single parameter, the `ActionData`. The return type may be anything, including `void`. Here is a simplified representation:
```js
type ActionCallback<T, R = any> = (data?: ActionData<T>) => R;
```
### ActionPredicate\<T\>
`ActionPredicate` is the type of the predicate function that can be passed to a `ToolbarAction` as `visible` parameter. An action predicate gets a single parameter, the `ActionData`. The return type must be `boolean`. Here is a simplified representation:
```js
type ActionPredicate<T> = (data?: ActionData<T>) => boolean;
```
### ToolbarActionOptions\<R = any\>
`ToolbarActionOptions` is the type that defines required and optional properties you have to pass in order to create an toolbar action.
Its type definition is as follows:
```js
type ToolbarActionOptions<R = any> = {
action: ActionCallback<R>,
text: string,
icon?: string,
permission?: string,
visible?: ActionPredicate<R>,
};
```
As you see, passing `action` and `text` is enough to create an toolbar action. Here is what each property is good for:
- **action** is a callback that is called when the toolbar action is clicked. (_required_)
- **text** is the button text which will be localized. (_required_)
- **icon** is the classes that define an icon to be placed before the text. (_default:_ `''`)
- **permission** is the permission context which will be used to decide if this toolbar action should be displayed to the user or not. (_default:_ `undefined`)
- **visible** is a predicate that will be used to decide if the page toolbar should have this action or not. (_default:_ `() => true`)
You may find a full example below.
### ToolbarAction\<R = any\>
`ToolbarAction` is the class that defines your toolbar actions. It takes an `ToolbarActionOptions` and sets the default values to the properties, creating an toolbar action that can be passed to an toolbar contributor.
```js
const options: ToolbarActionOptions<IdentityUserDto[]> = {
action: data => {
const service = data.getInjected(MyCustomIdentityService);
const lockedUsers = data.record.filter(user => user.isLockedOut);
service.unlockAll(lockedUsers);
},
text: 'MyProjectName::UnlockAll',
icon: 'fa fa-unlock',
permission: 'AbpIdentity.Users.Update',
visible: data => data.record.some(user => user.isLockedOut),
};
const action = new ToolbarAction(options);
```
It also has two static methods to create its instances:
- **ToolbarAction.create\<R = any\>\(options: ToolbarActionOptions\<R\>\)** is used to create an instance of `ToolbarAction`.
```js
const action = ToolbarAction.create(options);
```
- **ToolbarAction.createMany\<R = any\>\(options: ToolbarActionOptions\<R\>\[\]\)** is used to create multiple instances of `ToolbarAction` with given array of `ToolbarActionOptions`.
### ToolbarComponentOptions\<R = any\>
`ToolbarComponentOptions` is the type that defines required and optional properties you have to pass in order to create an toolbar component.
Its type definition is as follows:
```js
type ToolbarComponentOptions<R = any> = {
component: Type<any>,
action?: ActionCallback<R>,
permission?: string,
visible?: ActionPredicate<R>,
};
```
As you see, passing `action` and `text` is enough to create an toolbar action. Here is what each property is good for:
- **component** is the constructor of the component to be projected. (_required_)
- **action** is a predefined callback that you can reach in your component via `EXTENSIONS_ACTION_CALLBACK` token and trigger. (_optional_)
- **permission** is the permission context which will be used to decide if this toolbar action should be displayed to the user or not. (_default:_ `undefined`)
- **visible** is a predicate that will be used to decide if the page toolbar should have this action or not. (_default:_ `() => true`)
You may find a full example below.
### ToolbarComponent\<R = any\>
`ToolbarComponent` is the class that defines toolbar actions which project a custom component. It takes an `ToolbarComponentOptions` and sets the default values to the properties, creating a toolbar action that can be passed to an toolbar contributor.
```js
const options: ToolbarComponentOptions<IdentityUserDto[]> = {
component: UnlockAllButton,
action: data => {
const service = data.getInjected(MyCustomIdentityService);
const lockedUsers = data.record.filter(user => user.isLockedOut);
service.unlockAll(lockedUsers);
},
permission: 'AbpIdentity.Users.Update',
visible: data => data.record.some(user => user.isLockedOut),
};
const action = new ToolbarComponent(options);
```
It also has two static methods to create its instances:
- **ToolbarComponent.create\<R = any\>\(options: ToolbarComponentOptions\<R\>\)** is used to create an instance of `ToolbarComponent`.
```js
const action = ToolbarComponent.create(options);
```
- **ToolbarComponent.createMany\<R = any\>\(options: ToolbarComponentOptions\<R\>\[\]\)** is used to create multiple instances of `ToolbarComponent` with given array of `ToolbarComponentOptions`.
```js
const actions = ToolbarComponent.createMany(optionsArray);
```
### ToolbarActionList\<R = any\>
`ToolbarActionList` is the list of actions passed to every action contributor callback as the first parameter named `actionList`. It is a **doubly linked list**. You may find [all available methods here](../Common/Utils/Linked-List.md).
The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this:
```js
export function reorderUserContributors(
actionList: ToolbarActionList<IdentityUserDto[]>,
) {
// drop "New User" button
const newUserActionNode = actionList.dropByValue(
'AbpIdentity::NewUser',
(action, text) => action['text'] === text,
);
// add it back to the head of the list
actionList.addHead(newUserActionNode.value);
}
export const identityEntityActionContributors = {
[eIdentityComponents.Users]: [
logUserNamesContributor,
reorderUserContributors,
],
};
```
### ToolbarActionContributorCallback\<R = any\>
`ToolbarActionContributorCallback` is the type that you can pass as toolbar action contributor callbacks to static `forLazy` methods of the modules.
```js
// exportUsersContributor should have ToolbarActionContributorCallback<IdentityUserDto[]> type
export function exportUsersContributor(
actionList: ToolbarActionList<IdentityUserDto[]>,
) {
// add exportUsers just before the last action
actionList.add(exportUsers).byIndex(-1);
}
export const identityEntityActionContributors = {
[eIdentityComponents.Users]: [exportUsersContributor],
};
```
## See Also
- [Customizing Application Modules Guide](../../Customizing-Application-Modules-Guide.md)

BIN
docs/en/UI/Angular/images/user-action-extension-click-me-ng.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
docs/en/UI/Angular/images/user-page-toolbar-extension-click-me-ng.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
docs/en/UI/Angular/images/user-page-toolbar-extension-custom-click-me-ng.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
docs/en/UI/Angular/images/user-prop-extension-date-of-birth-field-ng.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
docs/en/UI/Angular/images/user-prop-extension-name-column-ng.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

28
docs/en/UI/AspNetCore/Data-Tables.md

@ -105,6 +105,32 @@ The `abp.libs.datatables.createAjax` method (used in the example above) adapts r
This works automatically, so most of the times you don't need to know how it works. See the [DTO document](../../Data-Transfer-Objects.md) if you want to learn more about `IPagedAndSortedResultRequest`, `IPagedResult` and other standard interfaces and base DTO classes those are used in client to server communication.
The `createAjax` also supports you to customize request parameters and handle the responses.
**Example:**
````csharp
var inputAction = function () {
return {
id: $('#Id').val(),
name: $('#Name').val(),
};
};
var responseCallback = function(result) {
// your custom code.
return {
recordsTotal: result.totalCount,
recordsFiltered: result.totalCount,
data: result.items
};
};
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList, inputAction, responseCallback)
````
### Row Actions
`rowAction` is an option defined by the ABP Framework to the column definitions to show a drop down button to take actions for a row in the table.
@ -260,4 +286,4 @@ Assuming that the possible values for a column data is `f` and `m`, the `gender`
## Other Data Grids
You can use any library you like. For example, [see this article](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) to learn how to use DevExtreme Data Grid in your applications.
You can use any library you like. For example, [see this article](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) to learn how to use DevExtreme Data Grid in your applications.

163
docs/en/UI/AspNetCore/Page-Toolbar-Extensions.md

@ -0,0 +1,163 @@
# Page Toolbar Extensions for ASP.NET Core UI
Page toolbar system allows you to add components to the toolbar of any page. The page toolbar is the area right to the header of a page. A button ("Import users from excel") was added to the user management page below:
![page-toolbar-button](../../images/page-toolbar-button.png)
You can add any type of view component item to the page toolbar or modify existing items.
## How to Set Up
In this example, we will add an "Import users from excel" button and execute a JavaScript code for the user management page of the [Identity Module](../../Modules/Identity.md).
### Add a New Button to the User Management Page
Write the following code inside the `ConfigureServices` of your web module class:
````csharp
Configure<AbpPageToolbarOptions>(options =>
{
options.Configure<Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel>(toolbar =>
{
toolbar.AddButton(
LocalizableString.Create<MyProjectNameResource>("ImportFromExcel"),
icon: "file-import",
id: "ImportUsersFromExcel",
type: AbpButtonType.Secondary
);
});
});
````
`AddButton` is a shortcut to simply add a button component. Note that you need to add the `ImportFromExcel` to your localization dictionary (json file) to localize the text.
When you run the application, you will see the button added next to the current button list. There are some other parameters of the `AddButton` method (for example, use `order` to set the order of the button component relative to the other components).
### Create a JavaScript File
Now, we can go to the client side to handle click event of the new button. First, add a new JavaScript file to your solution. We added inside the `/Pages/Identity/Users` folder of the `.Web` project:
![user-action-extension-on-solution](../../images/user-action-extension-on-solution.png)
Here, the content of this JavaScript file:
````js
$(function () {
$('#ImportUsersFromExcel').click(function (e) {
e.preventDefault();
alert('TODO: import users from excel');
});
});
````
In the `click` event, you can do anything you need to do.
### Add the File to the User Management Page
Then you need to add this JavaScript file to the user management page. You can take the power of the [Bundling & Minification system](Bundling-Minification.md).
Write the following code inside the `ConfigureServices` of your module class:
````csharp
Configure<AbpBundlingOptions>(options =>
{
options.ScriptBundles.Configure(
typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName,
bundleConfiguration =>
{
bundleConfiguration.AddFiles(
"/Pages/Identity/Users/my-user-extensions.js"
);
});
});
````
This configuration adds `my-user-extensions.js` to the user management page of the Identity Module. `typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName` is the name of the bundle in the user management page. This is a common convention used for all the ABP Commercial modules.
## Advanced Use Cases
While you typically want to add a button action to the page toolbar, it is possible to add any type of component.
### Add View Component to a Page Toolbar
First, create a new view component in your project:
![page-toolbar-custom-component](../../images/page-toolbar-custom-component.png)
For this example, we've created a `MyToolbarItem` view component under the `/Pages/Identity/Users/MyToolbarItem` folder.
`MyToolbarItemViewComponent.cs` content:
````csharp
public class MyToolbarItemViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("~/Pages/Identity/Users/MyToolbarItem/Default.cshtml");
}
}
````
`Default.cshtml` content:
````xml
<span>
<button type="button" class="btn btn-dark">CLICK ME</button>
</span>
````
* `.cshtml` file can contain any type of component(s). It is a typical view component.
* `MyToolbarItemViewComponent` can inject and use any service if you need.
Then you can add the `MyToolbarItemViewComponent` to the user management page:
````csharp
Configure<AbpPageToolbarOptions>(options =>
{
options.Configure<Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel>(
toolbar =>
{
toolbar.AddComponent<MyToolbarItemViewComponent>();
}
);
});
````
* If your component accepts arguments (in the `Invoke`/`InvokeAsync` method), you can pass them to the `AddComponent` method as an anonymous object.
#### Permissions
If your button/component should be available based on a [permission/policy](../../Authorization.md), you can pass the permission/policy name as the `requiredPolicyName` parameter to the `AddButton` and `AddComponent` methods.
### Add a Page Toolbar Contributor
If you perform advanced custom logic while adding an item to a page toolbar, you can create a class that implements the `IPageToolbarContributor` interface or inherits from the `PageToolbarContributor` class:
````csharp
public class MyToolbarContributor : PageToolbarContributor
{
public override Task ContributeAsync(PageToolbarContributionContext context)
{
context.Items.Insert(0, new PageToolbarItem(typeof(MyToolbarItemViewComponent)));
return Task.CompletedTask;
}
}
````
* You can use `context.ServiceProvider` to resolve dependencies if you need.
Then add your class to the `Contributors` list:
````csharp
Configure<AbpPageToolbarOptions>(options =>
{
options.Configure<Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel>(
toolbar =>
{
toolbar.Contributors.Add(new MyToolbarContributor());
}
);
});
````

11
docs/en/UI/AspNetCore/Tag-Helpers/Modals.md

@ -11,7 +11,7 @@ Basic usage:
````xml
<abp-button button-type="Primary" data-toggle="modal" data-target="#myModal">Launch modal</abp-button>
<abp-modal centered="true" size="Large" id="myModal">
<abp-modal centered="true" scrollable="true" size="Large" id="myModal">
<abp-modal-header title="Modal title"></abp-modal-header>
<abp-modal-body>
Woohoo, you're reading this text in a modal!
@ -33,6 +33,13 @@ A value indicates the positioning of the modal. Should be one of the following v
* `false` (default value)
* `true`
### Scrollable
A value indicates the scrolling of the modal. Should be one of the following values:
* `false` (default value)
* `true`
### size
A value indicates the size of the modal. Should be one of the following values:
@ -78,4 +85,4 @@ A value indicates the positioning of your modal footer buttons. Should be one of
* `Center`
* `Around`
* `Between`
* `End`
* `End`

25
docs/en/UI/Blazor/Components/SubmitButton.md

@ -0,0 +1,25 @@
# Blazor UI: SubmitButton component
`SubmitButton` is a simple wrapper around `Button` component. It is used to be placed inside of page Form or Modal dialogs where it can response to user actions and to be activated as a default button by pressing an ENTER key. Once clicked it will go into the `disabled` state and also it will show a small loading indicator until clicked event is finished.
## Quick Example
```html
<SubmitButton Clicked="@YourSaveOperation" />
```
Notice that we didn't specify any text, like `Save Changes`. This is because `SubmitButton` will by default pull text from the localization. If you want to change that you either specify a localization key or you can add custom content.
### With localization key
```html
<SubmitButton Clicked="@YourSaveOperation" SaveResourceKey="YourSaveName" />
```
### With custom content
```html
<SubmitButton Clicked="@YourSaveOperation">
@L["Save"]
</SubmitButton>
```

47
docs/en/UI/Blazor/Global-Scripts-Styles.md

@ -35,4 +35,49 @@ namespace MyProject.Blazor
> There is a BundleContributor class implementing `IBundleContributor` interface coming by default with the startup templates. So, most of the time, you don't need to add it manually.
> Bundle command adds style and script references individually. Bundling and minification support will be added to incoming releases.
## Bundling And Minification
`abp bundle` command offers bundling and minification support for client-side resources(JavaScript and CSS files). `abp bundle` command reads the `appsettings.json` file inside the Blazor project and bundles the resources according to the configuration. You can find the bundle configurations inside `AbpCli.Bundle` element.
Here are the options that you can control inside the `appsettings.json` file.
`Mode`: Bundling and minification mode. Possible values are
* `BundleAndMinify`: Bundle all the files into a single file and minify the content.
* `Bundle`: Bundle all files into a single file, but not minify.
* `None`: Add files individually, do not bundle.
`Name`: Bundle file name. Default value is `global`.
`Parameters`: You can define additional key/value pair parameters inside this section. `abp bundle` command automatically sends these parameters to the bundle contributors, and you can check these parameters inside the bundle contributor, take some actions according to these values.
Let's say that you want to exclude some resources from the bundle and control this action using the bundle parameters. You can add a parameter to the bundle section like below.
```json
"AbpCli": {
"Bundle": {
"Mode": "BundleAndMinify", /* Options: None, Bundle, BundleAndMinify */
"Name": "global",
"Parameters": {
"ExcludeThemeFromBundle":"true"
}
}
}
```
You can check this parameter and take action like below.
```csharp
public class MyProjectNameBundleContributor : IBundleContributor
{
public void AddScripts(BundleContext context)
{
}
public void AddStyles(BundleContext context)
{
var excludeThemeFromBundle = bool.Parse(context.Parameters.GetValueOrDefault("ExcludeThemeFromBundle"));
context.Add("mytheme.css", excludeFromBundle: excludeThemeFromBundle);
context.Add("main.css");
}
}
```

2
docs/en/UI/Blazor/Theming.md

@ -63,7 +63,7 @@ A theme is simply a Razor Class Library.
### The Easy Way
The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic` NuGet package and reference to the local project.
The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme` NuGet package and reference to the local project.
### Global Styles / Scripts

BIN
docs/en/_resources/ddd-microservice-simple.psd

Binary file not shown.

BIN
docs/en/_resources/ui-db-options.psd

Binary file not shown.

27
docs/en/docs-nav.json

@ -353,22 +353,8 @@
"path": "PlugIn-Modules.md"
},
{
"text": "Customizing the Application Modules",
"path": "Customizing-Application-Modules-Guide.md",
"items": [
{
"text": "Extending Entities",
"path": "Customizing-Application-Modules-Extending-Entities.md"
},
{
"text": "Overriding Services",
"path": "Customizing-Application-Modules-Overriding-Services.md"
},
{
"text": "Overriding the User Interface",
"path": "Customizing-Application-Modules-Overriding-User-Interface.md"
}
]
"text": "Customizing/Extending Modules",
"path": "Customizing-Application-Modules-Guide.md"
},
{
"text": "Best Practices",
@ -698,6 +684,15 @@
}
]
},
{
"text": "Other Components",
"items": [
{
"text": "SubmitButton",
"path": "UI/Blazor/SubmitButton.md"
}
]
},
{
"text": "Settings",
"path": "UI/Blazor/Settings.md"

BIN
docs/en/images/extension-navigation-property-form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/images/extension-navigation-property-table.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/images/page-toolbar-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/images/page-toolbar-custom-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

4
docs/zh-Hans/Blob-Storing.md

@ -21,7 +21,7 @@ ABP框架已经有以下存储提供程序的实现;
* [Azure](Blob-Storing-Azure.md): 将BLOG存储在 [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中.
* [Aliyun](Blob-Storing-Aliyun.md): 将BLOB存储在[Aliyun Storage Service](https://help.aliyun.com/product/31815.html)中.
* [Ninio](Blob-Storing-Minio.md): 将BLOB存储在[MinIO Object storage](https://min.io/)中.
* [Aws](Blob-Storing-Aws.md): 将BLOB存储在[Amazon Simple Storage Service](https://min.io/)中.
* [Aws](Blob-Storing-Aws.md): 将BLOB存储在[Amazon Simple Storage Service](https://aws.amazon.com/s3/)中.
以后会实现更多的提供程序,你可以为自己喜欢的提供程序创建[请求](https://github.com/abpframework/abp/issues/new),或者你也可以[自己实现](Blob-Storing-Custom-Provider.md)它并[贡献](Contribution/Index.md)到ABP框架.
@ -306,4 +306,4 @@ Configure<AbpBlobStoringOptions>(options =>
## 另请参阅
* [创建自定义BLOB存储提供程序](Blob-Storing-Custom-Provider.md)
* [创建自定义BLOB存储提供程序](Blob-Storing-Custom-Provider.md)

5
docs/zh-Hans/CLI.md

@ -34,7 +34,7 @@ dotnet tool update -g Volo.Abp.Cli
* **`generate-proxy`**: 生成客户端代理以使用HTTP API端点.
* **`remove-proxy`**: 移除以前生成的客户端代理.
* **`switch-to-preview`**: 切换到ABP框架的最新预览版本。
* **`switch-to-preview`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本.
* **`switch-to-nightly`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本.
* **`switch-to-stable`**: 切换解决方案所有ABP相关包为最新的稳定版本.
* **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程.
* **`login`**: 使用你在[abp.io](https://abp.io/)的用户名和密码在你的计算机上认证.
@ -125,7 +125,8 @@ abp update [options]
* `--nuget`: 仅更新的NuGet包
* `--solution-path``-sp`: 指定解决方案路径/目录. 默认使用当前目录
* `--solution-name``-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件.
*`--check-all`: 分别检查每个包的新版本. 默认是 `false`.
* `--check-all`: 分别检查每个包的新版本. 默认是 `false`.
* `--version` or `-v`: 指定用于升级的版本. 如果没有指定,则使用最新版本.
### add-package

1979
docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md

File diff suppressed because it is too large

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

Loading…
Cancel
Save