Browse Source

Merge branch 'dev' of github.com:abpframework/abp into split-crudappservice

pull/4346/head
Necati Meral 6 years ago
parent
commit
db2da83128
  1. 11
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  2. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  3. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pt-BR.json
  4. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
  5. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hant.json
  6. 2
      common.props
  7. 23
      docs/en/API/Application-Configuration.md
  8. 3
      docs/en/API/JavaScript-API/Auth.md
  9. 4
      docs/en/Application-Services.md
  10. 22
      docs/en/AspNet-Boilerplate-Migration-Guide.md
  11. 3
      docs/en/AspNetCore/JavaScript-API/Auth.md
  12. 3
      docs/en/AspNetCore/JavaScript-API/Index.md
  13. 59
      docs/en/Blob-Storing-Azure.md
  14. 177
      docs/en/Blob-Storing-Custom-Provider.md
  15. 98
      docs/en/Blob-Storing-Database.md
  16. 59
      docs/en/Blob-Storing-File-System.md
  17. 305
      docs/en/Blob-Storing.md
  18. 296
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/Post.md
  19. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/abp-chat-module.png
  20. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/easy-crm.png
  21. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/lepton-theme.png
  22. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/organization-units.png
  23. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/signalr-tiered-demo.png
  24. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/suite.png
  25. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-1.png
  26. BIN
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-2.png
  27. 1
      docs/en/CLI.md
  28. 3
      docs/en/Clock.md
  29. 2
      docs/en/Connection-Strings.md
  30. 19
      docs/en/Contribution/Index.md
  31. 166
      docs/en/CurrentUser.md
  32. 1
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  33. 159
      docs/en/Data-Seeding.md
  34. 281
      docs/en/Data-Transfer-Objects.md
  35. 4
      docs/en/Domain-Driven-Design.md
  36. 8
      docs/en/Entities.md
  37. 17
      docs/en/Entity-Framework-Core-MySQL.md
  38. 62
      docs/en/Entity-Framework-Core-Oracle.md
  39. 1
      docs/en/Entity-Framework-Core-Other-DBMS.md
  40. 112
      docs/en/Guid-Generation.md
  41. 5
      docs/en/How-To/Customize-Login-Page-MVC.md
  42. 4
      docs/en/Index.md
  43. 3
      docs/en/Json.md
  44. 29
      docs/en/Modules/Docs.md
  45. 1
      docs/en/Modules/Index.md
  46. 82
      docs/en/Modules/Virtual-File-Explorer.md
  47. 12
      docs/en/Multi-Tenancy.md
  48. 4
      docs/en/Object-Extensions.md
  49. 55
      docs/en/Object-To-Object-Mapping.md
  50. 23
      docs/en/Road-Map.md
  51. 21
      docs/en/Startup-Templates/Console.md
  52. 4
      docs/en/Startup-Templates/Index.md
  53. 113
      docs/en/Timing.md
  54. 40
      docs/en/Tutorials/Part-1.md
  55. 22
      docs/en/Tutorials/Part-2.md
  56. 485
      docs/en/UI/Angular/Component-Replacement.md
  57. 62
      docs/en/UI/Angular/Localization.md
  58. 500
      docs/en/UI/Angular/Permission-Management-Component-Replacement.md
  59. BIN
      docs/en/UI/Angular/images/layout-components.png
  60. BIN
      docs/en/UI/Angular/images/logo-component.png
  61. BIN
      docs/en/UI/Angular/images/nav-items-component.png
  62. BIN
      docs/en/UI/Angular/images/permission-management-modal.png
  63. BIN
      docs/en/UI/Angular/images/replaced-logo-component.png
  64. BIN
      docs/en/UI/Angular/images/replaced-nav-items-component.png
  65. BIN
      docs/en/UI/Angular/images/replaced-routes-component.png
  66. BIN
      docs/en/UI/Angular/images/routes-component.png
  67. 6
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  68. 5
      docs/en/UI/AspNetCore/JavaScript-API/Index.md
  69. 40
      docs/en/UI/AspNetCore/Tag-Helpers/Badges.md
  70. 126
      docs/en/UI/AspNetCore/Tag-Helpers/Borders.md
  71. 25
      docs/en/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md
  72. 37
      docs/en/UI/AspNetCore/Tag-Helpers/Button-groups.md
  73. 74
      docs/en/UI/AspNetCore/Tag-Helpers/Carousel.md
  74. 114
      docs/en/UI/AspNetCore/Tag-Helpers/Navs.md
  75. 61
      docs/en/UI/AspNetCore/Tag-Helpers/Tables.md
  76. 10
      docs/en/Virtual-File-System.md
  77. 76
      docs/en/docs-nav.json
  78. BIN
      docs/en/images/virtual-file-explorer.png
  79. 11
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-I.md
  80. 8
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-II.md
  81. 22
      docs/zh-Hans/API/Application-Configuration.md
  82. 2
      docs/zh-Hans/Application-Services.md
  83. 58
      docs/zh-Hans/Blob-Storing-Azure.md
  84. 177
      docs/zh-Hans/Blob-Storing-Custom-Provider.md
  85. 96
      docs/zh-Hans/Blob-Storing-Database.md
  86. 58
      docs/zh-Hans/Blob-Storing-File-System.md
  87. 306
      docs/zh-Hans/Blob-Storing.md
  88. 294
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/Post.md
  89. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/abp-chat-module.png
  90. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/easy-crm.png
  91. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/lepton-theme.png
  92. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/organization-units.png
  93. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/signalr-tiered-demo.png
  94. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/suite.png
  95. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-1.png
  96. BIN
      docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-2.png
  97. 32
      docs/zh-Hans/CLI.md
  98. 14
      docs/zh-Hans/Connection-Strings.md
  99. 18
      docs/zh-Hans/Contribution/Index.md
  100. 168
      docs/zh-Hans/CurrentUser.md

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

@ -148,11 +148,12 @@
"EmailSent": "Email Sent", "EmailSent": "Email Sent",
"SuccessfullySent": "Successfully Sent", "SuccessfullySent": "Successfully Sent",
"SuccessfullyDeleted": "Successfully Deleted", "SuccessfullyDeleted": "Successfully Deleted",
"DiscountRequestDeletionWarningMessage": "Discount request will be deleted" , "DiscountRequestDeletionWarningMessage": "Discount request will be deleted",
"BusinessType": "Business Type", "BusinessType": "Business Type",
"TotalQuestionCount": "Total question count", "TotalQuestionCount": "Total question count",
"RemainingQuestionCount": "Remaining question count", "RemainingQuestionCount": "Remaining question count",
"TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !", "TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !",
"QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !" "QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !",
"UnlimitedQuestionCount": "Unlimited question count"
} }
} }

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

@ -39,7 +39,7 @@
"Theming": "Theming", "Theming": "Theming",
"ThemingExplanationShort": "Use and customize the bootstrap-based standard UI theme or create your own one.", "ThemingExplanationShort": "Use and customize the bootstrap-based standard UI theme or create your own one.",
"BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dynamic Forms", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dynamic Forms",
"BootstrapTagHelpersDynamicFormsExplanation": "Built-in background job system that can be integrated to Hangfire, RabbitMQ or any tool you like.", //TODO explanation doesn't match. "BootstrapTagHelpersDynamicFormsExplanation": "Instead of manually writing the repeating details of bootstrap components, Use ABP's tag helpers to simplify it and take advantage of intellisense. Dynamic form can create the complete form from a C# class as the model.",
"HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies", "HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies",
"HTTPAPIsDynamicProxiesExplanation": "Automatically expose application services as REST style HTTP APIs and consume with dynamic JavaScript & C# proxies.", "HTTPAPIsDynamicProxiesExplanation": "Automatically expose application services as REST style HTTP APIs and consume with dynamic JavaScript & C# proxies.",
"CompleteArchitectureInfo": "Modern architecture to create maintainable software solutions.", "CompleteArchitectureInfo": "Modern architecture to create maintainable software solutions.",

9
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pt-BR.json

@ -29,17 +29,17 @@
"Authorization": "Autorização", "Authorization": "Autorização",
"AuthorizationExplanation": "Autorização avançada com usuário, função e sistema de permissão refinado. Criada na biblioteca Microsoft Identity.", "AuthorizationExplanation": "Autorização avançada com usuário, função e sistema de permissão refinado. Criada na biblioteca Microsoft Identity.",
"MultiTenancy": "Múltiplos inquilinos", "MultiTenancy": "Múltiplos inquilinos",
"MultiTenancyExplanation": "Aplicativos SaaS facilitados! Multilocação integrada do banco de dados à interface do usuário.", "MultiTenancyExplanationShort": "Aplicativos SaaS facilitados! Multilocação integrada do banco de dados à interface do usuário.",
"CrossCuttingConcerns": "Características transversais", "CrossCuttingConcerns": "Características transversais",
"CrossCuttingConcernsExplanation": "Infraestrutura completa para autorização, validação, tratamento de exceções, armazenamento em cache, log de auditoria, gerenciamento de transações e assim por diante.", "CrossCuttingConcernsExplanationShort": "Infraestrutura completa para autorização, validação, tratamento de exceções, armazenamento em cache, log de auditoria, gerenciamento de transações e assim por diante.",
"BuiltInBundlingMinification": "Pacote & Minificação Integrados", "BuiltInBundlingMinification": "Pacote & Minificação Integrados",
"BuiltInBundlingMinificationExplanation": "Pare de usar ferramentas externas para empacotamento e minificação. O ABP oferece uma maneira mais simples, dinâmica, poderosa, modular e integrada!", "BuiltInBundlingMinificationExplanation": "Pare de usar ferramentas externas para empacotamento e minificação. O ABP oferece uma maneira mais simples, dinâmica, poderosa, modular e integrada!",
"VirtualFileSystem": "Sistema de arquivos virtual", "VirtualFileSystem": "Sistema de arquivos virtual",
"VirtualFileSystemExplanation": "Incorpore visualizações, scripts, estilos, imagens ... aos pacotes/bibliotecas e reutilize-os em diferentes aplicativos.", "VirtualFileSystemExplanation": "Incorpore visualizações, scripts, estilos, imagens ... aos pacotes/bibliotecas e reutilize-os em diferentes aplicativos.",
"Theming": "Theming", "Theming": "Theming",
"ThemingExplanation": "Use e personalize o tema da interface do usuário padrão baseado em bootstrap-based ou crie o seu próprio.", "ThemingExplanationShort": "Use e personalize o tema da interface do usuário padrão baseado em bootstrap-based ou crie o seu próprio.",
"BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dynamic Forms", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dynamic Forms",
"BootstrapTagHelpersDynamicFormsExplanation": "Sistema de tarefas em segundo plano integrado que pode ser integrado ao Hangfire, RabbitMQ ou a qualquer ferramenta que você desejar.", //TODO explanation doesn't match. "BootstrapTagHelpersDynamicFormsExplanation": "Em vez de escrever manualmente os detalhes repetidos dos componentes de autoinicialização, use os auxiliares de tag da ABP para simplificá-lo e tirar proveito do intellisense. O formulário dinâmico pode criar o formulário completo a partir de uma classe C# como modelo.",
"HTTPAPIsDynamicProxies": "APIs HTTP e proxies dinâmicos", "HTTPAPIsDynamicProxies": "APIs HTTP e proxies dinâmicos",
"HTTPAPIsDynamicProxiesExplanation": "Exponha automaticamente os serviços de aplicativo como APIs HTTP do estilo REST e consuma com proxies JavaScript & C # dinâmicos.", "HTTPAPIsDynamicProxiesExplanation": "Exponha automaticamente os serviços de aplicativo como APIs HTTP do estilo REST e consuma com proxies JavaScript & C # dinâmicos.",
"CompleteArchitectureInfo": "Arquitetura moderna para criar soluções de software sustentáveis.", "CompleteArchitectureInfo": "Arquitetura moderna para criar soluções de software sustentáveis.",
@ -100,7 +100,6 @@
"DistributedEventBus": "Barramento de Eventos Distribuídos", "DistributedEventBus": "Barramento de Eventos Distribuídos",
"DistributedEventBusWithRabbitMQIntegration": "Barramento de Eventos Distribuídos com Integração RabbitMQ", "DistributedEventBusWithRabbitMQIntegration": "Barramento de Eventos Distribuídos com Integração RabbitMQ",
"TestInfrastructure": "Infraestrutura de Teste", "TestInfrastructure": "Infraestrutura de Teste",
"AuditLogging": "Log de auditoria",
"AuditLoggingEntityHistories": "Log de auditoria e históricos de entidades", "AuditLoggingEntityHistories": "Log de auditoria e históricos de entidades",
"ObjectToObjectMapping": "Objeto para Mapeamento de Objetos", "ObjectToObjectMapping": "Objeto para Mapeamento de Objetos",
"EmailSMSAbstractions": "Abstrações de E-mail e SMS", "EmailSMSAbstractions": "Abstrações de E-mail e SMS",

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

@ -31,7 +31,7 @@
"MultiTenancy": "多租户", "MultiTenancy": "多租户",
"MultiTenancyExplanationShort": "SaaS应用程序变得简单! 从数据库到UI的多租户集成.", "MultiTenancyExplanationShort": "SaaS应用程序变得简单! 从数据库到UI的多租户集成.",
"CrossCuttingConcerns": "横切关注点", "CrossCuttingConcerns": "横切关注点",
"CrossCuttingConcernsExplanationShort": "完整的基础架构,用于授权,验证,异常处理,缓存,审计日志记录,事务管理等.", "CrossCuttingConcernsExplanationShort": "完整的基础架构,用于授权,验证,异常处理,缓存,审计日志记录,事务管理等.",
"BuiltInBundlingMinification": "内置Bundling & Minification", "BuiltInBundlingMinification": "内置Bundling & Minification",
"BuiltInBundlingMinificationExplanation": "无需使用外部工具进行Bundling & Minification. ABP提供了一种更简单,动态,功能强大,模块化和内置的方式!", "BuiltInBundlingMinificationExplanation": "无需使用外部工具进行Bundling & Minification. ABP提供了一种更简单,动态,功能强大,模块化和内置的方式!",
"VirtualFileSystem": "虚拟文件系统", "VirtualFileSystem": "虚拟文件系统",
@ -39,7 +39,7 @@
"Theming": "主题", "Theming": "主题",
"ThemingExplanationShort": "使用和定制基于bootstrap的标准UI主题,或创建自己的主题.", "ThemingExplanationShort": "使用和定制基于bootstrap的标准UI主题,或创建自己的主题.",
"BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers和动态表单", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers和动态表单",
"BootstrapTagHelpersDynamicFormsExplanation": "内置的后台作业系统可以集成到Hangfire,RabbitMQ或您喜欢的任何工具中.", //TODO explanation doesn't match. "BootstrapTagHelpersDynamicFormsExplanation": "与其手动重复编写Bootstrap的组件,不如使用ABP的Tag Helpers利用智能感知来简化它. 动态表单可以从C#类创建完整的表单.",
"HTTPAPIsDynamicProxies": "HTTP APIs和动态代理", "HTTPAPIsDynamicProxies": "HTTP APIs和动态代理",
"HTTPAPIsDynamicProxiesExplanation": "自动将应用程序服务公开为REST样式的HTTP API,并与动态JavaScript和C#代理一起使用.", "HTTPAPIsDynamicProxiesExplanation": "自动将应用程序服务公开为REST样式的HTTP API,并与动态JavaScript和C#代理一起使用.",
"CompleteArchitectureInfo": "现代架构用来创建可维护的软件解决方案.", "CompleteArchitectureInfo": "现代架构用来创建可维护的软件解决方案.",

2
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hant.json

@ -39,7 +39,7 @@
"Theming": "主題", "Theming": "主題",
"ThemingExplanationShort": "使用和訂製基於bootstrap的標準UI主題,或建立自己的主題.", "ThemingExplanationShort": "使用和訂製基於bootstrap的標準UI主題,或建立自己的主題.",
"BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers和動態表單", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers和動態表單",
"BootstrapTagHelpersDynamicFormsExplanation": "內建的背景作業系統可以整合到Hangfire,RabbitMQ或您喜歡的任何工具中.", //TODO explanation doesn't match. "BootstrapTagHelpersDynamicFormsExplanation": "與其手動重複編寫Bootstrap的組件,不如使用ABP的Tag Helpers利用智能感知來簡化它. 動態表單可以從C#類創建完整的表單.",
"HTTPAPIsDynamicProxies": "HTTP APIs和動態代理", "HTTPAPIsDynamicProxies": "HTTP APIs和動態代理",
"HTTPAPIsDynamicProxiesExplanation": "自動將應用程式服務公開為REST樣式的HTTP API,並與動態JavaScript和C#代理一起使用.", "HTTPAPIsDynamicProxiesExplanation": "自動將應用程式服務公開為REST樣式的HTTP API,並與動態JavaScript和C#代理一起使用.",
"CompleteArchitectureInfo": "現在架構用來建立可維護的軟體解決方案.", "CompleteArchitectureInfo": "現在架構用來建立可維護的軟體解決方案.",

2
common.props

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

23
docs/en/API/Application-Configuration.md

@ -0,0 +1,23 @@
# Application Configuration Endpoint
ABP Framework provides a pre-built and standard endpoint that contains some useful information about the application/service. Here, the list of some fundamental information at this endpoint:
* [Localization](Localization.md) values, supported and the current language of the application.
* Available and granted [policies](Authorization.md) (permissions) for the current user.
* [Setting](Settings.md) values for the current user.
* Info about the [current user](CurrentUser.md) (like id and user name).
* Info about the current [tenant](Multi-Tenancy.md) (like id and name).
* [Time zone](Timing.md) information for the current user and the [clock](Timing.md) type of the application.
## HTTP API
If you navigate to the `/api/abp/application-configuration` URL of an ABP Framework based web application or HTTP Service, you can access the configuration as a JSON object. This endpoint is useful to create the client of your application.
## Script
For ASP.NET Core MVC (Razor Pages) applications, the same configuration values are also available on the JavaScript side. `/Abp/ApplicationConfigurationScript` is the URL of the script that is auto-generated based on the HTTP API above.
See the [JavaScript API document](../UI/AspNetCore/JavaScript-API/Index.md) for the ASP.NET Core UI.
Other UI types provide services native to the related platform. For example, see the [Angular UI localization documentation](../UI/Angular/Localization.md) to learn how to use the localization values exposes by this endpoint.

3
docs/en/API/JavaScript-API/Auth.md

@ -1,3 +0,0 @@
# abp.auth JavaScript API
TODO

4
docs/en/Application-Services.md

@ -2,7 +2,7 @@
Application services are used to implement the **use cases** of an application. They are used to **expose domain logic to the presentation layer**. Application services are used to implement the **use cases** of an application. They are used to **expose domain logic to the presentation layer**.
An Application Service is called from the presentation layer (optionally) with a **DTO (Data Transfer Object)** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer. An Application Service is called from the presentation layer (optionally) with a **DTO ([Data Transfer Object](Data-Transfer-Objects.md))** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer.
## Example ## Example
@ -205,7 +205,7 @@ See the [object to object mapping document](Object-To-Object-Mapping.md) for mor
## Validation ## Validation
Inputs of application service methods are automatically validated (like ASP.NET Core controller actions). You can use the standard data annotation attributes or custom validation method to perform the validation. ABP also ensures that the input is not null. Inputs of application service methods are automatically validated (like ASP.NET Core controller actions). You can use the standard data annotation attributes or a custom validation method to perform the validation. ABP also ensures that the input is not null.
See the [validation document](Validation.md) for more. See the [validation document](Validation.md) for more.

22
docs/en/AspNet-Boilerplate-Migration-Guide.md

@ -216,9 +216,9 @@ However, it provides the following methods those can be used to query a single e
* `FindAsync(id)` returns the entity or null if not found. * `FindAsync(id)` returns the entity or null if not found.
* `GetAsync(id)` method returns the entity or throws an `EntityNotFoundException` (which causes HTTP 404 status code) if not found. * `GetAsync(id)` method returns the entity or throws an `EntityNotFoundException` (which causes HTTP 404 status code) if not found.
#### Sync vs Async #### Sync vs Async
ABP Framework repository has no sync methods (like `Insert`). All the methods are async (like `InsertAsync`). So, if your application has sync repository method usages, convert them to async versions. ABP Framework repository has no sync methods (like `Insert`). All the methods are async (like `InsertAsync`). So, if your application has sync repository method usages, convert them to async versions.
In general, ABP Framework forces you to completely use async everywhere, because mixing async & sync methods is not a recommended approach. In general, ABP Framework forces you to completely use async everywhere, because mixing async & sync methods is not a recommended approach.
@ -444,7 +444,7 @@ ASP.NET Boilerplate uses Castle Windsor's [logging facility](http://docs.castlep
using Castle.Core.Logging; //1: Import Logging namespace using Castle.Core.Logging; //1: Import Logging namespace
public class TaskAppService : ITaskAppService public class TaskAppService : ITaskAppService
{ {
//2: Getting a logger using property injection //2: Getting a logger using property injection
public ILogger Logger { get; set; } public ILogger Logger { get; set; }
@ -595,7 +595,7 @@ ABP Framework separates it and provides the setting management module (pre-added
ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplate.com/Pages/Documents/Timing)) which is used to abstract the `DateTime` kind, so you can easily switch between Local and UTC times. You don't inject it, but just use the `Clock.Now` static method to obtain the current time. ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplate.com/Pages/Documents/Timing)) which is used to abstract the `DateTime` kind, so you can easily switch between Local and UTC times. You don't inject it, but just use the `Clock.Now` static method to obtain the current time.
ABP Framework has the `IClock` service ([see](Clock.md)) which has a similar goal, but now you need to inject it whenever you need it. ABP Framework has the `IClock` service ([see](Timing.md)) which has a similar goal, but now you need to inject it whenever you need it.
### Event Bus ### Event Bus
@ -693,26 +693,22 @@ public class AbpTenantManagementWebMainMenuContributor : IMenuContributor
var administrationMenu = context.Menu.GetAdministration(); var administrationMenu = context.Menu.GetAdministration();
//Resolve some needed services from the DI container //Resolve some needed services from the DI container
var authorizationService = context.ServiceProvider var l = context.GetLocalizer<AbpTenantManagementResource>();
.GetRequiredService<IAuthorizationService>();
var l = context.ServiceProvider
.GetRequiredService<IStringLocalizer<AbpTenantManagementResource>>();
var tenantManagementMenuItem = new ApplicationMenuItem( var tenantManagementMenuItem = new ApplicationMenuItem(
TenantManagementMenuNames.GroupName, TenantManagementMenuNames.GroupName,
l["Menu:TenantManagement"], l["Menu:TenantManagement"],
icon: "fa fa-users"); icon: "fa fa-users");
administrationMenu.AddItem(tenantManagementMenuItem); administrationMenu.AddItem(tenantManagementMenuItem);
//Conditionally add the "Tenants" menu item based on the permission //Conditionally add the "Tenants" menu item based on the permission
if (await authorizationService if (await context.IsGrantedAsync(TenantManagementPermissions.Tenants.Default))
.IsGrantedAsync(TenantManagementPermissions.Tenants.Default))
{ {
tenantManagementMenuItem.AddItem( tenantManagementMenuItem.AddItem(
new ApplicationMenuItem( new ApplicationMenuItem(
TenantManagementMenuNames.Tenants, TenantManagementMenuNames.Tenants,
l["Tenants"], l["Tenants"],
url: "/TenantManagement/Tenants")); url: "/TenantManagement/Tenants"));
} }
} }
@ -731,4 +727,4 @@ The following features are not present for the ABP Framework. Here, a list of so
* [Real time notification system](https://aspnetboilerplate.com/Pages/Documents/Notification-System) ([#633](https://github.com/abpframework/abp/issues/633)) * [Real time notification system](https://aspnetboilerplate.com/Pages/Documents/Notification-System) ([#633](https://github.com/abpframework/abp/issues/633))
* [NHibernate Integration](https://aspnetboilerplate.com/Pages/Documents/NHibernate-Integration) ([#339](https://github.com/abpframework/abp/issues/339)) - We don't intent to work on this, but any community contribution welcome. * [NHibernate Integration](https://aspnetboilerplate.com/Pages/Documents/NHibernate-Integration) ([#339](https://github.com/abpframework/abp/issues/339)) - We don't intent to work on this, but any community contribution welcome.
Some of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework, it is appreciated. Some of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework, it is appreciated.

3
docs/en/AspNetCore/JavaScript-API/Auth.md

@ -1,3 +0,0 @@
This document has moved.
[Click to navigate to JavaScript Auth document](../../API/JavaScript-API/Auth.md)

3
docs/en/AspNetCore/JavaScript-API/Index.md

@ -1,3 +0,0 @@
This document has moved.
[Click to navigate to JavaScript API document](../../API/JavaScript-API/Index.md)

59
docs/en/Blob-Storing-Azure.md

@ -0,0 +1,59 @@
# BLOB Storing Azure Provider
BLOB Storing Azure Provider can store BLOBs in [Azure Blob storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Azure BLOB as the storage provider.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Azure` package.
* Run `abp add-package Volo.Abp.BlobStoring.Azure` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAzureModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the azure storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAzure(azure =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
### Options
* **ConnectionString** (string): A connection string includes the authorization information required for your application to access data in an Azure Storage account at runtime using Shared Key authorization. Please refer to Azure documentation: https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
* **ContainerName** (string): You can specify the container name in azure. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Azure has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names):
* Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character.
* Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
* All letters in a container name must be **lowercase**.
* Container names must be from **3** through **63** characters long.
* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in azure, `AzureBlobProvider` will try to create it.
## Azure Blob Name Calculator
Azure Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default:
* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container).
* Appends `tenants/<tenant-id>` string if current tenant is not `null`.
* Appends the BLOB name.
## Other Services
* `AzureBlobProvider` is the main service that implements the Azure BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AzureBlobProvider` class).
* `IAzureBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAzureBlobNameCalculator` by default.

177
docs/en/Blob-Storing-Custom-Provider.md

@ -0,0 +1,177 @@
# BLOB Storing: Creating a Custom Provider
This document explains how you can create a new storage provider for the BLOB storing system with an example.
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to create a new storage provider.
## Example Implementation
The first step is to create a class implements the `IBlobProvider` interface or inherit from the `BlobProviderBase` abstract class.
````csharp
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
namespace AbpDemo
{
public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
{
public override Task SaveAsync(BlobProviderSaveArgs args)
{
//TODO...
}
public override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
//TODO...
}
public override Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
//TODO...
}
public override Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
//TODO...
}
}
}
````
* `MyCustomBlobProvider` inherits from the `BlobProviderBase` and overrides the `abstract` methods. The actual implementation is up to you.
* Implementing `ITransientDependency` registers this class to the [Dependency Injection](Dependency-Injection.md) system as a transient service.
> **Notice: Naming conventions are important**. If your class name doesn't end with `BlobProvider`, you must manually register/expose your service for the `IBlobProvider`.
That's all. Now, you can configure containers (inside the `ConfigureServices` method of your [module](Module-Development-Basics.md)) to use the `MyCustomBlobProvider` class:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.ProviderType = typeof(MyCustomBlobProvider);
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) if you want to configure a specific container.
### BlobContainerConfiguration Extension Method
If you want to provide a simpler configuration, create an extension method for the `BlobContainerConfiguration` class:
````csharp
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
this BlobContainerConfiguration containerConfiguration)
{
containerConfiguration.ProviderType = typeof(MyCustomBlobProvider);
return containerConfiguration;
}
}
````
Then you can configure containers easier using the extension method:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider();
});
});
````
### Extra Configuration Options
`BlobContainerConfiguration` allows to add/remove provider specific configuration objects. If your provider needs to additional configuration, you can create a wrapper class to the `BlobContainerConfiguration` for a type-safe configuration option:
````csharp
public class MyCustomBlobProviderConfiguration
{
public string MyOption1
{
get => _containerConfiguration
.GetConfiguration<string>("MyCustomBlobProvider.MyOption1");
set => _containerConfiguration
.SetConfiguration("MyCustomBlobProvider.MyOption1", value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public MyCustomBlobProviderConfiguration(
BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
````
Then you can change the `MyBlobContainerConfigurationExtensions` class like that:
````csharp
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
this BlobContainerConfiguration containerConfiguration,
Action<MyCustomBlobProviderConfiguration> configureAction)
{
containerConfiguration.ProviderType = typeof(MyCustomBlobProvider);
configureAction.Invoke(
new MyCustomBlobProviderConfiguration(containerConfiguration)
);
return containerConfiguration;
}
public static MyCustomBlobProviderConfiguration GetMyCustomBlobProviderConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new MyCustomBlobProviderConfiguration(containerConfiguration);
}
}
````
* Added an action parameter to the `UseMyCustomBlobProvider` method to allow developers to set the additional options.
* Added a new `GetMyCustomBlobProviderConfiguration` method to be used inside `MyCustomBlobProvider` class to obtain the configured values.
Then anyone can set the `MyOption1` as shown below:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider(provider =>
{
provider.MyOption1 = "my value";
});
});
});
````
Finally, you can access to the extra options using the `GetMyCustomBlobProviderConfiguration` method:
````csharp
public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
{
public override Task SaveAsync(BlobProviderSaveArgs args)
{
var config = args.Configuration.GetMyCustomBlobProviderConfiguration();
var value = config.MyOption1;
//...
}
}
````
## Contribute?
If you create a new provider and you think it can be useful for other developers, please consider to [contribute](Contribution/Index.md) to the ABP Framework on GitHub.

98
docs/en/Blob-Storing-Database.md

@ -0,0 +1,98 @@
# BLOB Storing Database Provider
BLOB Storing Database Storage Provider can store BLOBs in a relational or non-relational database.
There are two database providers implemented;
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) package implements for [EF Core](Entity-Framework-Core.md), so it can store BLOBs in [any DBMS supported](https://docs.microsoft.com/en-us/ef/core/providers/) by the EF Core.
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) package implements for [MongoDB](MongoDB.md).
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a database as the storage provider.
## Installation
### Automatic Installation
If you've created your solution based on the [application startup template](Startup-Templates/Application.md), you can use the `abp add-module` [CLI](CLI.md) command to automatically add related packages to your solution.
Open a command prompt (terminal) in the folder containing your solution (`.sln`) file and run the following command:
````bash
abp add-module Volo.Abp.BlobStoring.Database
````
This command adds all the NuGet packages to corresponding layers of your solution. If you are using EF Core, it adds necessary configuration, adds a new database migration and updates the database.
### Manual Installation
Here, all the NuGet packages defined by this provider;
* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Domain.Shared)
* [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain)
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore)
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB)
You can only install Volo.Abp.BlobStoring.Database.EntityFrameworkCore or Volo.Abp.BlobStoring.Database.MongoDB (based on your preference) since they depends on the other packages.
After installation, add `DepenedsOn` attribute to your related [module](Module-Development-Basics.md). Here, the list of module classes defined by the related NuGet packages listed above:
* `BlobStoringDatabaseDomainModule`
* `BlobStoringDatabaseDomainSharedModule`
* `BlobStoringDatabaseEntityFrameworkCoreModule`
* `BlobStoringDatabaseMongoDbModule`
Whenever you add a NuGet package to a project, also add the module class dependency.
If you are using EF Core, you also need to configure your **Migration DbContext** to add BLOB storage tables to your database schema. Call `builder.ConfigureBlobStoring()` extension method inside the `OnModelCreating` method to include mappings to your DbContext. Then you can use the standard `Add-Migration` and `Update-Database` [commands](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create necessary tables in your database.
## Configuration
### Connection String
If you will use your `Default` connection string, you don't need to any additional configuration.
If you want to use a separate database for BLOB storage, use the `AbpBlobStoring` as the [connection string](Connection-Strings.md) name in your configuration file (`appsettings.json`). In this case, also read the [EF Core Migrations](Entity-Framework-Core-Migrations.md) document to learn how to create and use a different database for a desired module.
### Configuring the Containers
If you are using only the database storage provider, you don't need to manually configure it, since it is automatically done. If you are using multiple storage providers, you may want to configure it.
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the database storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseDatabase();
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
## Additional Information
It is expected to use the [BLOB Storing services](Blob-Storing.md) to use the BLOB storing system. However, if you want to work on the database tables/entities, you can use the following information.
### Entities
Entities defined for this module:
* `DatabaseBlobContainer` (aggregate root) represents a container stored in the database.
* `DatabaseBlob` (aggregate root) represents a BLOB in the database.
See the [entities document](Entities.md) to learn what is an entity and aggregate root.
### Repositories
* `IDatabaseBlobContainerRepository`
* `IDatabaseBlobRepository`
You can also use `IRepository<DatabaseBlobContainer, Guid>` and `IRepository<DatabaseBlob, Guid>` to take the power of IQueryable. See the [repository document](Repositories.md) for more.
### Other Services
* `DatabaseBlobProvider` is the main service that implements the database BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `DatabaseBlobProvider` class).

59
docs/en/Blob-Storing-File-System.md

@ -0,0 +1,59 @@
# BLOB Storing File System Provider
File System Storage Provider is used to store BLOBs in the local file system as standard files inside a folder.
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use the file system.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.FileSystem` package.
* Run `abp add-package Volo.Abp.BlobStoring.FileSystem` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringFileSystemModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the File System storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = "C:\\my-files";
});
});
});
````
`UseFileSystem` extension method is used to set the File System Provider for a container and configure the file system options.
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
### Options
* **BasePath** (string): The base folder path to store BLOBs. It is required to set this option.
* **AppendContainerNameToBasePath** (bool; default: `true`): Indicates whether to create a folder with the container name inside the base folder. If you store multiple containers in the same `BaseFolder`, leave this as `true`. Otherwise, you can set it to `false` if you don't like an unnecessarily deeper folder hierarchy.
## File Path Calculation
File System Provider organizes BLOB files inside folders and implements some conventions. The full path of a BLOB file is determined by the following rules by default:
* It starts with the `BasePath` configured as shown above.
* Appends `host` folder if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container).
* Appends `tenants/<tenant-id>` folder if current tenant is not `null`.
* Appends the container's name if `AppendContainerNameToBasePath` is `true`. If container name contains `/`, this will result with nested folders.
* Appends the BLOB name. If the BLOB name contains `/` it creates folders. If the BLOB name contains `.` it will have a file extension.
## Extending the File System BLOB Provider
* `FileSystemBlobProvider` is the main service that implements the File System storage. You can inherit from this class and [override](Customizing-Application-Modules-Overriding-Services.md) methods to customize it.
* The `IBlobFilePathCalculator` service is used to calculate the file paths. Default implementation is the `DefaultBlobFilePathCalculator`. You can replace/override it if you want to customize the file path calculation.

305
docs/en/Blob-Storing.md

@ -0,0 +1,305 @@
# BLOB Storing
It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. [BLOB](https://en.wikipedia.org/wiki/Binary_large_object)s, into a **storage**. For example, you may want to save user profile pictures.
A BLOB is a typically **byte array**. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be options.
The ABP Framework provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits;
* You can **easily integrate** to your favorite BLOB storage provides with a few lines of configuration.
* You can then **easily change** your BLOB storage without changing your application code.
* If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored.
ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](Multi-Tenancy.md).
## BLOB Storage Providers
The ABP Framework has already the following storage provider implementations;
* [File System](Blob-Storing-File-System.md): Stores BLOBs in a folder of the local file system, as standard files.
* [Database](Blob-Storing-Database.md): Stores BLOBs in a database.
* [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
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.
Multiple providers **can be used together** by the help of the **container system**, where each container can uses a different provider.
> BLOB storing system can not work unless you **configure a storage provider**. Refer to the linked documents for the storage provider configurations.
## Installation
[Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring) is the main package that defines the BLOB storing services. You can use this package to use the BLOB Storing system without depending a specific storage provider.
Use the ABP CLI to add this package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), if you haven't installed it.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring` package.
* Run `abp add-package Volo.Abp.BlobStoring` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## The IBlobContainer
`IBlobContainer` is the main interface to store and read BLOBs. Your application may have multiple containers and each container can be separately configured. But, there is a **default container** that can be simply used by [injecting](Dependency-Injection.md) the `IBlobContainer`.
**Example: Simply save and read bytes of a named BLOB**
````csharp
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IBlobContainer _blobContainer;
public MyService(IBlobContainer blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveBytesAsync(byte[] bytes)
{
await _blobContainer.SaveAsync("my-blob-1", bytes);
}
public async Task<byte[]> GetBytesAsync()
{
return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1");
}
}
}
````
This service saves the given bytes with the `my-blob-1` name and then gets the previously saved bytes with the same name.
> A BLOB is a named object and **each BLOB should have a unique name**, which is an arbitrary string.
`IBlobContainer` can work with `Stream` and `byte[]` objects, which will be detailed in the next sections.
### Saving BLOBs
`SaveAsync` method is used to save a new BLOB or replace an existing BLOB. It can save a `Stream` by default, but there is a shortcut extension method to save byte arrays.
`SaveAsync` gets the following parameters:
* **name** (string): Unique name of the BLOB.
* **stream** (Stream) or **bytes** (byte[]): The stream to read the BLOB content or a byte array.
* **overrideExisting** (bool): Set `true` to replace the BLOB content if it does already exists. Default value is `false` and throws `BlobAlreadyExistsException` if there is already a BLOB in the container with the same name.
### Reading/Getting BLOBs
* `GetAsync`: Only gets a BLOB name and returns a `Stream` object that can be used to read the BLOB content. Always **dispose the stream** after using it. This method throws exception, if it can not find the BLOB with the given name.
* `GetOrNullAsync`: In opposite to the `GetAsync` method, this one returns `null` if there is no BLOB found with the given name.
* `GetAllBytesAsync`: Returns a `byte[]` instead of a `Stream`. Still throws exception if can not find the BLOB with the given name.
* `GetAllBytesOrNullAsync`: In opposite to the `GetAllBytesAsync` method, this one returns `null` if there is no BLOB found with the given name.
### Deleting BLOBs
`DeleteAsync` method gets a BLOB name and deletes the BLOB data. It doesn't throw any exception if given BLOB was not found. Instead, it returns a `bool` indicating that the BLOB was actually deleted or not, if you care about it.
### Other Methods
* `ExistsAsync` method simply checks if there is a BLOB in the container with the given name.
### About Naming the BLOBs
There is not a rule for naming the BLOBs. A BLOB name is just a string that is unique per container (and per tenant - see the "*Multi-Tenancy*" section). However, different storage providers may conventionally implement some practices. For example, the [File System Provider](Blob-Storing-File-System.md) use directory separators (`/`) and file extensions in your BLOB name (if your BLOB name is `images/common/x.png` then it is saved as `x.png` in the `images/common` folder inside the root container folder).
## Typed IBlobContainer
Typed BLOB container system is a way of creating and managing **multiple containers** in an application;
* **Each container is separately stored**. That means the BLOB names should be unique in a container and two BLOBs with the same name can live in different containers without effecting each other.
* **Each container can be separately configured**, so each container can use a different storage provider based on your configuration.
To create a typed container, you need to create a simple class decorated with the `BlobContainerName` attribute:
````csharp
using Volo.Abp.BlobStoring;
namespace AbpDemo
{
[BlobContainerName("profile-pictures")]
public class ProfilePictureContainer
{
}
}
````
> If you don't use the `BlobContainerName` attribute, ABP Framework uses the full name of the class (with namespace), but it is always recommended to use a container name which is stable and does not change even if you rename the class.
Once you create the container class, you can inject `IBlobContainer<T>` for your container type.
**Example: An [application service](Application-Services.md) to save and read profile picture of the [current user](CurrentUser.md)**
````csharp
[Authorize]
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer<ProfilePictureContainer> _blobContainer;
public ProfileAppService(IBlobContainer<ProfilePictureContainer> blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveProfilePictureAsync(byte[] bytes)
{
var blobName = CurrentUser.GetId().ToString();
await _blobContainer.SaveAsync(blobName, bytes);
}
public async Task<byte[]> GetProfilePictureAsync()
{
var blobName = CurrentUser.GetId().ToString();
return await _blobContainer.GetAllBytesOrNullAsync(blobName);
}
}
````
`IBlobContainer<T>` has the same methods with the `IBlobContainer`.
> It is a good practice to **always use a typed container while developing re-usable modules**, so the final application can configure the provider for your container without effecting the other containers.
### The Default Container
If you don't use the generic argument and directly inject the `IBlobContainer` (as explained before), you get the default container. Another way of injecting the default container is using `IBlobContainer<DefaultContainer>`, which returns exactly the same container.
The name of the default container is `Default`.
### Named Containers
Typed containers are just shortcuts for named containers. You can inject and use the `IBlobContainerFactory` to get a BLOB container by its name:
````csharp
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer _blobContainer;
public ProfileAppService(IBlobContainerFactory blobContainerFactory)
{
_blobContainer = blobContainerFactory.Create("profile-pictures");
}
//...
}
````
## IBlobContainerFactory
`IBlobContainerFactory` is the service that is used to create the BLOB containers. One example was shown above.
**Example: Create a container by name**
````csharp
var blobContainer = blobContainerFactory.Create("profile-pictures");
````
**Example: Create a container by type**
````csharp
var blobContainer = blobContainerFactory.Create<ProfilePictureContainer>();
````
> You generally don't need to use the `IBlobContainerFactory` since it is used internally, when you inject a `IBlobContainer` or `IBlobContainer<T>`.
## Configuring the Containers
Containers should be configured before using them. The most fundamental configuration is to **select a BLOB storage provider** (see the "*BLOB Storage Providers*" section above).
`AbpBlobStoringOptions` is the [options class](Options.md) to configure the containers. You can configure the options inside the `ConfigureServices` method of your [module](Module-Development-Basics.md).
### Configure a Single Container
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
//TODO...
});
});
````
This example configures the `ProfilePictureContainer`. You can also configure by the container name:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure("profile-pictures", container =>
{
//TODO...
});
});
````
### Configure the Default Container
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
//TODO...
});
});
````
> There is a special case about the default container; If you don't specify a configuration for a container, it **fallbacks to the default container configuration**. This is a good way to configure defaults for all containers and specialize configuration for a specific container when needed.
### Configure All Containers
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
//TODO...
});
});
````
This is a way to configure all the containers.
> The main difference from configuring the default container is that `ConfigureAll` overrides the configuration even if it was specialized for a specific container.
## Multi-Tenancy
If your application is set as multi-tenant, the BLOB Storage system **works seamlessly with the [multi-tenancy](Multi-Tenancy.md)**. All the providers implement multi-tenancy as a standard feature. They **isolate BLOBs** of different tenants from each other, so they can only access to their own BLOBs. It means you can use the **same BLOB name for different tenants**.
If your application is multi-tenant, you may want to control **multi-tenancy behavior** of the containers individually. For example, you may want to **disable multi-tenancy** for a specific container, so the BLOBs inside it will be **available to all the tenants**. This is a way to share BLOBs among all tenants.
**Example: Disable multi-tenancy for a specific container**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
container.IsMultiTenant = false;
});
});
````
> If your application is not multi-tenant, no worry, it works as expected. You don't need to configure the `IsMultiTenant` option.
## Extending the BLOB Storing System
Most of the times, you won't need to customize the BLOB storage system except [creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md). However, you can replace any service (injected via [dependency injection](Dependency-Injection.md)), if you need. Here, some other services not mentioned above, but you may want to know:
* `IBlobProviderSelector` is used to get a `IBlobProvider` instance by a container name. Default implementation (`DefaultBlobProviderSelector`) selects the provider using the configuration.
* `IBlobContainerConfigurationProvider` is used to get the `BlobContainerConfiguration` for a given container name. Default implementation (`DefaultBlobContainerConfigurationProvider`) gets the configuration from the `AbpBlobStoringOptions` explained above.
## BLOB Storing vs File Management System
Notice that BLOB storing is not a file management system. It is a low level system that is used to save, get and delete named BLOBs. It doesn't provide a hierarchical structure like directories, you may expect from a typical file system.
If you want to create folders and move files between folders, assign permissions to files and share files between users then you need to implement your own application on top of the BLOB Storage system.
## See Also
* [Creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md)

296
docs/en/Blog-Posts/2020-06-05 v2_9_Release/Post.md

@ -0,0 +1,296 @@
# ABP Framework v2.9 Has Been Released
The **ABP Framework** & and the **ABP Commercial** version 2.9 have been released, which are the last versions before v3.0! This post will cover **what's new** with these this release.
## What's New with the ABP Framework 2.9?
You can see all the changes on the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0). This post will only cover the important features/changes.
### Pre-Compiling Razor Pages
Pre-built pages (for [the application modules](https://docs.abp.io/en/abp/latest/Modules/Index)) and view components were compiling on runtime until this version. Now, they are pre-compiled and we've measured that the application startup time (especially for the MVC UI) has been reduced more than 50%. In other words, it is **two-times faster** than the previous version. The speed change also effects when you visit a page for the first time.
Here, a test result for the startup application template with v2.8 and v.2.9:
````
### v2.8
2020-06-04 22:59:04.891 +08:00 [INF] Starting web host.
2020-06-04 22:59:07.662 +08:00 [INF] Now listening on: https://localhost:44391
2020-06-04 22:59:17.315 +08:00 [INF] Request finished in 7756.6218ms 200 text/html;
Total: 12.42s
### v2.9
2020-06-04 22:59:13.720 +08:00 [INF] Starting web host.
2020-06-04 22:59:16.639 +08:00 [INF] Now listening on: https://localhost:44369
2020-06-04 22:59:18.957 +08:00 [INF] Request finished in 1780.5461ms 200 text/html;
Total: 5.24s
````
You do nothing to get the benefit of the new approach. [Overriding UI pages/components](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface) are also just working as before. We will be working on more performance improvements in the v3.0.
### Organization Unit System
[The Identity Module](https://docs.abp.io/en/abp/latest/Modules/Identity) now has the most requested feature: Organization Units!
Organization unit system is used to create a hierarchical organization tree in your application. You can then use this organization tree to authorize data and functionality in your application.
The documentation will come soon...
### New Blob Storing Package
We've created a new [Blob Storing package](https://www.nuget.org/packages/Volo.Abp.BlobStoring) to store arbitrary binary objects. It is generally used to store the content of the files in your application. This package provides an abstraction, so any application or [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics) can save and retrieve files independent from the actual storing provider.
There are two storage provider currently implemented:
* [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) package stores objects/files in the local file system.
* [Volo.Abp.BlobStoring.Database](https://github.com/abpframework/abp/tree/dev/modules/blob-storing-database) module stores objects/files in a database. It currently supports [Entity Framework Core](https://docs.abp.io/en/abp/latest/Entity-Framework-Core) (so, you can use [any relational DBMS](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS)) and [MongoDB](https://docs.abp.io/en/abp/latest/MongoDB).
[Azure BLOB provider](https://github.com/abpframework/abp/issues/4098) will be available with v3.0. You can request other cloud providers or contribute yourself on the [GitHub repository](https://github.com/abpframework/abp/issues/new).
One of the benefits of the blob storing system is that it allows you to create multiple containers (each container is a blob storage) and use different storage providers for each container.
**Example: Use the default container to save and get a byte array**
````csharp
public class MyService : ITransientDependency
{
private readonly IBlobContainer _container;
public MyService(IBlobContainer container)
{
_container = container;
}
public async Task FooAsync()
{
//Save a BLOB
byte[] bytes = GetBytesFromSomeWhere();
await _container.SaveAsync("my-unique-blob-name", bytes);
//Retrieve a BLOB
bytes = await _container.GetAllBytesAsync("my-unique-blob-name");
}
}
````
It can work with `byte[]` and `Stream` objects.
**Example: Use a typed (named) container to save and get a stream**
````csharp
public class MyService : ITransientDependency
{
private readonly IBlobContainer<TestContainer> _container;
public MyService(IBlobContainer<TestContainer> container)
{
_container = container;
}
public async Task FooAsync()
{
//Save a BLOB
Stream stream = GetStreamFromSomeWhere();
await _container.SaveAsync("my-unique-blob-name", stream);
//Retrieve a BLOB
stream = await _container.GetAsync("my-unique-blob-name");
}
}
````
`TestContainer` is an empty class that has no purpose than identifying the container:
````csharp
[BlobContainerName("test")] //specifies the name of the container
public class TestContainer
{
}
````
A typed (named) container can be configured to use a different storing provider than the default one. It is a good practice to always use a typed container while developing re-usable modules, so the final application can configure provider for this container without effecting the other containers.
**Example: Configure the File System provider for the `TestContainer`**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<TestContainer>(configuration =>
{
configuration.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = "C:\\MyStorageFolder";
});
});
});
````
See the [blob storing documentation](https://docs.abp.io/en/abp/latest/Blob-Storing) for more information.
### Oracle Integration Package for Entity Framework Core
We've created an [integration package for Oracle](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart), so you can easily switch to the Oracle for the EF Core. It is tested for the framework and pre-built modules.
[See the documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle) to start using the Oracle integration package.
### Automatically Determining the Database Provider
When you develop a **reusable application module** with EF Core integration, you generally want to develop your module **DBMS independent**. However, there are minor (sometimes major) differences between different DBMSs. If you perform a custom mapping based on the DBMS, you can now use `ModelBuilder.IsUsingXXX()` extension methods:
````csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Phone>(b =>
{
//...
if (modelBuilder.IsUsingPostgreSql()) //Check if using PostgreSQL!
{
b.Property(x => x.Number).HasMaxLength(20);
}
else
{
b.Property(x => x.Number).HasMaxLength(32);
}
});
}
````
Beside the stupid example above, you can configure your mapping however you need!
### ABP CLI: Translate Command
`abp translate` is a new command that simplifies to translate [localization](https://docs.abp.io/en/abp/latest/Localization) files when you have multiple JSON localization files in a source control repository.
The main purpose of this command is to **translate the ABP Framework** localization files (since the [abp repository](https://github.com/abpframework/abp) has tens of localization files to be translated in different folders).
It is appreciated if you use this command to translate the framework resources **for your mother language**.
See [the documentation](https://docs.abp.io/en/abp/latest/CLI#translate) to learn how to use it. Also see [the contribution guide](https://docs.abp.io/en/abp/latest/Contribution/Index).
### The New Virtual File System Explorer Module
Thanks to [@liangshiw](https://github.com/liangshiw) created and contributed a new module to explore files in the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). It works for MVC UI and shows all the virtual files in the application. Example screenshots:
![virtual-file-explorer-1](virtual-file-explorer-1.png)
![virtual-file-explorer-2](virtual-file-explorer-2.png)
[See the documentation](https://docs.abp.io/en/abp/latest/Modules/Virtual-File-Explorer) to learn how to use it.
### Sample Application: SignalR with Tiered Architecture
Implementing SignalR in a distributed/tiered architecture can be challenging. We've created a sample application that demonstrate how to implement it using the [SignalR integration](https://docs.abp.io/en/abp/latest/SignalR-Integration) and the [distributed event bus](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) system easily.
See [the source code](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo) of the sample solution.
**An article is on the road** that will deeply explain the solution. Follow the [@abpframework](https://twitter.com/abpframework) Twitter account.
![signalr-tiered-demo](signalr-tiered-demo.png)
*A picture from the article that shows the communication diagram of the solution*
### About gRPC
We've created a sample application to show how to create and consume gRPC endpoints in your ABP based applications.
See [the source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) on GitHub.
We were planning to create gRPC endpoints for all the pre-built application modules, but we see that ASP.NET Core gRPC integration is not mature enough and doesn't support some common deployment scenarios yet. So, deferring this to the next versions ([see this comment](https://github.com/abpframework/abp/issues/2882#issuecomment-633080242) for more). However, it is pretty standard if you want to use gRPC in your applications. ABP Framework has no issue with gRPC. Just check the [sample application](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo).
### Others
* [Time zone system](https://github.com/abpframework/abp/pull/3933) to support different time zones for an application.
* Support for [virtual path deployment](https://github.com/abpframework/abp/issues/4089) on IIS.
* RTL support for the Angular UI.
See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0) for others updates.
## What's New with the ABP Commercial 2.9
In addition to all the features coming with the ABP Framework, the ABP Commercial has additional features with this release, as always. This section covers the [ABP Commercial](https://commercial.abp.io/) highlights in the version 2.9.
### Organization Unit Management UI
We've created the UI for manage organization units, their members and roles for the ABP Commercial [Identity Module](https://commercial.abp.io/modules/Volo.Identity.Pro):
![organization-units](organization-units.png)
OU management is available for both of the MVC (Razor Pages) and the Angular user interfaces.
> See [this entry](https://support.abp.io/QA/Questions/222/Bugs--Problems-v290#answer-3cf5eba3-0bf1-2aa1-cc5e-39f5a0750329) if you're upgrading your solution from an earlier version.
### Chat Module Angular UI
We had introduced a new [chat module](https://commercial.abp.io/modules/Volo.Chat) in the previous version, which was only supporting the ASP.NET Core MVC / Razor Pages UI. Now, it has also an Angular UI option.
![abp-chat-module](abp-chat-module.png)
*A screenshot from the chat module - two users are sending messages to each other*
### Easy CRM Angular UI
Easy CRM is a sample application that is built on the ABP Commercial to provide a relatively complex application to the ABP Commercial customers. In the version 2.7, we have lunched it with MVC / Razor Pages UI. With the 2.9 version, we are releasing the Angular UI for the Easy CRM application.
![easy-crm](easy-crm.png)
*A screenshot from the "Order Details" page of the Easy CRM application.*
See the [Easy CRM document](https://docs.abp.io/en/commercial/latest/samples/easy-crm) to learn how to download and run it.
### Module Code Generation for the ABP Suite
[ABP Suite](https://commercial.abp.io/tools/suite) is a tool that's main feature is to [generate code](https://docs.abp.io/en/commercial/latest/abp-suite/generating-crud-page) for complete CRUD functionality for an entity, from database to the UI layer.
![suite](suite.png)
*A screenshot from the ABP Suite: Define the properties of a new entity and let it to create the application code for you!*
It was working only for [the application template](https://docs.abp.io/en/commercial/latest/startup-templates/application/index) until this release. Now, it supports to generate code for the [module projects](https://docs.abp.io/en/commercial/latest/startup-templates/module/index) too. That's a great way to create reusable application modules by taking the power of the code generation.
In addition to this main feature, we added many minor enhancements on the ABP Suite in this release.
> Notice: Generating code for the module template is currently in beta. Please inform us if you find any bug.
### Lepton Theme
[Lepton Theme](https://commercial.abp.io/themes) is the commercial theme we've developed for the ABP Commercial;
* It is 100% bootstrap compatible - so you don't write theme specific HTML!
* Provides different kind of styles - you see the material style in the picture below.
* Provides different kind of layouts (side/top menu, fluid/boxed layout...).
* It is lightweight, responsive and modern.
* And... it is upgradeable with no cost! You just update a NuGet/NPM package to get the new features.
We've create its own web site: [http://leptontheme.com/](http://leptontheme.com/)
You can view all the components together, independent from an application:
![lepton-theme](lepton-theme.png)
This web site is currently in a very early stage. We will be documenting and improving this web site to be a reference for your development and explore the features of the theme.
### Coming Soon: The File management Module
Based on the new blob storing system (introduced above), we've started to build a file management module that is used to manage (navigate/upload/download) a hierarchical file system on your application and share the files between your users and with your customers.
We plan to release the initial version with the ABP Commercial v3.0 and continue to improve it with the subsequent releases.
## About the Next Version: 3.0
We have added many new features with the [v2.8](https://blog.abp.io/abp/ABP-v2.8.0-Releases-%26-Road-Map) and v2.9. In the next version, we will completely focus on the **documentation, performance improvements** and and other enhancements as well as bug fixes.
For a long time, we were releasing a new feature version in every 2 weeks. We will continue to this approach after v3.0. But, as an exception to the v3.0, the development cycle will be ~4 weeks. **The planned release date for the v3.0 is the July 1, 2020**.
## Bonus: Articles!
Beside developing our products, our team are constantly writing articles/tutorials on various topics. You may want to check the latest articles:
* [ASP.NET Core 3.1 WebHook Implementation Using Pub/Sub](https://volosoft.com/blog/ASP.NET-CORE-3.1-Webhook-Implementation-Using-Pub-Sub)
* [Using Azure Key Vault with ASP.NET Core](https://volosoft.com/blog/Using-Azure-Key-Vault-with-ASP.NET-Core)

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/abp-chat-module.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/easy-crm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/lepton-theme.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/organization-units.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/signalr-tiered-demo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/suite.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

1
docs/en/CLI.md

@ -87,6 +87,7 @@ abp new Acme.BookStore
* `mongodb`: MongoDB. * `mongodb`: MongoDB.
* **`module`**: [Module template](Startup-Templates/Module.md). Additional options: * **`module`**: [Module template](Startup-Templates/Module.md). Additional options:
* `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI). * `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI).
* **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`). * `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`).

3
docs/en/Clock.md

@ -1,3 +0,0 @@
# Clock
TODO

2
docs/en/Connection-Strings.md

@ -72,7 +72,7 @@ Relational databases require to create the database and the database schema (tab
The startup template (with EF Core ORM) comes with a single database and a `.EntityFrameworkCore.DbMigrations` project that contains the migration files for that database. This project mainly defines a *YourProjectName*MigrationsDbContext that calls the `Configure...()` methods of the used modules, like `builder.ConfigurePermissionManagement()`. The startup template (with EF Core ORM) comes with a single database and a `.EntityFrameworkCore.DbMigrations` project that contains the migration files for that database. This project mainly defines a *YourProjectName*MigrationsDbContext that calls the `Configure...()` methods of the used modules, like `builder.ConfigurePermissionManagement()`.
Once you want to separate a module's database, you typically will need to create a second migration path. The easiest way to create a copy of the `.EntityFrameworkCore.DbMigrations` project with the `DbContext` inside it, change its content to only call the `Configure...()` methods of the modules needs to be stored in the second database and re-create the initial migration. In this case, you also need to change the `.DbMigrator` application to be able to work with these second database too. In this way, you will have a separate migrations DbContext per database. Once you want to separate a module's database, you typically will need to create a second migration path. See the [EF Core Migrations](Entity-Framework-Core-Migrations.md) document to learn how to create and use a different database for a desired module.
## Multi-Tenancy ## Multi-Tenancy

19
docs/en/Contribution/Index.md

@ -41,19 +41,22 @@ A new language is published after these minimum translations have been completed
ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application. ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application.
In addition to that, the framework and pre-build modules have already localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). In addition to that, the framework and the [pre-build modules](https://docs.abp.io/en/abp/latest/Modules/Index) have localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
You can create a new file in the [same folder](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi) to translate it. #### Using the "abp translate" command
This is the recommended approach, since it automatically finds all missing texts for a specific culture and lets you to translate in one place.
* Clone the [ABP repository](https://github.com/abpframework/abp/) from Github. * Clone the [ABP repository](https://github.com/abpframework/abp/) from Github.
* Create a new file for the target language for a localization text (json) file (near to the en.json file). * Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Copy all texts from the en.json file. * Run `abp translate -c <culture-name>` command for your language in the root folder of the abp repository. For example, use `abp translate -c fr` for French. Check [this document](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes) to find the culture code for your language.
* Translate the texts. * This command creates a file in the same folder, named `abp-translation.json`. Open this file in your favorite editor and fill the missing text values.
* Send pull request on Github. * Once you done the translation, use `abp translate -a` command to apply changes to the related files.
* Send a pull request on GitHub.
You can also use the `abp translate` command of [ABP CLI](CLI.md) to translate localized texts. #### Manual Translation
ABP is a modular framework. So there are many localization text resource, one per module. To find all .json files, you can search for "en.json" after cloning the repository. You can also check [this list](Localization-Text-Files.md) for a list of localization text files. If you want to make a change on a specific resource file, you can find the file yourself, make the necessary change (or create a new file for your language) and send a pull request on GitHub.
### Blog Posts & Tutorials ### Blog Posts & Tutorials

166
docs/en/CurrentUser.md

@ -1,3 +1,167 @@
# Current User # Current User
TODO! It is very common to retrieve the information about the logged in user in a web application. The current user is the active user related to the current request in a web application.
## ICurrentUser
`ICurrentUser` is the main service to get info about the current active user.
Example: [Injecting](Dependency-Injection.md) the `ICurrentUser` into a service:
````csharp
using System;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly ICurrentUser _currentUser;
public MyService(ICurrentUser currentUser)
{
_currentUser = currentUser;
}
public void Foo()
{
Guid? userId = _currentUser.Id;
}
}
}
````
Common base classes have already injected this service as a base property. For example, you can directly use the `CurrentUser` property in an [application service](Application-Services.md):
````csharp
using System;
using Volo.Abp.Application.Services;
namespace AbpDemo
{
public class MyAppService : ApplicationService
{
public void Foo()
{
Guid? userId = CurrentUser.Id;
}
}
}
````
### Properties
Here are the fundamental properties of the `ICurrentUser` interface:
* **IsAuthenticated** (bool): Returns `true` if the current user has logged in (authenticated). If the user has not logged in then `Id` and `UserName` returns `null`.
* **Id** (Guid?): Id of the current user. Returns `null`, if the current user has not logged in.
* **UserName** (string): User name of the current user. Returns `null`, if the current user has not logged in.
* **TenantId** (Guid?): Tenant Id of the current user, which can be useful for a [multi-tenant](Multi-Tenancy.md) application. Returns `null`, if the current user is not assigned to a tenant.
* **Email** (string): Email address of the current user.Returns `null`, if the current user has not logged in or not set an email address.
* **EmailVerified** (bool): Returns `true`, if the email address of the current user has been verified.
* **PhoneNumber** (string): Phone number of the current user. Returns `null`, if the current user has not logged in or not set a phone number.
* **PhoneNumberVerified** (bool): Returns `true`, if the phone number of the current user has been verified.
* **Roles** (string[]): Roles of the current user. Returns a string array of the role names of the current user.
### Methods
`ICurrentUser` is implemented on the `ICurrentPrincipalAccessor` (see the section below) and works with the claims. So, all of the above properties are actually retrieved from the claims of the current authenticated user.
`ICurrentUser` has some methods to directly work with the claims, if you have custom claims or get other non-common claim types.
* **FindClaim**: Gets a claim with the given name. Returns `null` if not found.
* **FindClaims**: Gets all the claims with the given name (it is allowed to have multiple claim values with the same name).
* **GetAllClaims**: Gets all the claims.
* **IsInRole**: A shortcut method to check if the current user is in the specified role.
Beside these standard methods, there are some extension methods:
* **FindClaimValue**: Gets the value of the claim with the given name, or `null` if not found. It has a generic overload that also casts the value to a specific type.
* **GetId**: Returns `Id` of the current user. If the current user has not logged in, it throws an exception (instead of returning `null`) . Use this only if you are sure that the user has already authenticated in your code context.
### Authentication & Authorization
`ICurrentUser` works independently of how the user is authenticated or authorized. It seamlessly works with any authentication system that works with the current principal (see the section below).
## ICurrentPrincipalAccessor
`ICurrentPrincipalAccessor` is the service that should be used (by the ABP Framework and your application code) whenever the current principle of the current user is needed.
For a web application, it gets the `User` property of the current `HttpContext`. For a non-web application, it returns the `Thread.CurrentPrincipal`.
> You generally don't need to this low level `ICurrentPrincipalAccessor` service and directly work with the `ICurrentUser` explained above.
### Basic Usage
You can inject `ICurrentPrincipalAccessor` and use the `Principal` property to the the current principal:
````csharp
public class MyService : ITransientDependency
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public MyService(ICurrentPrincipalAccessor currentPrincipalAccessor)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public void Foo()
{
var allClaims = _currentPrincipalAccessor.Principal.Claims.ToList();
//...
}
}
````
### Changing the Current Principle
Current principle is not something you want to set or change, except at some advanced scenarios. If you need it, use the `Change` method of the `ICurrentPrincipalAccessor`. It takes a `ClaimsPrinciple` object and makes it "current" for a scope.
Example:
````csharp
public class MyAppService : ApplicationService
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public MyAppService(ICurrentPrincipalAccessor currentPrincipalAccessor)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public void Foo()
{
var newPrinciple = new ClaimsPrincipal(
new ClaimsIdentity(
new Claim[]
{
new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString()),
new Claim(AbpClaimTypes.UserName, "john"),
new Claim("MyCustomCliam", "42")
}
)
);
using (_currentPrincipalAccessor.Change(newPrinciple))
{
var userName = CurrentUser.UserName; //returns "john"
//...
}
}
}
````
Use the `Change` method always in a `using` statement, so it will be restored to the original value after the `using` scope ends.
This can be a way to simulate a user login for a scope of the application code, however try to use it carefully.
## AbpClaimTypes
`AbpClaimTypes` is a static class that defines the names of the standard claims and used by the ABP Framework.
* Default values for the `UserName`, `UserId`, `Role` and `Email` properties are set from the [System.Security.Claims.ClaimTypes](https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes) class, but you can change them.
* Other properties, like `EmailVerified`, `PhoneNumber`, `TenantId`... are defined by the ABP Framework by following the standard names wherever possible.
It is suggested to use properties of this class instead of magic strings for claim names.

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

@ -59,6 +59,7 @@ In most cases, you will want to change one or a few methods of the current imple
### Example: Overriding an Application Service ### Example: Overriding an Application Service
````csharp ````csharp
//[RemoteService(IsEnabled = false)] // If you use dynamic controller feature you can disable remote service. Prevent creating duplicate controller for the application service.
[Dependency(ReplaceServices = true)] [Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))] [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))]
public class MyIdentityUserAppService : IdentityUserAppService public class MyIdentityUserAppService : IdentityUserAppService

159
docs/en/Data-Seeding.md

@ -1,3 +1,160 @@
# Data Seeding # Data Seeding
TODO ## Introduction
Some applications (or modules) using a database may need to have some **initial data** to be able to properly start and run. For example, an **admin user** & roles must be available at the beginning. Otherwise you can not **login** to the application to create new users and roles.
Data seeding is also useful for [testing](Testing.md) purpose, so your automatic tests can assume some initial data available in the database.
### Why a Data Seed System?
While EF Core Data Seeding system provides a way, it is very limited and doesn't cover production scenarios. Also, it is only for EF Core.
ABP Framework provides a data seed system that is;
* **Modular**: Any [module](Module-Development-Basics.md) can silently contribute to the data seeding process without knowing and effecting each other. In this way, a module seeds its own initial data.
* **Database Independent**: It is not only for EF Core, it also works for other database providers (like [MongoDB](MongoDB.md)).
* **Production Ready**: It solves the problems on production environments. See the "*On Production*" section below.
* **Dependency Injection**: It takes the full advantage of dependency injection, so you can use any internal or external service while seeding the initial data. Actually, you can do much more than data seeding.
## IDataSeedContributor
`IDataSeedContributor` is the interface that should be implemented in order to seed data to the database.
**Example: Seed one initial book to the database if there is no book**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() > 0)
{
return;
}
var book = new Book(
id: _guidGenerator.Create(),
name: "The Hitchhiker's Guide to the Galaxy",
type: BookType.ScienceFiction,
publishDate: new DateTime(1979, 10, 12),
price: 42
);
await _bookRepository.InsertAsync(book);
}
}
}
````
* `IDataSeedContributor` defines the `SeedAsync` method to execute the **data seed logic**.
* It is typical to **check database** if the seeding data is already present.
* You can **inject** service and perform any logic needed to seed the data.
> Data seed contributors are automatically discovered by the ABP Framework and executed as a part of the data seed process.
### DataSeedContext
`DataSeedContext` contains `TenantId` if your application is [multi-tenant](Multi-Tenancy.md), so you can use this value while inserting data or performing custom logic based on the tenant.
`DataSeedContext` also contains name-value style configuration parameters for passing to the seeder contributors from the `IDataSeeder`.
## Modularity
An application can have multiple data seed contributor (`IDataSeedContributor`) class. So, any reusable module can also implement this interface to seed its own initial data.
For example, the [Identity Module](Modules/Identity.md) has a data seed contributor that creates an admin role and admin user and assign all the permissions.
## IDataSeeder
> You typically never need to directly use the `IDataSeeder` service since it is already done if you've started with the [application startup template](Startup-Templates/Application.md). But its suggested to read it to understand the design behind the data seed system.
`IDataSeeder` is the main service that is used to seed initial data. It is pretty easy to use;
````csharp
public class MyService : ITransientDependency
{
private readonly IDataSeeder _dataSeeder;
public MyService(IDataSeeder dataSeeder)
{
_dataSeeder = dataSeeder;
}
public async Task FooAsync()
{
await _dataSeeder.SeedAsync();
}
}
````
You can [inject](Dependency-Injection.md) the `IDataSeeder` and use it to seed the initial data when you need. It internally calls all the `IDataSeedContributor` implementations to complete the data seeding.
It is possible to send named configuration parameters to the `SeedAsync` method as shown below:
````csharp
await _dataSeeder.SeedAsync(
new DataSeedContext()
.WithProperty("MyProperty1", "MyValue1")
.WithProperty("MyProperty2", 42)
);
````
Then the data seed contributors can access to these properties via the `DataSeedContext` explained before.
If a module needs to a parameter, it should be declared on the [module documentation](Modules/Index.md). For example, the [Identity Module](Modules/Identity.md) can use `AdminEmail` and `AdminPassword` parameters if you provide (otherwise uses the default values).
### Where & How to Seed Data?
It is important to understand where & how to execute the `IDataSeeder.SeedAsync()`?
#### On Production
The [application startup template](Startup-Templates/Application.md) comes with a *YourProjectName***.DbMigrator** project (Acme.BookStore.DbMigrator on the picture below), which is a **console application** that is responsible to **migrate** the database schema (for relational databases) and **seed** the initial data:
![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png)
This console application is properly configured for you. It even supports **multi-tenant** scenarios where each tenant has its own database (migrates & seeds all necessary databases).
It is expected to run this DbMigrator application whenever you **deploy a new version** of your solution to the server. It will migrate your **database schema** (create new tables/fields... etc.) and **seed new initial data** needed to properly run the new version of your solution. Then you can deploy/start your actual application.
Even if you are using MongoDB or another NoSQL database (that doesn't need to schema migrations), it is recommended to use the DbMigrator application to seed your data or perform your data migration.
Having such a separate console application has several advantages;
* You can **run it before** updating your application, so your application will run on the ready database.
* Your application **starts faster** compared to if it seeds the initial data itself.
* Your application can properly run on a **clustered environment** (where multiple instances of your application run concurrently). If you seed data on application startup you would have conflicts in this case.
#### On Development
We suggest the same way on development. Run the DbMigrator console application whenever you [create a database migration](https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/) (using EF Core `Add-Migration` command, for example) or change the data seed code (will be explained later).
> You can continue to use the standard `Update-Database` command for EF Core, but it will not seed if you've created a new seed data.
#### On Testing
You probably want to seed the data also for automated [testing](Testing.md), so want to use the `IDataSeeder.SeedAsync()`. In the [application startup template](Startup-Templates/Application.md), it is done in the [OnApplicationInitialization](Module-Development-Basics.md) method of the *YourProjectName*TestBaseModule class of the TestBase project.
In addition to the standard seed data (that is also used on production), you may want to seed additional data unique to the automated tests. If so, you can create a new data seed contributor in the test project to have more data to work on.

281
docs/en/Data-Transfer-Objects.md

@ -1,3 +1,280 @@
## Data Transfer Objects # Data Transfer Objects
TODO ## Introduction
**Data Transfer Objects** (DTO) are used to transfer data between the **Application Layer** and the **Presentation Layer** or other type of clients.
Typically, an [application service](Application-Services.md) is called from the presentation layer (optionally) with a **DTO** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer.
### The Need for DTOs
> **You can skip this section** if you feel that you know and confirm the benefits of using DTOs.
At first, creating a DTO class for each application service method can be seen as tedious and time-consuming work. However, they can save your application if you correctly use them. Why & how?
#### Abstraction of the Domain Layer
DTOs provide an efficient way of **abstracting domain objects** from the presentation layer. In effect, your **layers** are correctly separated. If you want to change the presentation layer completely, you can continue with the existing application and domain layers. Alternatively, you can re-write your domain layer, completely change the database schema, entities and O/RM framework, all without changing the presentation layer. This, of course, is as long as the contracts (method signatures and DTOs) of your application services remain unchanged.
#### Data Hiding
Say you have a `User` entity with the properties Id, Name, EmailAddress and Password. If a `GetAllUsers()` method of a `UserAppService` returns a `List<User>`, anyone can access the passwords of all your users, even if you do not show it on the screen. It's not just about security, it's about data hiding. Application services should return only what it needs by the presentation layer (or client). Not more, not less.
#### Serialization & Lazy Load Problems
When you return data (an object) to the presentation layer, it's most likely serialized. For example, in a REST API that returns JSON, your object will be serialized to JSON and sent to the client. Returning an Entity to the presentation layer can be problematic in that regard, especially if you are using a relational database and an ORM provider like Entity Framework Core. How?
In a real-world application, your entities may have references to each other. The `User` entity can have a reference to it's `Role`s. If you want to serialize `User`, its `Role`s are also serialized. The `Role` class may have a `List<Permission>` and the `Permission` class can has a reference to a `PermissionGroup` class and so on... Imagine all of these objects being serialized at once. You could easily and accidentally serialize your whole database! Also, if your objects have circular references, they may **not** be serialized at all.
What's the solution? Marking properties as `NonSerialized`? No, you can not know when it should be serialized and when it shouldn't be. It may be needed in one application service method, and not needed in another. Returning safe, serializable, and specially designed DTOs is a good choice in this situation.
Almost all O/RM frameworks support lazy-loading. It's a feature that loads entities from the database when they're needed. Say a `User` class has a reference to a `Role` class. When you get a `User` from the database, the `Role` property (or collection) is not filled. When you first read the `Role` property, it's loaded from the database. So, if you return such an Entity to the presentation layer, it will cause it to retrieve additional entities from the database by executing additional queries. If a serialization tool reads the entity, it reads all properties recursively and again your whole database can be retrieved (if there are relations between entities).
More problems can arise if you use Entities in the presentation layer. **It's best not to reference the domain/business layer assembly in the presentation layer.**
If you are convinced about using DTOs, we can continue to what ABP Framework provides and suggests about DTOs.
> ABP doesn't force you to use DTOs, however using DTOs is **strongly suggested as a best practice**.
## Standard Interfaces & Base Classes
A DTO is a simple class that has no dependency and you can design it in any way. However, ABP introduces some **interfaces** to determine the **conventions** for naming **standard properties** and **base classes** to **don't repeat yourself** while declaring **common properties**.
**None of them are required**, but using them **simplifies and standardizes** your application code.
### Entity Related DTOs
You typically create DTOs corresponding to your entities, which results similar classes to your entities. ABP Framework provides some base classes to simplify while creating such DTOs.
#### EntityDto
`IEntityDto<TKey>` is a simple interface that only defines an `Id` property. You can implement it or inherit from the `EntityDto<TKey>` for your DTOs that matches to an [entity](Entities.md).
**Example:**
````csharp
using System;
using Volo.Abp.Application.Dtos;
namespace AbpDemo
{
public class ProductDto : EntityDto<Guid>
{
public string Name { get; set; }
//...
}
}
````
#### Audited DTOs
If your entity inherits from audited entity classes (or implements auditing interfaces), you can use the following base classes to create your DTOs:
* `CreationAuditedEntityDto`
* `CreationAuditedEntityWithUserDto`
* `AuditedEntityDto`
* `AuditedEntityWithUserDto`
* `FullAuditedEntityDto`
* `FullAuditedEntityWithUserDto`
#### Extensible DTOs
If you want to use the [object extension system](Object-Extensions.md) for your DTOs, you can use or inherit from the following DTO classes:
* `ExtensibleObject` implements the `IHasExtraProperties` (other classes inherits this class).
* `ExtensibleEntityDto`
* `ExtensibleCreationAuditedEntityDto`
* `ExtensibleCreationAuditedEntityWithUserDto`
* `ExtensibleAuditedEntityDto`
* `ExtensibleAuditedEntityWithUserDto`
* `ExtensibleFullAuditedEntityDto`
* `ExtensibleFullAuditedEntityWithUserDto`
### List Results
It is common to return a list of DTOs to the client. `IListResult<T>` interface and `ListResultDto<T>` class is used to make it standard.
The definition of the `IListResult<T>` interface:
````csharp
public interface IListResult<T>
{
IReadOnlyList<T> Items { get; set; }
}
````
**Example: Return a list of products**
````csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace AbpDemo
{
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<ListResultDto<ProductDto>> GetListAsync()
{
//Get entities from the repository
List<Product> products = await _productRepository.GetListAsync();
//Map entities to DTOs
List<ProductDto> productDtos =
ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
//Return the result
return new ListResultDto<ProductDto>(productDtos);
}
}
}
````
You could simply return the `productDtos` object (and change the method return type) and it has nothing wrong. Returning a `ListResultDto` makes your `List<ProductDto>` wrapped into another object as an `Items` property. This has one advantage: You can later add more properties to your return value without breaking your remote clients (when they get the value as a JSON result). So, it is especially suggested when you are developing reusable application modules.
### Paged & Sorted List Results
It is more common to request a paged list from server and return a paged list to the client. ABP defines a few interface and classes to standardize it:
#### Input (Request) Types
The following interfaces and classes is to standardize the input sent by the clients.
* `ILimitedResultRequest`: Defines a `MaxResultCount` (`int`) property to request a limited result from the server.
* `IPagedResultRequest`: Inherits from the `ILimitedResultRequest` (so it inherently has the `MaxResultCount` property) and defines a `SkipCount` (`int`) to declare the skip count while requesting a paged result from the server.
* `ISortedResultRequest`: Defines a `Sorting` (`string`) property to request a sorted result from the server. Sorting value can be "*Name*", "*Name DESC*", "*Name ASC, Age DESC*"... etc.
* `IPagedAndSortedResultRequest` inherits from both of the `IPagedResultRequest` and `ISortedResultRequest`, so has `MaxResultCount`, `SkipCount` and `Sorting` properties.
Instead of implementing the interfaces manually, it is suggested to inherit one of the following base DTO classes:
* `LimitedResultRequestDto` implements `ILimitedResultRequest`.
* `PagedResultRequestDto` implements `IPagedResultRequest` (and inherits from the `LimitedResultRequestDto`).
* `PagedAndSortedResultRequestDto` implements `IPagedAndSortedResultRequest` (and inherit from the `PagedResultRequestDto`).
##### Max Result Count
`LimitedResultRequestDto` (and inherently the others) limits and validates the `MaxResultCount` by the following rules;
* If the client doesn't set `MaxResultCount`, it is assumed as **10** (the default page size). This value can be changed by setting the `LimitedResultRequestDto.DefaultMaxResultCount` static property.
* If the client sends `MaxResultCount` greater than **1,000**, it produces a **validation error**. It is important to protect the server from abuse of the service. If you want, you can change this value by setting the `LimitedResultRequestDto.MaxMaxResultCount` static property.
Static properties suggested to be set on application startup since they are static (global).
#### Output (Response) Types
The following interfaces and classes is to standardize the output sent to the clients.
* `IHasTotalCount` defines a `TotalCount` (`long`) property to return the total count of the records in case of paging.
* `IPagedResult<T>` inherits from the `IListResult<T>` and `IHasTotalCount`, so it has the `Items` and `TotalCount` properties.
Instead of implementing the interfaces manually, it is suggested to inherit one of the following base DTO classes:
* `PagedResultDto<T>` inherits from the `ListResultDto<T>` and also implements the `IPagedResult<T>`.
**Example: Request a paged & sorted result from server and return a paged list**
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace AbpDemo
{
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<PagedResultDto<ProductDto>> GetListAsync(
PagedAndSortedResultRequestDto input)
{
//Create the query
var query = _productRepository
.OrderBy(input.Sorting)
.Skip(input.SkipCount)
.Take(input.MaxResultCount);
//Get total count from the repository
var totalCount = await query.CountAsync();
//Get entities from the repository
List<Product> products = await query.ToListAsync();
//Map entities to DTOs
List<ProductDto> productDtos =
ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
//Return the result
return new PagedResultDto<ProductDto>(totalCount, productDtos);
}
}
}
````
ABP Framework also defines a `PageBy` extension method (that is compatible with the `IPagedResultRequest`) that can be used instead of `Skip` + `Take` calls:
````csharp
var query = _productRepository
.OrderBy(input.Sorting)
.PageBy(input);
````
> Notice that we added `Volo.Abp.EntityFrameworkCore` package to the project to be able to use the `ToListAsync` and `CountAsync` methods since they are not included in the standard LINQ, but defined by the Entity Framework Core.
See also the [repository documentation](Repositories.md) to if you haven't understood the example code.
## Related Topics
### Validation
Inputs of [application service](Application-Services.md) methods, controller actions, page model inputs... are automatically validated. You can use the standard data annotation attributes or a custom validation method to perform the validation.
See the [validation document](Validation.md) for more.
### Object to Object Mapping
When you create a DTO that is related to an entity, you generally need to map these objects. ABP provides an object to object mapping system to simplify the mapping process. See the following documents:
* [Object to Object Mapping document](Object-To-Object-Mapping.md) covers all the features.
* [Application Services document](Application-Services.md) provides a full example.
## Best Practices
You are free to design your DTO classes. However, there are some best practices & suggestions that you may want to follow.
### Common Principles
* DTOs should be **well serializable** since they are generally serialized and deserialized (to JSON or other format). It is suggested to have an empty (parameterless) public constructor if you have another constructor with parameter(s).
* DTOs **should not contain any business logic**, except some formal [validation](Validation.md) code.
* Do not inherit DTOs from entities and **do not reference to entities**. The [application startup template](Startup-Templates/Application.md) already prevents it by separating the projects.
* If you use an auto [object to object mapping](Object-To-Object-Mapping.md) library, like AutoMapper, enable the **mapping configuration validation** to prevent potential bugs.
### Input DTO Principles
* Define only the **properties needed** for the use case. Do not include properties not used for the use case, which confuses developers if you do so.
* **Don't reuse** input DTOs among different application service methods. Because, different use cases will need to and use different properties of the DTO which results some properties are not used in some cases and that makes harder to understand and use the services and causes potential bugs in the future.
### Output DTO Principles
* You can **reuse output DTOs** if you **fill all the properties** on all the cases.

4
docs/en/Domain-Driven-Design.md

@ -2,7 +2,7 @@
## What is DDD? ## What is DDD?
ABP framework provides an **infrastructure** to make **DDD** based development easier to implement. DDD is [defined in the Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design) as below: ABP framework provides an **infrastructure** to make **Domain Driven Design** based development easier to implement. DDD is [defined in the Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design) as below:
> **Domain-driven design** (**DDD**) is an approach to software development for complex needs by connecting the implementation to an evolving model. The premise of domain-driven design is the following: > **Domain-driven design** (**DDD**) is an approach to software development for complex needs by connecting the implementation to an evolving model. The premise of domain-driven design is the following:
> >
@ -16,7 +16,7 @@ ABP follows DDD principles and patterns to achieve a layered application model w
- **Presentation Layer**: Provides an interface to the user. Uses the *Application Layer* to achieve user interactions. - **Presentation Layer**: Provides an interface to the user. Uses the *Application Layer* to achieve user interactions.
- **Application Layer**: Mediates between the Presentation and Domain Layers. Orchestrates business objects to perform specific application tasks. Implements use cases as the application logic. - **Application Layer**: Mediates between the Presentation and Domain Layers. Orchestrates business objects to perform specific application tasks. Implements use cases as the application logic.
- **Domain Layer**: Includes business objects and their business rules. This is the heart of the application. - **Domain Layer**: Includes business objects and the core (domain) business rules. This is the heart of the application.
- **Infrastructure Layer**: Provides generic technical capabilities that support higher layers mostly using 3rd-party libraries. - **Infrastructure Layer**: Provides generic technical capabilities that support higher layers mostly using 3rd-party libraries.
## Contents ## Contents

8
docs/en/Entities.md

@ -26,9 +26,9 @@ public class Book : Entity<Guid>
If your entity's Id type is `Guid`, there are some good practices to implement: If your entity's Id type is `Guid`, there are some good practices to implement:
* Create a constructor that gets the Id as a parameter and passes to the base class. * Create a constructor that gets the Id as a parameter and passes to the base class.
* If you don't set a GUID Id, ABP Framework sets it on save, but it is good to have a valid Id on the entity even before saving it to the database. * If you don't set a GUID Id, **ABP Framework sets it on save**, but it is good to have a valid Id on the entity even before saving it to the database.
* If you create an entity with a constructor that takes parameters, also create a `protected` empty constructor. This is used while your database provider reads your entity from the database (on deserialization). * If you create an entity with a constructor that takes parameters, also create a `private` or `protected` empty constructor. This is used while your database provider reads your entity from the database (on deserialization).
* Don't use the `Guid.NewGuid()` to set the Id! Use [the `IGuidGenerator` service](Guid-Generation.md) while passing the Id from the code that creates the entity. `IGuidGenerator` optimized to generate sequential GUIDs, which is critical for clustered indexes in the relational databases. * Don't use the `Guid.NewGuid()` to set the Id! **Use [the `IGuidGenerator` service](Guid-Generation.md)** while passing the Id from the code that creates the entity. `IGuidGenerator` optimized to generate sequential GUIDs, which is critical for clustered indexes in the relational databases.
An example entity: An example entity:
@ -382,7 +382,7 @@ The way to store this dictionary in the database depends on the database provide
Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way. Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way.
You normally **don't need** to this system for your own entities, because it has the following drawbacks: You typically **don't need** to use this system for your own entities, because it has the following drawbacks:
* It is **not fully type safe** since it works with strings as property names. * It is **not fully type safe** since it works with strings as property names.
* It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects. * It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects.

17
docs/en/Entity-Framework-Core-MySQL.md

@ -25,23 +25,6 @@ MySQL connection strings are different than SQL Server connection strings. So, c
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure.
## Change the Migrations DbContext
MySQL DBMS has some slight differences than the SQL Server. Some module database mapping configuration (especially the field lengths) causes problems with MySQL. For example, some of the the [IdentityServer module](Modules/IdentityServer.md) tables has such problems and it provides an option to configure the fields based on your DBMS.
The startup template contains a *YourProjectName*MigrationsDbContext which is responsible to maintain and migrate the database schema. This DbContext basically calls extension methods of the depended modules to configure their database tables.
Open the *YourProjectName*MigrationsDbContext and change the `builder.ConfigureIdentityServer();` line as shown below:
````csharp
builder.ConfigureIdentityServer(options =>
{
options.DatabaseProvider = EfCoreDatabaseProvider.MySql;
});
````
Then `ConfigureIdentityServer()` method will set the field lengths to not exceed the MySQL limits. Refer to related module documentation if you have any problem while creating or executing the database migrations.
## Re-Generate the Migrations ## Re-Generate the Migrations
The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails.

62
docs/en/Entity-Framework-Core-Oracle.md

@ -0,0 +1,62 @@
# Switch to EF Core Oracle Provider
This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured.
> This document uses a paid library of [Devart](https://www.devart.com/dotconnect/oracle/) company, because it is the only library for Oracle that supports EF Core 3.x.
## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package
`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) package.
## Replace the Module Dependency
Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreOracleDevartModule)` (also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`).
## UseOracle()
Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files:
* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project.
* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project.
In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block
```csharp
var builder = new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>()
.UseSqlServer(configuration.GetConnectionString("Default"));
```
with this one
```csharp
var builder = (DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>)
new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>().UseOracle
(
configuration.GetConnectionString("Default")
);
```
> Depending on your solution structure, you may find more code files need to be changed.
## Change the Connection Strings
Oracle connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/oracle/ ) for details of Oracle connection string options.
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure.
## Re-Generate the Migrations
The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default.
EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations.
* Delete the `Migrations` folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution.
* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console).
This will scaffold a new migration for Oracle.
Run the `.DbMigrator` project to create the database, apply the changes and seed the initial data.
## Run the Application
It is ready. Just run the application and enjoy coding.

1
docs/en/Entity-Framework-Core-Other-DBMS.md

@ -6,6 +6,7 @@ ABP framework provides **integration packages** for some common DBMSs to make th
* [MySQL](Entity-Framework-Core-MySQL.md) * [MySQL](Entity-Framework-Core-MySQL.md)
* [PostgreSQL](Entity-Framework-Core-PostgreSQL.md) * [PostgreSQL](Entity-Framework-Core-PostgreSQL.md)
* [Oracle](Entity-Framework-Core-Oracle.md)
* [SQLite](Entity-Framework-Core-SQLite.md) * [SQLite](Entity-Framework-Core-SQLite.md)
However, you can configure your DBMS provider **without** these integration packages. While using the integration package is always recommended (it also makes standard for the depended version across different modules), you can do it manually if there is no integration package for your DBMS provider. However, you can configure your DBMS provider **without** these integration packages. While using the integration package is always recommended (it also makes standard for the depended version across different modules), you can do it manually if there is no integration package for your DBMS provider.

112
docs/en/Guid-Generation.md

@ -1,3 +1,111 @@
## Guid Generation # GUID Generation
TODO GUID is a common **primary key type** that is used in database management systems. ABP Framework prefers GUID as the primary for pre-built [application modules](Modules/Index.md). Also, `ICurrentUser.Id` property ([see](CurrentUser.md)) is type of GUID, that means the ABP Framework assumes that the User Id is always GUID.
## Why Prefer GUID?
GUID has advantages and disadvantages. You can find many articles on the web related to this topic, so we will not discuss all again, but will list the most fundamental advantages:
* It is **usable** in all database providers.
* It allows to **determine the primary key** on the client side, without needing a **database round trip** to generate the Id value. This can be more performant while inserting new records to the database and allows us to know the PK before interacting to the database.
* GUIDs are **naturally unique** which has some advantages in the following situations if;
* You need to integrate to **external** systems.
* You need to **split or merge** different tables.
* You are creating **distributed systems**.
* GUIDs are impossible to guess, so they can be **more secure** compared to auto-increment Id values in some cases.
While there are some disadvantages (just search it on the web), we found these advantages much more important while designing the ABP Framework.
## IGuidGenerator
The most important problem with GUID is that it is **not sequential by default**. When you use the GUID as the primary key and set it as the **clustered index** (which is default) for your table, it brings a significant **performance problem on insert** (because inserting new record may need to re-order the existing records).
So, **never use `Guid.NewGuid()` to create Ids** for your entities!
One good solution to this problem is to generate **sequential GUIDs**, which is provided by the ABP Framework out of the box. `IGuidGenerator` service creates sequential GUIDs (implemented by the `SequentialGuidGenerator` by default). Use `IGuidGenerator.Create()` when you need to manually set Id of an [entity](Entities.md).
**Example: An entity with GUID primary key and creating the entity**
Assume that you've a `Product` [entity](Entities.md) that has a `Guid` key:
````csharp
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
private Product() { /* This constructor is used by the ORM/database provider */ }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
}
}
````
And you want to create a new product:
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace AbpDemo
{
public class MyProductService : ITransientDependency
{
private readonly IRepository<Product, Guid> _productRepository;
private readonly IGuidGenerator _guidGenerator;
public MyProductService(
IRepository<Product, Guid> productRepository,
IGuidGenerator guidGenerator)
{
_productRepository = productRepository;
_guidGenerator = guidGenerator;
}
public async Task CreateAsync(string productName)
{
var product = new Product(_guidGenerator.Create(), productName);
await _productRepository.InsertAsync(product);
}
}
}
````
This service injects the `IGuidGenerator` in the constructor. If your class is an [application service](Application-Services.md) or deriving from one of the other base classes, you can directly use the `GuidGenerator` base property which is a pre-injected `IGuidGenerator` instance.
## Options
### AbpSequentialGuidGeneratorOptions
`AbpSequentialGuidGeneratorOptions` is the [option class](Options.md) that is used to configure the sequential GUID generation. It has a single property:
* `DefaultSequentialGuidType` (`enum` of type `SequentialGuidType`): The strategy used while generating GUID values.
Database providers behaves differently while processing GUIDs, so you should set it based on your database provider. `SequentialGuidType` has the following `enum` members:
* `SequentialAtEnd` (**default**) works well with the [SQL Server](Entity-Framework-Core.md).
* `SequentialAsString` is used by [MySQL](Entity-Framework-Core-MySQL.md) and [PostgreSQL](Entity-Framework-Core-PostgreSQL.md).
* `SequentialAsBinary` is used by [Oracle](Entity-Framework-Core-Oracle.md).
Configure this option in the `ConfigureServices` method of your [module](Module-Development-Basics.md), as shown below:
````csharp
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
});
````
> EF Core [integration packages](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS) sets this option to a proper value for the related DBMS. So, most of the times, you don't need to set this option if you are using these integration packages.

5
docs/en/How-To/Customize-Login-Page-MVC.md

@ -26,7 +26,7 @@ Then you can override any method you need and add new methods and properties nee
## Overriding the Login Page UI ## Overriding the Login Page UI
Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module thanks to the [Virtual File System](../Virtual-File-System.md). Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module.
A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below: A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below:
@ -35,7 +35,6 @@ A good way to customize a page is to copy its source code. [Click here](https://
@using Volo.Abp.Account.Settings @using Volo.Abp.Account.Settings
@using Volo.Abp.Settings @using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel @model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject Volo.Abp.Settings.ISettingProvider SettingProvider @inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin) @if (Model.EnableLocalLogin)
{ {
@ -110,4 +109,4 @@ You can find the source code of the completed example [here](https://github.com/
## See Also ## See Also
* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md). * [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md).

4
docs/en/Index.md

@ -8,8 +8,8 @@ Explore the left navigation menu to deep dive in the documentation.
Easiest way to start a new project with ABP is to use the startup templates: Easiest way to start a new project with ABP is to use the startup templates:
* [ASP.NET Core MVC (Razor Pages) UI Startup Template](Getting-Started?UI=MVC&DB=EF&Tiered=No) * [ASP.NET Core MVC (Razor Pages) UI Startup Template](Getting-Started.md?UI=MVC&DB=EF&Tiered=No)
* [Angular UI Startup Template](Getting-Started?UI=NG&DB=EF&Tiered=No) * [Angular UI Startup Template](Getting-Started.md?UI=NG&DB=EF&Tiered=No)
If you want to start from scratch (with an empty project) then manually install the ABP Framework and use the following tutorials: If you want to start from scratch (with an empty project) then manually install the ABP Framework and use the following tutorials:

3
docs/en/Json.md

@ -0,0 +1,3 @@
# JSON
TODO

29
docs/en/Modules/Docs.md

@ -58,14 +58,18 @@ Now an empty ABP project has been created! You can now run your project and see
To login your website enter `admin` as the username and `1q2w3E*` as the password. To login your website enter `admin` as the username and `1q2w3E*` as the password.
### 2- Referencing Docs Module Packages ### 3- Installation Module
Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project. Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project.
#### 3.1- Use ABP CLI
It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
`abp add-module Volo.Docs` `abp add-module Volo.Docs`
#### 3.2- Manually install
Or you can also manually install nuget package to each project: Or you can also manually install nuget package to each project:
* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project. * Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project.
@ -84,7 +88,7 @@ Or you can also manually install nuget package to each project:
`Install-Package Volo.Docs.Web` `Install-Package Volo.Docs.Web`
### 3- Adding Module Dependencies ##### 3.2.1- Adding Module Dependencies
An ABP module must declare `[DependsOn]` attribute if it has a dependency upon another module. Each module has to be added in`[DependsOn]` attribute to the relevant project. An ABP module must declare `[DependsOn]` attribute if it has a dependency upon another module. Each module has to be added in`[DependsOn]` attribute to the relevant project.
@ -165,6 +169,27 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
} }
``` ```
##### 3.2.2- Adding NPM Package
Open `package.json` and add `@abp/docs": "^2.9.0` as shown below:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0",
"@abp/docs": "^2.9.0"
}
}
```
Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command:
1. `yarn`
2. `gulp`
### 4- Database Integration ### 4- Database Integration
#### 4.1- Entity Framework Integration #### 4.1- Entity Framework Integration

1
docs/en/Modules/Index.md

@ -23,6 +23,7 @@ There are some **free and open source** application modules developed and mainta
* **[Setting Management](Setting-Management.md)**: Used to persist and manage the [settings](../Settings.md). * **[Setting Management](Setting-Management.md)**: Used to persist and manage the [settings](../Settings.md).
* **Tenant Management**: Manages tenants for a [multi-tenant](../Multi-Tenancy.md) application. * **Tenant Management**: Manages tenants for a [multi-tenant](../Multi-Tenancy.md) application.
* **Users**: Abstract users, so other modules can depend on this module instead of the Identity module. * **Users**: Abstract users, so other modules can depend on this module instead of the Identity module.
* [**Virtual File Explorer**](Virtual-File-Explorer.md): Provided a simple UI to view files in [virtual file system](../Virtual-File-System.md).
See [the GitHub repository](https://github.com/abpframework/abp/tree/master/modules) for source code of all modules. See [the GitHub repository](https://github.com/abpframework/abp/tree/master/modules) for source code of all modules.

82
docs/en/Modules/Virtual-File-Explorer.md

@ -0,0 +1,82 @@
# Virtual File Explorer Module
## What is Virtual File Explorer Module?
Virtual File Explorer Module provided a simple UI to view all files in [virtual file system](../Virtual-File-System.md).
> Virtual File Explorer Module is not installed for [the startup templates](../Startup-Templates/Index.md). So, you need to manually add this module to your application.
### Installation
#### 1- Use ABP CLI
It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
`abp add-module Volo.VirtualFileExplorer`
#### 2- Manually install
Or you can also manually install nuget package to `Acme.MyProject.Web` project:
* Install [Volo.Abp.VirtualFileExplorer.Web](https://www.nuget.org/packages/Volo.Abp.VirtualFileExplorer.Web/) nuget package to `Acme.MyProject.Web` project.
`Install-Package Volo.Abp.VirtualFileExplorer.Web`
##### 2.1- Adding Module Dependencies
* Open `MyProjectWebModule.cs`and add `typeof(AbpVirtualFileExplorerWebModule)` as shown below;
```csharp
[DependsOn(
typeof(AbpVirtualFileExplorerWebModule),
typeof(MyProjectApplicationModule),
typeof(MyProjectEntityFrameworkCoreModule),
typeof(AbpAutofacModule),
typeof(AbpIdentityWebModule),
typeof(AbpAccountWebModule),
typeof(AbpAspNetCoreMvcUiBasicThemeModule)
)]
public class MyProjectWebModule : AbpModule
{
//...
}
```
##### 2.2- Adding NPM Package
* Open `package.json` and add `@abp/virtual-file-explorer": "^2.9.0` as shown below:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0",
"@abp/virtual-file-explorer": "^2.9.0"
}
}
```
Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command:
1. `yarn`
2. `gulp`
That's all,Now run the application and Navigate to `/VirtualFileExplorer`. You will see virtual file explorer page:
![Virtual-File-Explorer](../images/virtual-file-explorer.png)
### Options
You can disabled virtual file explorer module via `AbpVirtualFileExplorerOptions` options:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpVirtualFileExplorerOptions>(options =>
{
options.IsEnabled = false;
});
}
```

12
docs/en/Multi-Tenancy.md

@ -303,10 +303,10 @@ TODO:...
Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box: Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box:
* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**. * **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**.
* **QueryStringTenantResolver**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default. * **QueryStringTenantResolveContributor**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default.
* **RouteTenantResolver**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route. * **RouteTenantResolveContributor**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route.
* **HeaderTenantResolver**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default. * **HeaderTenantResolveContributor**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default.
* **CookieTenantResolver**: Tries to find current tenant id from cookie values. Cookie name is "__tenant" by default. * **CookieTenantResolveContributor**: Tries to find current tenant id from cookie values. Cookie name is "__tenant" by default.
> If you use nginx as a reverse proxy server, please note that if `TenantKey` contains an underscore or other special characters, there may be a problem, please refer to: > If you use nginx as a reverse proxy server, please note that if `TenantKey` contains an underscore or other special characters, there may be a problem, please refer to:
http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
@ -346,7 +346,7 @@ namespace MyCompany.MyProject
//Subdomain format: {0}.mydomain.com //Subdomain format: {0}.mydomain.com
//Adding as the second highest priority resolver after 'CurrentUserTenantResolveContributor' to //Adding as the second highest priority resolver after 'CurrentUserTenantResolveContributor' to
//ensure the user cannot impersonate a different tenant. //ensure the user cannot impersonate a different tenant.
options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com")); options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));
}); });
//... //...
@ -357,7 +357,7 @@ namespace MyCompany.MyProject
{0} is the the placeholder to determine current tenant's unique name. {0} is the the placeholder to determine current tenant's unique name.
Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut: Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));`` you can use this shortcut:
````C# ````C#
options.AddDomainTenantResolver("{0}.mydomain.com"); options.AddDomainTenantResolver("{0}.mydomain.com");

4
docs/en/Object-Extensions.md

@ -2,7 +2,7 @@
ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module. ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module.
> Object extension system is not normally not needed for your own objects since you can easily add regular properties to your own classes. > Object extension system normally is not needed for your own objects since you can easily add regular properties to your own classes.
## IHasExtraProperties Interface ## IHasExtraProperties Interface
@ -409,4 +409,4 @@ ObjectExtensionManager.Instance
); );
```` ````
See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more. See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more.

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

@ -162,6 +162,61 @@ public class MyProfile : Profile
It is suggested to use the `MapExtraProperties()` method if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](Object-Extensions.md) for more. It is suggested to use the `MapExtraProperties()` method if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](Object-Extensions.md) for more.
### Other Useful Extension Methods
There are some more extension methods those can simplify your mapping code.
#### Ignoring Audit Properties
It is common to ignore audit properties when you map an object to another.
Assume that you need to map a `ProductDto` ([DTO](Data-Transfer-Objects.md)) to a `Product` [entity](Entities.md) and the entity is inheriting from the `AuditedEntity` class (which provides properties like `CreationTime`, `CreatorId`, `IHasModificationTime`... etc).
You probably want to ignore these base properties while mapping from the DTO. You can use `IgnoreAuditedObjectProperties()` method to ignore all audit properties (instead of manually ignoring them one by one):
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<ProductDto, Product>()
.IgnoreAuditedObjectProperties();
}
}
````
There are more extension methods like `IgnoreFullAuditedObjectProperties()` and `IgnoreCreationAuditedObjectProperties()` those can be used based on your entity type.
> See the "*Base Classes & Interfaces for Audit Properties*" section in the [entities document](Entities.md) to know more about auditing properties.
#### Ignoring Other Properties
In AutoMapper, you typically write such a mapping code to ignore a property:
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<SimpleClass1, SimpleClass2>()
.ForMember(x => x.CreationTime, map => map.Ignore());
}
}
````
We found it unnecessarily long and created the `Ignore()` extension method:
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<SimpleClass1, SimpleClass2>()
.Ignore(x => x.CreationTime);
}
}
````
## Advanced Topics ## Advanced Topics
### IObjectMapper<TContext> Interface ### IObjectMapper<TContext> Interface

23
docs/en/Road-Map.md

@ -4,8 +4,27 @@ You can always check the milestone planning and the prioritized backlog issues o
While we will **continue to add other exciting features**, we will work on the following major items in the **middle term**: While we will **continue to add other exciting features**, we will work on the following major items in the **middle term**:
* **gRPC integration** and implementation for all the pre-built modules.
* **Blazor UI** for the framework and all the pre-built modules. * **Blazor UI** for the framework and all the pre-built modules.
* **.NET 5.0**! As Microsoft has announced that the .NET 5.0 will be released in November 2020, we will prepare for this change before and move to the .NET 5.0 just after Microsoft releases it. We hope a smooth transition. * **.NET 5.0**! As Microsoft has announced that the .NET 5.0 will be released in November 2020, we will prepare for this change before and move to the .NET 5.0 just after Microsoft releases it. We hope a smooth transition.
Please create an issue on [the GitHub repository](https://github.com/abpframework/abp) for your feature requests, but first search in in the existing issues. Beside this middle term goals, there are many features in the [backlog](https://github.com/abpframework/abp/milestone/2). Here, a list of some major items in the backlog;
* [#4098](https://github.com/abpframework/abp/issues/4098) / Blob Storing Azure provider.
* [#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
* [#1754](https://github.com/abpframework/abp/issues/1754) / Multi-lingual entities
* [#347](https://github.com/abpframework/abp/issues/347) / Support MongoDB ACID transactions
* [#633](https://github.com/abpframework/abp/issues/633) / Realtime notification system
* [#57](https://github.com/abpframework/abp/issues/57) / Built-in CQRS infrastructure
* [#4222](https://github.com/abpframework/abp/issues/4222) / Kafka integration for the Distributed Event Bus
* [#336](https://github.com/abpframework/abp/issues/336) / Health Check abstraction
* [#2532](https://github.com/abpframework/abp/issues/2532), [#2564](https://github.com/abpframework/abp/issues/2465) / CosmosDB integration with EF Core and MongoDB API
* [#1168](https://github.com/abpframework/abp/issues/1168) / Official Vue UI startup template
* [#1638](https://github.com/abpframework/abp/issues/1638) Official React startup template
* [#4223](https://github.com/abpframework/abp/issues/4223) / WebHook system
* [#162](https://github.com/abpframework/abp/issues/162) / Azure ElasticDB Integration for multitenancy
* [#2296](https://github.com/abpframework/abp/issues/2296) / Feature toggling infrastructure
The backlog items are subject to change. We are adding new items and changing priorities based on the community feedbacks and goals of the project.
Vote for your favorite feature on the related GitHub issues (and write your thoughts). You can create an issue on [the GitHub repository](https://github.com/abpframework/abp) for your feature requests, but first search in in the existing issues.

21
docs/en/Startup-Templates/Console.md

@ -0,0 +1,21 @@
# Console Application Startup Template
This template is used to create a minimalist console application project.
## How to Start With?
First, install the [ABP CLI](../CLI.md) if you haven't installed before:
````bash
dotnet tool install -g Volo.Abp.Cli
````
Then use the `abp new` command in an empty folder to create a new solution:
````bash
abp new Acme.MyConsoleApp -t console
````
`Acme.MyConsoleApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming.
###

4
docs/en/Startup-Templates/Index.md

@ -4,6 +4,4 @@ While you can start with an empty project and add needed packages manually, star
* [**app**](Application.md): Application template. * [**app**](Application.md): Application template.
* [**module**](Module.md): Module/service template. * [**module**](Module.md): Module/service template.
* [**console**](Console.md): Console template.

113
docs/en/Timing.md

@ -0,0 +1,113 @@
# Timing
Working with times & [time zones](https://en.wikipedia.org/wiki/Time_zone) is always tricky, especially if you need to build a **global system** that is used by users in **different time zones**.
ABP provides a basic infrastructure to make it easy and handle automatically wherever possible. This document covers the ABP Framework services and systems related to time and time zones.
> If you are creating a local application that runs in a single time zone region, you may not need all these systems. But even in this case, it is suggested to use the `IClock` service introduced in this document.
## IClock
`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. The things especially gets complicated when you save this value to a database and read later, or send it to a client in a **different time zone**.
One solution to this problem is always use `DateTime.UtcNow` and assume all `DateTime` objects as UTC time. In this was, you can convert it to the time zone of the target client when needed.
`IClock` provides an abstraction while getting the current time, so you can control the kind of the date time (UTC or local) in a single point in your application.
**Example: Getting the current time**
````csharp
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IClock _clock;
public MyService(IClock clock)
{
_clock = clock;
}
public void Foo()
{
//Get the current time!
var now = _clock.Now;
}
}
}
````
* Inject the `IClock` service when you need to get the current time. Common base classes (like ApplicationService) already injects it and provides as a base property - so, you can directly use as `Clock`.
* Use the `Now` property to get the current time.
> Most of the times, `IClock` is the only service you need to know and use in your application.
### Clock Options
`AbpClockOptions` is the [options](Options.md) class that used to set the clock kind.
**Example: Use UTC Clock**
````csharp
Configure<AbpClockOptions>(options =>
{
options.Kind = DateTimeKind.Utc;
});
````
Write this inside the `ConfigureServices` method of your [module](Module-Development-Basics.md).
> Default `Kind` is `Unspecified`, that actually make the Clock as it doesn't exists at all. Either make it `Utc` or `Local` if you want to get benefit of the Clock system.
### DateTime Normalization
Other important function of the `IClock` is to normalize `DateTime` objects.
**Example usage:**
````csharp
DateTime dateTime = ...; //Get from somewhere
var normalizedDateTime = Clock.Normalize(dateTime)
````
`Normalize` method works as described below:
* Converts the given `DateTime` to the UTC (by using the `DateTime.ToUniversalTime()` method) if current Clock is UTC and given `DateTime` is local.
* Converts the given `DateTime` to the local (by using the `DateTime.ToLocalTime()` method) if current Clock is local and given `DateTime` is UTC.
* Sets `Kind` of the given `DateTime` (using the `DateTime.SpecifyKind(...)` method) to the `Kind` of the current Clock if given `DateTime`'s `Kind` is `Unspecified`.
`Normalize` method is used by the ABP Framework when the it gets a `DateTime` that is not created by `IClock.Now` and may not be compatible with the current Clock type. Examples;
* `DateTime` type binding in the ASP.NET Core MVC model binding.
* Saving data to and reading data from database via [Entity Framework Core](Entity-Framework-Core.md).
* Working with `DateTime` objects on [JSON deserialization](Json.md).
#### DisableDateTimeNormalization Attribute
`DisableDateTimeNormalization` attribute can be used to disable the normalization operation for desired classes or properties.
### Other IClock Properties
In addition to the `Now`, `IClock` service has the following properties:
* `Kind`: Returns a `DateTimeKind` for the currently used clock type (`DateTimeKind.Utc`, `DateTimeKind.Local` or `DateTimeKind.Unspecified`).
* `SupportsMultipleTimezone`: Returns `true` if currently used clock is UTC.
## Time Zones
This section covers the ABP Framework infrastructure related to managing time zones.
### TimeZone Setting
ABP Framework defines **a setting**, named `Abp.Timing.Timezone`, that can be used to set and get the time zone for a user, [tenant](Multi-Tenancy.md) or globally for the application. The default value is `UTC`.
See the [setting documentation](Settings.md) to learn more about the setting system.
### ITimezoneProvider
`ITimezoneProvider` is a service to simple convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get list of these time zones and get a `TimeZoneInfo` with a given name.
It has been implemented using the [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) library.

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

@ -14,7 +14,7 @@ else if UI == "NG"
DB="mongodb" DB="mongodb"
DB_Text="MongoDB" DB_Text="MongoDB"
UI_Text="angular" UI_Text="angular"
else else
DB ="?" DB ="?"
UI_Text="?" UI_Text="?"
end end
@ -26,7 +26,7 @@ In this tutorial series, you will build an ABP application named `Acme.BookStore
The ASP.NET Core {{UI_Value}} tutorial series consists of 3 parts: The ASP.NET Core {{UI_Value}} tutorial series consists of 3 parts:
- **Part-1: Creating the project and book list page (this tutorial)** - **Part-1: Creating the project and book list page (this tutorial)**
- [Part-2: Creating, updating and deleting books](part-2.md) - [Part-2: Creating, updating and deleting books](part-2.md)
- [Part-3: Integration tests](part-3.md) - [Part-3: Integration tests](part-3.md)
@ -106,7 +106,7 @@ This is how the layered solution structure looks like:
![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png) ![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png)
Check out the [solution structure](../startup-templates/application#solution-structure) section to understand the structure in details. Check out the [solution structure](../startup-templates/application#solution-structure) section to understand the structure in details.
### Create the book entity ### Create the book entity
@ -444,7 +444,7 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore namespace Acme.BookStore
{ {
public interface IBookAppService : public interface IBookAppService :
ICrudAppService< //Defines CRUD methods ICrudAppService< //Defines CRUD methods
BookDto, //Used to show books BookDto, //Used to show books
Guid, //Primary key of the book entity Guid, //Primary key of the book entity
@ -473,12 +473,12 @@ using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore namespace Acme.BookStore
{ {
public class BookAppService : public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto, CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>, CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService IBookAppService
{ {
public BookAppService(IRepository<Book, Guid> repository) public BookAppService(IRepository<Book, Guid> repository)
: base(repository) : base(repository)
{ {
@ -564,17 +564,15 @@ Open the `Index.cshtml` and change the whole content as shown below:
````html ````html
@page @page
@using Acme.BookStore.Web.Pages.Books @using Acme.BookStore.Web.Pages.Books
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model IndexModel @model IndexModel
<h2>Books</h2> <h2>Books</h2>
```` ````
* This code changes the default inheritance of the Razor View Page Model so it **inherits** from the `BookStorePage` class (instead of `PageModel`). The `BookStorePage` class which comes with the startup template, provides some shared properties/methods used by all pages.
* Set the `IndexModel`'s namespace to `Acme.BookStore.Pages.Books` in `Index.cshtml.cs`. * Set the `IndexModel`'s namespace to `Acme.BookStore.Pages.Books` in `Index.cshtml.cs`.
**Index.cshtml.cs:** **Index.cshtml.cs:**
@ -602,12 +600,12 @@ Open the `BookStoreMenuContributor` class in the `Menus` folder and add the foll
namespace Acme.BookStore.Web.Menus namespace Acme.BookStore.Web.Menus
{ {
public class BookStoreMenuContributor : IMenuContributor public class BookStoreMenuContributor : IMenuContributor
{ {
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{ {
//<-- added the below code //<-- added the below code
context.Menu.AddItem( context.Menu.AddItem(
new ApplicationMenuItem("BooksStore", l["Menu:BookStore"]) new ApplicationMenuItem("BooksStore", l["Menu:BookStore"], icon: "fa fa-book")
.AddItem( .AddItem(
new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books") new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books")
) )
@ -646,7 +644,16 @@ Open the `en.json` (*English translations*) file and add the below localization
"Type": "Type", "Type": "Type",
"Price": "Price", "Price": "Price",
"CreationTime": "Creation time", "CreationTime": "Creation time",
"AreYouSureToDelete": "Are you sure you want to delete this item?" "AreYouSureToDelete": "Are you sure you want to delete this item?",
"Enum:BookType:0": "Undefined",
"Enum:BookType:1": "Adventure",
"Enum:BookType:2": "Biography",
"Enum:BookType:3": "Dystopia",
"Enum:BookType:4": "Fantastic",
"Enum:BookType:5": "Horror",
"Enum:BookType:6": "Science",
"Enum:BookType:7": "ScienceFiction",
"Enum:BookType:8": "Poetry"
} }
} }
```` ````
@ -672,7 +679,6 @@ Change the `Pages/Books/Index.cshtml` as following:
````html ````html
@page @page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model Acme.BookStore.Web.Pages.Books.IndexModel @model Acme.BookStore.Web.Pages.Books.IndexModel
@section scripts @section scripts
{ {
@ -716,7 +722,11 @@ $(function () {
ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList),
columnDefs: [ columnDefs: [
{ data: "name" }, { data: "name" },
{ data: "type" }, { data: "type",
render: function(data){
return l('Enum:BookType:' + data);
}
},
{ data: "publishDate" }, { data: "publishDate" },
{ data: "price" }, { data: "price" },
{ data: "creationTime" } { data: "creationTime" }
@ -736,7 +746,7 @@ It's end of this part. The final UI of this work is shown as below:
{{end}} {{end}}
{{if UI == "NG"}} {{if UI == "NG"}}
### Angular development ### Angular development
#### Create the books page #### Create the books page

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

@ -15,7 +15,7 @@ else if UI == "NG"
DB="mongodb" DB="mongodb"
DB_Text="MongoDB" DB_Text="MongoDB"
UI_Text="angular" UI_Text="angular"
else else
DB ="?" DB ="?"
UI_Text="?" UI_Text="?"
end end
@ -86,7 +86,6 @@ Open the `CreateModal.cshtml` file and paste the code below:
````html ````html
@page @page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model Acme.BookStore.Web.Pages.Books.CreateModalModel @model Acme.BookStore.Web.Pages.Books.CreateModalModel
@{ @{
@ -203,7 +202,7 @@ namespace Acme.BookStore.Web.Pages.Books
* In the `GetAsync` method, we get `BookDto `from `BookAppService` and this is being mapped to the DTO object `CreateUpdateBookDto`. * In the `GetAsync` method, we get `BookDto `from `BookAppService` and this is being mapped to the DTO object `CreateUpdateBookDto`.
* The `OnPostAsync` uses `BookAppService.UpdateAsync()` to update the entity. * The `OnPostAsync` uses `BookAppService.UpdateAsync()` to update the entity.
#### Mapping from BookDto to CreateUpdateBookDto #### Mapping from BookDto to CreateUpdateBookDto
To be able to map the `BookDto` to `CreateUpdateBookDto`, configure a new mapping. To do this, open the `BookStoreWebAutoMapperProfile.cs` in the `Acme.BookStore.Web` project and change it as shown below: To be able to map the `BookDto` to `CreateUpdateBookDto`, configure a new mapping. To do this, open the `BookStoreWebAutoMapperProfile.cs` in the `Acme.BookStore.Web` project and change it as shown below:
@ -230,7 +229,6 @@ Replace `EditModal.cshtml` content with the following content:
````html ````html
@page @page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Acme.BookStore.Web.Pages.Books @using Acme.BookStore.Web.Pages.Books
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel @model EditModalModel
@ -256,7 +254,7 @@ This page is very similar to the `CreateModal.cshtml`, except:
#### Add "Actions" dropdown to the table #### Add "Actions" dropdown to the table
We will add a dropdown button to the table named *Actions*. We will add a dropdown button to the table named *Actions*.
Open the `Pages/Books/Index.cshtml` page and change the `<abp-table>` section as shown below: Open the `Pages/Books/Index.cshtml` page and change the `<abp-table>` section as shown below:
@ -525,7 +523,7 @@ export class BookState {
* We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book. * We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book.
When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`. When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`.
#### Add a modal to BookListComponent #### Add a modal to BookListComponent
@ -601,7 +599,7 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
</abp-modal> </abp-modal>
``` ```
* We added the `abp-modal` which renders a modal to allow user to create a new book. * We added the `abp-modal` which renders a modal to allow user to create a new book.
* `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits. * `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
* We added `New book` button to the `AbpContentToolbar`. * We added `New book` button to the `AbpContentToolbar`.
@ -864,7 +862,7 @@ export class BookListComponent implements OnInit {
} }
``` ```
* We imported ` NgbDateNativeAdapter, NgbDateAdapter` * We imported ` NgbDateNativeAdapter, NgbDateAdapter`
* We added a new provider `NgbDateAdapter` that converts Datepicker value to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details. * We added a new provider `NgbDateAdapter` that converts Datepicker value to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.
@ -971,7 +969,7 @@ Open `book-list.component.html` in `app\book\book-list` folder and add the follo
<button type="button" class="btn btn-secondary" #abpClose> <button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpAccount::Close' | abpLocalization }}}%} {%{{{ 'AbpAccount::Close' | abpLocalization }}}%}
</button> </button>
<!--added save button--> <!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid"> <button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i> <i class="fa fa-check mr-1"></i>
@ -986,7 +984,7 @@ Find the `<form [formGroup]="form">` tag and replace below content:
<form [formGroup]="form" (ngSubmit)="save()"> <!-- added the ngSubmit --> <form [formGroup]="form" (ngSubmit)="save()"> <!-- added the ngSubmit -->
``` ```
* We added the `(ngSubmit)="save()"` to `<form>` element to save a new book by pressing the enter. * We added the `(ngSubmit)="save()"` to `<form>` element to save a new book by pressing the enter.
* We added `abp-button` to the bottom area of the modal to save a new book. * We added `abp-button` to the bottom area of the modal to save a new book.
@ -1131,7 +1129,7 @@ export class BookListComponent implements OnInit {
* We imported `BookService`. * We imported `BookService`.
* We declared a variable named `selectedBook` as `BookDto`. * We declared a variable named `selectedBook` as `BookDto`.
* We injected `BookService` to the constructor. `BookService` is being used to retrieve the book data which is being edited. * We injected `BookService` to the constructor. `BookService` is being used to retrieve the book data which is being edited.
* We added `editBook` method. This method fetches the book with the given `Id` and sets it to `selectedBook` object. * We added `editBook` method. This method fetches the book with the given `Id` and sets it to `selectedBook` object.
* We replaced the `buildForm` method so that it creates the form with the `selectedBook` data. * We replaced the `buildForm` method so that it creates the form with the `selectedBook` data.
* We replaced the `createBook` method so it sets `selectedBook` to an empty object. * We replaced the `createBook` method so it sets `selectedBook` to an empty object.
* We added `selectedBook.id` to the constructor of the new `CreateUpdateBook`. * We added `selectedBook.id` to the constructor of the new `CreateUpdateBook`.
@ -1291,7 +1289,7 @@ import { ConfirmationService } from '@abp/ng.theme.shared';
//... //...
constructor( constructor(
private store: Store, private store: Store,
private fb: FormBuilder, private fb: FormBuilder,
private bookService: BookService, private bookService: BookService,
private confirmation: ConfirmationService // <== added this line ==> private confirmation: ConfirmationService // <== added this line ==>

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

@ -1,10 +1,10 @@
## Component Replacement # Component Replacement
You can replace some ABP components with your custom components. You can replace some ABP components with your custom components.
The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_. The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_.
### How to Replace a Component ## How to Replace a Component
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`. Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
@ -15,16 +15,20 @@ import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddRepl
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store import { Store } from '@ngxs/store'; // imported Store
//... //...
export class AppComponent {
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store constructor(..., private store: Store) {} // injected Store
ngOnInit() { ngOnInit() {
// added dispatch
this.store.dispatch( this.store.dispatch(
new AddReplaceableComponent({ new AddReplaceableComponent({
component: YourNewRoleComponent, component: YourNewRoleComponent,
key: eIdentityComponents.Roles, key: eIdentityComponents.Roles,
}), }),
); );
//... //...
} }
} }
@ -33,13 +37,13 @@ export class AppComponent {
![Example Usage](./images/component-replacement.gif) ![Example Usage](./images/component-replacement.gif)
### How to Replace a Layout ## How to Replace a Layout
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced with the same way. Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way.
> A layout component template should contain `<router-outlet></router-outlet>` element. > A layout component template should contain `<router-outlet></router-outlet>` element.
The below example describes how to replace the `ApplicationLayoutComponent`: The example below describes how to replace the `ApplicationLayoutComponent`:
Run the following command to generate a layout in `angular` folder: Run the following command to generate a layout in `angular` folder:
@ -55,7 +59,7 @@ Add the following code in your layout template (`my-layout.component.html`) wher
<router-outlet></router-outlet> <router-outlet></router-outlet>
``` ```
Open the `app.component.ts` and add the below content: Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js ```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
@ -63,11 +67,13 @@ import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeB
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store import { Store } from '@ngxs/store'; // imported Store
//... //...
export class AppComponent {
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store constructor(..., private store: Store) {} // injected Store
ngOnInit() { ngOnInit() {
// added below content // added dispatch
this.store.dispatch( this.store.dispatch(
new AddReplaceableComponent({ new AddReplaceableComponent({
component: MyApplicationLayoutComponent, component: MyApplicationLayoutComponent,
@ -80,6 +86,467 @@ export class AppComponent {
} }
``` ```
### Layout Components
![Layout Components](./images/layout-components.png)
#### How to Replace LogoComponent
![LogoComponent](./images/logo-component.png)
Run the following command in `angular` folder to create a new component called `LogoComponent`.
```bash
yarn ng generate component logo --inlineTemplate --inlineStyle --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Open the generated `logo.component.ts` in `src/app/logo` folder and replace its content with the following:
```js
import { Component } from '@angular/core';
@Component({
selector: 'app-logo',
template: `
<a class="navbar-brand" routerLink="/">
<!-- Change the img src -->
<img
src="https://via.placeholder.com/100x50/343a40/FF0000?text=MyLogo"
alt="logo"
width="100%"
height="auto"
/>
</a>
`,
})
export class LogoComponent {}
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: LogoComponent,
key: eThemeBasicComponents.Logo,
}),
);
}
}
```
The final UI looks like below:
![New logo](./images/replaced-logo-component.png)
#### How to Replace RoutesComponent
![RoutesComponent](./images/routes-component.png)
Run the following command in `angular` folder to create a new component called `RoutesComponent`.
```bash
yarn ng generate component routes --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following:
```js
import { ABP, ReplaceableComponents } from '@abp/ng.core';
import {
Component,
HostBinding,
Inject,
Renderer2,
TrackByFunction,
AfterViewInit,
} from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Component({
selector: 'app-routes',
templateUrl: 'routes.component.html',
})
export class RoutesComponent implements AfterViewInit {
@HostBinding('class.mx-auto')
marginAuto = true;
smallScreen = window.innerWidth < 992;
constructor(private renderer: Renderer2) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
}
```
Open the generated `routes.component.html` in `src/app/routes` folder and replace its content with the following:
```html
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" routerLink="/"
><i class="fas fa-home"></i> {%{{{ '::Menu:Home' | abpLocalization }}}%}</a
>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/my-page"><i class="fas fa-newspaper mr-1"></i>My Page</a>
</li>
<li
#navbarRootDropdown
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
(click)="
navbarRootDropdown.expand
? (navbarRootDropdown.expand = false)
: (navbarRootDropdown.expand = true)
"
>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
<i class="fas fa-wrench"></i>
{%{{{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}}%}
</a>
<div
#routeContainer
class="dropdown-menu border-0 shadow-sm"
(click)="$event.preventDefault(); $event.stopPropagation()"
[class.d-block]="smallScreen && navbarRootDropdown.expand"
>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpIdentity.Roles || AbpIdentity.Users'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-id-card-o"></i>
{%{{{ 'AbpIdentity::Menu:IdentityManagement' | abpLocalization }}}%}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Roles'">
<a class="dropdown-item" routerLink="/identity/roles">
{%{{{ 'AbpIdentity::Roles' | abpLocalization }}}%}</a
>
</div>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Users'">
<a class="dropdown-item" routerLink="/identity/users">
{%{{{ 'AbpIdentity::Users' | abpLocalization }}}%}</a
>
</div>
</div>
</div>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpTenantManagement.Tenants'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-users"></i>
{%{{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}}%}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
<a class="dropdown-item" routerLink="/tenant-management/tenants">
{%{{{ 'AbpTenantManagement::Tenants' | abpLocalization }}}%}</a
>
</div>
</div>
</div>
</div>
</li>
</ul>
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: RoutesComponent,
key: eThemeBasicComponents.Routes,
}),
);
}
}
```
The final UI looks like below:
![New routes](./images/replaced-routes-component.png)
#### How to Replace NavItemsComponent
![NavItemsComponent](./images/nav-items-component.png)
Run the following command in `angular` folder to create a new component called `NavItemsComponent`.
```bash
yarn ng generate component nav-items --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Open the generated `nav-items.component.ts` in `src/app/nav-items` folder and replace the content with the following:
```js
import {
ApplicationConfiguration,
AuthService,
ConfigState,
SessionState,
SetLanguage,
} from '@abp/ng.core';
import { Component, AfterViewInit } from '@angular/core';
import { Navigate, RouterState } from '@ngxs/router-plugin';
import { Select, Store } from '@ngxs/store';
import { Observable, fromEvent } from 'rxjs';
import { map, debounceTime } from 'rxjs/operators';
import snq from 'snq';
@Component({
selector: 'app-nav-items',
templateUrl: 'nav-items.component.html',
})
export class NavItemsComponent implements AfterViewInit {
@Select(ConfigState.getOne('currentUser'))
currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
smallScreen = window.innerWidth < 992;
get defaultLanguage$(): Observable<string> {
return this.languages$.pipe(
map(
languages =>
snq(
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
),
'',
),
);
}
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
return this.languages$.pipe(
map(
languages =>
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
[],
),
);
}
get selectedLangCulture(): string {
return this.store.selectSnapshot(SessionState.getLanguage);
}
constructor(private store: Store, private authService: AuthService) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
onChangeLang(cultureName: string) {
this.store.dispatch(new SetLanguage(cultureName));
}
logout() {
this.authService.logout().subscribe(() => {
this.store.dispatch(
new Navigate(['/'], null, {
state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
}),
);
});
}
}
```
Open the generated `nav-items.component.html` in `src/app/nav-items` folder and replace the content with the following:
```html
<ul class="navbar-nav">
<input type="search" placeholder="Search" class="bg-transparent border-0 text-white" />
<li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
<div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{%{{{ defaultLanguage$ | async }}}%}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && languageDropdown.isOpen()"
>
<a
*ngFor="let lang of dropdownLanguages$ | async"
href="javascript:void(0)"
class="dropdown-item"
(click)="onChangeLang(lang.cultureName)"
>{%{{{ lang?.displayName }}}%}</a
>
</div>
</div>
</li>
<li class="nav-item">
<ng-template #loginBtn>
<a role="button" class="nav-link" routerLink="/account/login">{%{{{
'AbpAccount::Login' | abpLocalization
}}}%}</a>
</ng-template>
<div
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
ngbDropdown
class="dropdown"
#currentUserDropdown="ngbDropdown"
display="static"
>
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{%{{{ (currentUser$ | async)?.userName }}}%}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item" routerLink="/account/manage-profile"
><i class="fa fa-cog mr-1"></i>{%{{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}}%}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
><i class="fa fa-power-off mr-1"></i>{%{{{ 'AbpUi::Logout' | abpLocalization }}}%}</a
>
</div>
</div>
</li>
</ul>
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: NavItemsComponent,
key: eThemeBasicComponents.NavItems,
}),
);
}
}
```
The final UI looks like below:
![New nav-items](./images/replaced-nav-items-component.png)
## See Also
- [How to Replace PermissionManagementComponent](./Permission-Management-Component-Replacement.md)
## What's Next? ## What's Next?
- [Custom Setting Page](./Custom-Setting-Page.md) - [Custom Setting Page](./Custom-Setting-Page.md)

62
docs/en/UI/Angular/Localization.md

@ -130,6 +130,68 @@ this.store.selectSnapshot(
Localization resources are stored in the `localization` property of `ConfigState`. Localization resources are stored in the `localization` property of `ConfigState`.
## RTL Support
As of v2.9 ABP has RTL support. If you are generating a new project with v2.9 and above, everything is set, you do not need to do any changes. If you are migrating your project from an earlier version, please follow the 2 steps below:
#### Step 1. Create Chunks for Bootstrap LTR and RTL
Find [styles configuration in angular.json](https://angular.io/guide/workspace-config#style-script-config) and make sure the chunks in your project has `bootstrap-rtl.min` and `bootstrap-ltr.min` as shown below.
```json
{
"projects": {
"MyProjectName": {
"architect": {
"build": {
"options": {
"styles": [
{
"input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css",
"inject": false,
"bundleName": "bootstrap-rtl.min"
},
{
"input": "node_modules/bootstrap/dist/css/bootstrap.min.css",
"inject": true,
"bundleName": "bootstrap-ltr.min"
},
{
"input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"inject": true,
"bundleName": "fontawesome-all.min"
},
{
"input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css",
"inject": true,
"bundleName": "fontawesome-v4-shims.min"
},
"apps/dev-app/src/styles.scss"
],
}
}
}
}
}
}
#### Step 2. Clear Lazy Loaded Fontawesome in AppComponent
If you have created and injected chunks for Fontawesome as seen above, you no longer need the lazy loading in the `AppComponent` which was implemented before v2.9. Simply remove them. The `AppComponent` in the template of the new version looks like this:
```js
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<abp-loader-bar></abp-loader-bar>
<router-outlet></router-outlet>
`,
})
export class AppComponent {}
```
## See Also ## See Also

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

@ -0,0 +1,500 @@
# How to Replace PermissionManagementComponent
![Permission management modal](./images/permission-management-modal.png)
Run the following command in `angular` folder to create a new component called `PermissionManagementComponent`.
```bash
yarn ng generate component permission-management --entryComponent --inlineStyle
# You don't need the --entryComponent option in Angular 9
```
Open the generated `permission-management.component.ts` in `src/app/permission-management` folder and replace the content with the following:
```js
import {
Component,
EventEmitter,
Input,
Output,
Renderer2,
TrackByFunction,
Inject,
Optional,
} from '@angular/core';
import { ReplaceableComponents } from '@abp/ng.core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, map, pluck, take, tap } from 'rxjs/operators';
import {
GetPermissions,
UpdatePermissions,
PermissionManagement,
PermissionManagementState,
} from '@abp/ng.permission-management';
type PermissionWithMargin = PermissionManagement.Permission & {
margin: number;
};
@Component({
selector: 'app-permission-management',
templateUrl: './permission-management.component.html',
styles: [
`
.overflow-scroll {
max-height: 70vh;
overflow-y: scroll;
}
`,
],
})
export class PermissionManagementComponent
implements
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs {
protected _providerName: string;
@Input()
get providerName(): string {
if (this.replaceableData) return this.replaceableData.inputs.providerName;
return this._providerName;
}
set providerName(value: string) {
this._providerName = value;
}
protected _providerKey: string;
@Input()
get providerKey(): string {
if (this.replaceableData) return this.replaceableData.inputs.providerKey;
return this._providerKey;
}
set providerKey(value: string) {
this._providerKey = value;
}
protected _hideBadges = false;
@Input()
get hideBadges(): boolean {
if (this.replaceableData) return this.replaceableData.inputs.hideBadges;
return this._hideBadges;
}
set hideBadges(value: boolean) {
this._hideBadges = value;
}
protected _visible = false;
@Input()
get visible(): boolean {
return this._visible;
}
set visible(value: boolean) {
if (value === this._visible) return;
if (value) {
this.openModal().subscribe(() => {
this._visible = true;
this.visibleChange.emit(true);
if (this.replaceableData) this.replaceableData.outputs.visibleChange(true);
});
} else {
this.selectedGroup = null;
this._visible = false;
this.visibleChange.emit(false);
if (this.replaceableData) this.replaceableData.outputs.visibleChange(false);
}
}
@Output() readonly visibleChange = new EventEmitter<boolean>();
@Select(PermissionManagementState.getPermissionGroups)
groups$: Observable<PermissionManagement.Group[]>;
@Select(PermissionManagementState.getEntityDisplayName)
entityName$: Observable<string>;
selectedGroup: PermissionManagement.Group;
permissions: PermissionManagement.Permission[] = [];
selectThisTab = false;
selectAllTab = false;
modalBusy = false;
trackByFn: TrackByFunction<PermissionManagement.Group> = (_, item) => item.name;
get selectedGroupPermissions$(): Observable<PermissionWithMargin[]> {
return this.groups$.pipe(
map((groups) =>
this.selectedGroup
? groups.find((group) => group.name === this.selectedGroup.name).permissions
: []
),
map<PermissionManagement.Permission[], PermissionWithMargin[]>((permissions) =>
permissions.map(
(permission) =>
(({
...permission,
margin: findMargin(permissions, permission),
isGranted: this.permissions.find((per) => per.name === permission.name).isGranted,
} as any) as PermissionWithMargin)
)
)
);
}
get isVisible(): boolean {
if (!this.replaceableData) return this.visible;
return this.replaceableData.inputs.visible;
}
constructor(
@Optional()
@Inject('REPLACEABLE_DATA')
public replaceableData: ReplaceableComponents.ReplaceableTemplateData<
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs
>,
private store: Store
) {}
getChecked(name: string) {
return (this.permissions.find((per) => per.name === name) || { isGranted: false }).isGranted;
}
isGrantedByOtherProviderName(grantedProviders: PermissionManagement.GrantedProvider[]): boolean {
if (grantedProviders.length) {
return grantedProviders.findIndex((p) => p.providerName !== this.providerName) > -1;
}
return false;
}
onClickCheckbox(clickedPermission: PermissionManagement.Permission, value) {
if (
clickedPermission.isGranted &&
this.isGrantedByOtherProviderName(clickedPermission.grantedProviders)
)
return;
setTimeout(() => {
this.permissions = this.permissions.map((per) => {
if (clickedPermission.name === per.name) {
return { ...per, isGranted: !per.isGranted };
} else if (clickedPermission.name === per.parentName && clickedPermission.isGranted) {
return { ...per, isGranted: false };
} else if (clickedPermission.parentName === per.name && !clickedPermission.isGranted) {
return { ...per, isGranted: true };
}
return per;
});
this.setTabCheckboxState();
this.setGrantCheckboxState();
}, 0);
}
setTabCheckboxState() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe((permissions) => {
const selectedPermissions = permissions.filter((per) => per.isGranted);
const element = document.querySelector('#select-all-in-this-tabs') as any;
if (selectedPermissions.length === permissions.length) {
element.indeterminate = false;
this.selectThisTab = true;
} else if (selectedPermissions.length === 0) {
element.indeterminate = false;
this.selectThisTab = false;
} else {
element.indeterminate = true;
}
});
}
setGrantCheckboxState() {
const selectedAllPermissions = this.permissions.filter((per) => per.isGranted);
const checkboxElement = document.querySelector('#select-all-in-all-tabs') as any;
if (selectedAllPermissions.length === this.permissions.length) {
checkboxElement.indeterminate = false;
this.selectAllTab = true;
} else if (selectedAllPermissions.length === 0) {
checkboxElement.indeterminate = false;
this.selectAllTab = false;
} else {
checkboxElement.indeterminate = true;
}
}
onClickSelectThisTab() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe((permissions) => {
permissions.forEach((permission) => {
if (permission.isGranted && this.isGrantedByOtherProviderName(permission.grantedProviders))
return;
const index = this.permissions.findIndex((per) => per.name === permission.name);
this.permissions = [
...this.permissions.slice(0, index),
{ ...this.permissions[index], isGranted: !this.selectThisTab },
...this.permissions.slice(index + 1),
];
});
});
this.setGrantCheckboxState();
}
onClickSelectAll() {
this.permissions = this.permissions.map((permission) => ({
...permission,
isGranted:
this.isGrantedByOtherProviderName(permission.grantedProviders) || !this.selectAllTab,
}));
this.selectThisTab = !this.selectAllTab;
}
onChangeGroup(group: PermissionManagement.Group) {
this.selectedGroup = group;
this.setTabCheckboxState();
}
submit() {
this.modalBusy = true;
const unchangedPermissions = getPermissions(
this.store.selectSnapshot(PermissionManagementState.getPermissionGroups)
);
const changedPermissions: PermissionManagement.MinimumPermission[] = this.permissions
.filter((per) =>
unchangedPermissions.find((unchanged) => unchanged.name === per.name).isGranted ===
per.isGranted
? false
: true
)
.map(({ name, isGranted }) => ({ name, isGranted }));
if (changedPermissions.length) {
this.store
.dispatch(
new UpdatePermissions({
providerKey: this.providerKey,
providerName: this.providerName,
permissions: changedPermissions,
})
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.visible = false;
});
} else {
this.modalBusy = false;
this.visible = false;
}
}
openModal() {
if (!this.providerKey || !this.providerName) {
throw new Error('Provider Key and Provider Name are required.');
}
return this.store
.dispatch(
new GetPermissions({
providerKey: this.providerKey,
providerName: this.providerName,
})
)
.pipe(
pluck('PermissionManagementState', 'permissionRes'),
tap((permissionRes: PermissionManagement.Response) => {
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
})
);
}
initModal() {
this.setTabCheckboxState();
this.setGrantCheckboxState();
}
onVisibleChange(visible: boolean) {
this.visible = visible;
if (this.replaceableData) {
this.replaceableData.inputs.visible = visible;
this.replaceableData.outputs.visibleChange(visible);
}
}
}
function findMargin(
permissions: PermissionManagement.Permission[],
permission: PermissionManagement.Permission
) {
const parentPermission = permissions.find((per) => per.name === permission.parentName);
if (parentPermission && parentPermission.parentName) {
let margin = 20;
return (margin += findMargin(permissions, parentPermission));
}
return parentPermission ? 20 : 0;
}
function getPermissions(groups: PermissionManagement.Group[]): PermissionManagement.Permission[] {
return groups.reduce((acc, val) => [...acc, ...val.permissions], []);
}
```
Open the generated `permission-management.component.html` in `src/app/permission-management` folder and replace the content with the below:
```html
<abp-modal
[visible]="isVisible"
(visibleChange)="onVisibleChange($event)"
(init)="initModal()"
[busy]="modalBusy"
>
<ng-container *ngIf="{ entityName: entityName$ | async } as data">
<ng-template #abpHeader>
<h4>
{%{{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}}%} - {%{{{ data.entityName }}}%}
</h4>
</ng-template>
<ng-template #abpBody>
<div class="custom-checkbox custom-control mb-2">
<input
type="checkbox"
id="select-all-in-all-tabs"
name="select-all-in-all-tabs"
class="custom-control-input"
[(ngModel)]="selectAllTab"
(click)="onClickSelectAll()"
/>
<label class="custom-control-label" for="select-all-in-all-tabs">{%{{{
'AbpPermissionManagement::SelectAllInAllTabs' | abpLocalization
}}}%}</label>
</div>
<hr class="mt-2 mb-2" />
<div class="row">
<div class="overflow-scroll col-md-4">
<ul class="nav nav-pills flex-column">
<li *ngFor="let group of groups$ | async; trackBy: trackByFn" class="nav-item">
<a
class="nav-link pointer"
[class.active]="selectedGroup?.name === group?.name"
(click)="onChangeGroup(group)"
>{%{{{ group?.displayName }}}%}</a
>
</li>
</ul>
</div>
<div class="col-md-8 overflow-scroll">
<h4>{%{{{ selectedGroup?.displayName }}}%}</h4>
<hr class="mt-2 mb-3" />
<div class="pl-1 pt-1">
<div class="custom-checkbox custom-control mb-2">
<input
type="checkbox"
id="select-all-in-this-tabs"
name="select-all-in-this-tabs"
class="custom-control-input"
[(ngModel)]="selectThisTab"
(click)="onClickSelectThisTab()"
/>
<label class="custom-control-label" for="select-all-in-this-tabs">{%{{{
'AbpPermissionManagement::SelectAllInThisTab' | abpLocalization
}}}%}</label>
</div>
<hr class="mb-3" />
<div
*ngFor="
let permission of selectedGroupPermissions$ | async;
let i = index;
trackBy: trackByFn
"
[style.margin-left]="permission.margin + 'px'"
class="custom-checkbox custom-control mb-2"
>
<input
#permissionCheckbox
type="checkbox"
[checked]="getChecked(permission.name)"
[value]="getChecked(permission.name)"
[attr.id]="permission.name"
class="custom-control-input"
[disabled]="isGrantedByOtherProviderName(permission.grantedProviders)"
/>
<label
class="custom-control-label"
[attr.for]="permission.name"
(click)="onClickCheckbox(permission, permissionCheckbox.value)"
>{%{{{ permission.displayName }}}%}
<ng-container *ngIf="!hideBadges">
<span
*ngFor="let provider of permission.grantedProviders"
class="badge badge-light"
>{%{{{ provider.providerName }}}%}: {%{{{ provider.providerKey }}}%}</span
>
</ng-container>
</label>
</div>
</div>
</div>
</div>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpIdentity::Cancel' | abpLocalization }}}%}
</button>
<abp-button iconClass="fa fa-check" (click)="submit()">{%{{{
'AbpIdentity::Save' | abpLocalization
}}}%}</abp-button>
</ng-template>
</ng-container>
</abp-modal>
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngxs/store';
import { PermissionManagementComponent } from './permission-management/permission-management.component';
//...
export class AppComponent implements OnInit {
constructor(private store: Store) {} // injected store
ngOnInit() {
// added dispatching the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: PermissionManagementComponent,
key: ePermissionManagementComponents.PermissionManagement,
})
);
}
}
```
## See Also
- [Component Replacement](./Component-Replacement.md)

BIN
docs/en/UI/Angular/images/layout-components.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/en/UI/Angular/images/logo-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/en/UI/Angular/images/nav-items-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/UI/Angular/images/permission-management-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

BIN
docs/en/UI/Angular/images/replaced-logo-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
docs/en/UI/Angular/images/replaced-nav-items-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/UI/Angular/images/replaced-routes-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/en/UI/Angular/images/routes-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

6
docs/en/UI/AspNetCore/Customization-User-Interface.md

@ -51,15 +51,13 @@ namespace Acme.BookStore.Web.Pages.Identity.Users
### Overriding a Razor Page (.CSHTML) ### Overriding a Razor Page (.CSHTML)
Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through the [Virtual File System](../../Virtual-File-System.md). Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through creating the same `.cshtml` file under the same path.
Virtual File system allows us to **embed resources into assemblies**. In this way, pre-built modules define the razor pages inside their NuGet packages. When you depend a module, you can override any file added to the virtual file system by that module, including pages/views.
#### Example #### Example
This example overrides the **login page** UI defined by the [Account Module](../../Modules/Account.md). This example overrides the **login page** UI defined by the [Account Module](../../Modules/Account.md).
Physical files override the embedded files defined in the same location. The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path: The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path:
![overriding-login-cshtml](../../images/overriding-login-cshtml.png) ![overriding-login-cshtml](../../images/overriding-login-cshtml.png)

5
docs/en/API/JavaScript-API/Index.md → docs/en/UI/AspNetCore/JavaScript-API/Index.md

@ -5,7 +5,7 @@ ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages application
## APIs ## APIs
* abp.ajax * abp.ajax
* [abp.auth](Auth.md) * abp.auth
* abp.currentUser * abp.currentUser
* abp.dom * abp.dom
* abp.event * abp.event
@ -20,5 +20,4 @@ ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages application
* abp.utils * abp.utils
* abp.ResourceLoader * abp.ResourceLoader
* abp.WidgetManager * abp.WidgetManager
* Other APIs * Other APIs

40
docs/en/UI/AspNetCore/Tag-Helpers/Badges.md

@ -0,0 +1,40 @@
# Badges
## Introduction
`abp-badge` and `abp-badge-pill` are abp tags for badges.
Basic usage:
````csharp
<span abp-badge="Primary">Primary</span>
<a abp-badge="Info" href="#">Info</a>
<a abp-badge-pill="Danger" href="#">Danger</a>
````
## Demo
See the [badges demo page](https://bootstrap-taghelpers.abp.io/Components/Badges) to see it in action.
### Values
* Indicates the type of the badge. Should be one of the following values:
* `_` (default value)
* `Default` (default value)
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
Example:
````csharp
<span abp-badge-pill="Danger">Danger</span>
````

126
docs/en/UI/AspNetCore/Tag-Helpers/Borders.md

@ -0,0 +1,126 @@
# Borders
## Introduction
`abp-border` is a main element for border styling.
Basic usage:
````csharp
<span abp-border="Default"></span>
<span abp-border="Top"></span>
<span abp-border="Right"></span>
<span abp-border="Bottom"></span>
<span abp-border="Left"></span>
````
## Demo
See the [borders demo page](https://bootstrap-taghelpers.abp.io/Components/Borders) to see it in action.
## Values
A value indicates type, position and the color of the border. Should be one of the following values:
* `Default`
* `_0`
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
* `White`
* `Primary_0`
* `Secondary_0`
* `Success_0`
* `Danger_0`
* `Warning_0`
* `Info_0`
* `Light_0`
* `Dark_0`
* `White_0`
* `Top`
* `Top_0`
* `Top_Primary`
* `Top_Secondary`
* `Top_Success`
* `Top_Danger`
* `Top_Warning`
* `Top_Info`
* `Top_Light`
* `Top_Dark`
* `Top_White`
* `Top_Primary_0`
* `Top_Secondary_0`
* `Top_Success_0`
* `Top_Danger_0`
* `Top_Warning_0`
* `Top_Info_0`
* `Top_Light_0`
* `Top_Dark_0`
* `Top_White_0`
* `Right`
* `Right_0`
* `Right_Primary`
* `Right_Secondary`
* `Right_Success`
* `Right_Danger`
* `Right_Warning`
* `Right_Info`
* `Right_Light`
* `Right_Dark`
* `Right_White`
* `Right_Primary_0`
* `Right_Secondary_0`
* `Right_Success_0`
* `Right_Danger_0`
* `Right_Warning_0`
* `Right_Info_0`
* `Right_Light_0`
* `Right_Dark_0`
* `Right_White_0`
* `Left`
* `Left_0`
* `Left_Primary`
* `Left_Secondary`
* `Left_Success`
* `Left_Danger`
* `Left_Warning`
* `Left_Info`
* `Left_Light`
* `Left_Dark`
* `Left_White`
* `Left_Primary_0`
* `Left_Secondary_0`
* `Left_Success_0`
* `Left_Danger_0`
* `Left_Warning_0`
* `Left_Info_0`
* `Left_Light_0`
* `Left_Dark_0`
* `Left_White_0`
* `Bottom`
* `Bottom_0`
* `Bottom_Primary`
* `Bottom_Secondary`
* `Bottom_Success`
* `Bottom_Danger`
* `Bottom_Warning`
* `Bottom_Info`
* `Bottom_Light`
* `Bottom_Dark`
* `Bottom_White`
* `Bottom_Primary_0`
* `Bottom_Secondary_0`
* `Bottom_Success_0`
* `Bottom_Danger_0`
* `Bottom_Warning_0`
* `Bottom_Info_0`
* `Bottom_Light_0`
* `Bottom_Dark_0`
* `Bottom_White_0`

25
docs/en/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md

@ -0,0 +1,25 @@
# Breadcrumbs
## Introduction
`abp-breadcrumb` is the main container for breadcrumb items.
Basic usage:
````csharp
<abp-breadcrumb>
<abp-breadcrumb-item href="#" title="Home" />
<abp-breadcrumb-item href="#" title="Library"/>
<abp-breadcrumb-item title="Page"/>
</abp-breadcrumb>
````
## Demo
See the [breadcrumbs demo page](https://bootstrap-taghelpers.abp.io/Components/Breadcrumbs) to see it in action.
## abp-breadcrumb-item Attributes
- **title**: Sets the text of the breadcrumb item.
- **active**: Sets the active breadcrumb item. Last item is active by default, if no other item is active.
- **href**: A value indicates if an `abp-breadcrumb-item` has a link. Should be a string link value.

37
docs/en/UI/AspNetCore/Tag-Helpers/Button-groups.md

@ -0,0 +1,37 @@
# Button groups
## Introduction
`abp-button-group` is the main container for grouped button elements.
Basic usage:
````csharp
<abp-button-group>
<abp-button button-type="Secondary">Left</abp-button>
<abp-button button-type="Secondary">Middle</abp-button>
<abp-button button-type="Secondary">Right</abp-button>
</abp-button-group>
````
## Demo
See the [button groups demo page](https://bootstrap-taghelpers.abp.io/Components/Button-groups) to see it in action.
## Attributes
### direction
A value indicates the direction of the buttons. Should be one of the following values:
* `Horizontal` (default value)
* `Vertical`
### size
A value indicates the size of the buttons in the group. Should be one of the following values:
* `Default` (default value)
* `Small`
* `Medium`
* `Large`

74
docs/en/UI/AspNetCore/Tag-Helpers/Carousel.md

@ -0,0 +1,74 @@
# Carousel
## Introduction
`abp-carousel` is a the abp tag for carousel element.
Basic usage:
````csharp
<abp-carousel>
<abp-carousel-item src=""></abp-carousel-item>
<abp-carousel-item src=""></abp-carousel-item>
<abp-carousel-item src=""></abp-carousel-item>
</abp-carousel>
````
## Demo
See the [carousel_demo page](https://bootstrap-taghelpers.abp.io/Components/Carousel) to see it in action.
## Attributes
### id
A value sets the id of the carousel. If not set, generated id will be set whenever the tag is created.
### controls
A value to enable the controls (previous and next buttons) on carousel. Should be one of the following values:
* `false`
* `true`
### indicators
A value to enables the indicators on carousel. Should be one of the following values:
* `false`
* `true`
### crossfade
A value to enables the fade animation instead of slide on carousel. Should be one of the following values:
* `false`
* `true`
## abp-carousel-item Attributes
### caption-title
A value sets the caption title of the carousel item.
### caption
A value sets the caption of the carousel item.
### src
A link value sets the source of the image displayed on carousel item.
### active
A value to set the active carousel item. Should be one of the following values:
* `false`
* `true`
### alt
A value sets the alternate text for the carousel item image when the image can not be displayed.

114
docs/en/UI/AspNetCore/Tag-Helpers/Navs.md

@ -0,0 +1,114 @@
# Navs
## Introduction
`abp-nav` is the basic tag helper component derived from bootstrap nav element.
Basic usage:
````csharp
<abp-nav nav-style="Pill" align="Center">
<abp-nav-item>
<a abp-nav-link active="true" href="#">Active</a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link href="#">Longer nav link</a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link href="#">link</a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link disabled="true" href="#">disabled</a>
</abp-nav-item>
</abp-nav>
````
## Demo
See the [navs demo page](https://bootstrap-taghelpers.abp.io/Components/Navs) to see it in action.
## abp-nav Attributes
- **nav-style**: The value indicates the positioning and style of the containing items. Should be one of the following values:
* `Default` (default value)
* `Vertical`
* `Pill`
* `PillVertical`
- **align:** The value indicates the alignment of the containing items:
* `Default` (default value)
* `Start`
* `Center`
* `End`
### abp-nav-bar Attributes
- **nav-style**: The value indicates the color layout of the base navigation bar. Should be one of the following values:
* `Default` (default value)
* `Dark`
* `Light`
* `Dark_Primary`
* `Dark_Secondary`
* `Dark_Success`
* `Dark_Danger`
* `Dark_Warning`
* `Dark_Info`
* `Dark_Dark`
* `Dark_Link`
* `Light_Primary`
* `Light_Secondary`
* `Light_Success`
* `Light_Danger`
* `Light_Warning`
* `Light_Info`
* `Light_Dark`
* `Light_Link`
- **size:** The value indicates size of the base navigation bar. Should be one of the following values:
* `Default` (default value)
* `Sm`
* `Md`
* `Lg`
* `Xl`
### abp-nav-item Attributes
**dropdown**: A value that sets the navigation item to be a dropdown menu if provided. Can be one of the following values:
* `false` (default value)
* `true`
Example:
````csharp
<abp-nav-bar size="Lg" navbar-style="Dark_Warning">
<a abp-navbar-brand href="#">Navbar</a>
<abp-navbar-toggle>
<abp-navbar-nav>
<abp-nav-item active="true">
<a abp-nav-link href="#">Home <span class="sr-only">(current)</span></a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link href="#">Link</a>
</abp-nav-item>
<abp-nav-item dropdown="true">
<abp-dropdown>
<abp-dropdown-button nav-link="true" text="Dropdown" />
<abp-dropdown-menu>
<abp-dropdown-header>Dropdown header</abp-dropdown-header>
<abp-dropdown-item href="#" active="true">Action</abp-dropdown-item>
<abp-dropdown-item href="#" disabled="true">Another disabled action</abp-dropdown-item>
<abp-dropdown-item href="#">Something else here</abp-dropdown-item>
<abp-dropdown-divider />
<abp-dropdown-item href="#">Separated link</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link disabled="true" href="#">Disabled</a>
</abp-nav-item>
</abp-navbar-nav>
<span abp-navbar-text>
Sample Text
</span>
</abp-navbar-toggle>
</abp-nav-bar>
````

61
docs/en/UI/AspNetCore/Tag-Helpers/Tables.md

@ -0,0 +1,61 @@
# Tables
## Introduction
`abp-table` is the basic tag component for tables in abp.
Basic usage:
````csharp
<abp-table hoverable-rows="true" responsive-sm="true">
<thead>
<tr>
<th scope="Column">#</th>
<th scope="Column">First</th>
<th scope="Column">Last</th>
<th scope="Column">Handle</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="Row">1</th>
<td>Mark</td>
<td>Otto</td>
<td table-style="Danger">mdo</td>
</tr>
<tr table-style="Warning">
<th scope="Row">2</th>
<td>Jacob</td>
<td>Thornton</td>
<td>fat</td>
</tr>
<tr>
<th scope="Row">3</th>
<td table-style="Success">Larry</td>
<td>the Bird</td>
<td>twitter</td>
</tr>
</tbody>
</abp-table>
````
## Demo
See the [tables demo page](https://bootstrap-taghelpers.abp.io/Components/Tables) to see it in action.
## abp-table Attributes
- **responsive**: Used to create responsive tables up to a particular breakpoint. see [breakpoint specific](https://getbootstrap.com/docs/4.1/content/tables/#breakpoint-specific) for more information.
- **responsive-sm**: If not set to false, sets the table responsiveness for small screen devices.
- **responsive-md**: If not set to false, sets the table responsiveness for medium screen devices.
- **responsive-lg**: If not set to false, sets the table responsiveness for large screen devices.
- **responsive-xl**: If not set to false, sets the table responsiveness for extra large screen devices.
- **dark-theme**: If set to true, sets the table color theme to dark.
- **striped-rows**: If set to true, adds zebra-striping to table rows.
- **hoverable-rows**: If set to true, adds hover state to table rows.
- **border-style**: Sets the border style of the table. Should be one of the following values:
- `Default` (default)
- `Bordered`
- `Borderless`

10
docs/en/Virtual-File-System.md

@ -1,6 +1,6 @@
## Virtual File System ## Virtual File System
The Virtual File System makes it possible to manage files that do not physically exist on the file system (disk). It's mainly used to embed (js, css, image, cshtml...) files into assemblies and use them like physical files at runtime. The Virtual File System makes it possible to manage files that do not physically exist on the file system (disk). It's mainly used to embed (js, css, image..) files into assemblies and use them like physical files at runtime.
### Volo.Abp.VirtualFileSystem Package ### Volo.Abp.VirtualFileSystem Package
@ -147,7 +147,7 @@ The code above assumes that `MyWebAppModule` and `MyModule` are two different pr
The Virtual File System is well integrated to ASP.NET Core: The Virtual File System is well integrated to ASP.NET Core:
* Virtual files can be used just like physical (static) files in a web application. * Virtual files can be used just like physical (static) files in a web application.
* Razor Views, Razor Pages, js, css, image files and all other web content types can be embedded into assemblies and used just like the physical files. * Js, css, image files and all other web content types can be embedded into assemblies and used just like the physical files.
* An application (or another module) can override a virtual file of a module just like placing a file with the same name and extension into the same folder of the virtual file. * An application (or another module) can override a virtual file of a module just like placing a file with the same name and extension into the same folder of the virtual file.
#### Virtual Files Middleware #### Virtual Files Middleware
@ -161,9 +161,3 @@ app.UseVirtualFiles();
Adding virtual files middleware after the static files middleware makes it possible to override a virtual file with a real physical file simply by placing it in the same location as the virtual file. Adding virtual files middleware after the static files middleware makes it possible to override a virtual file with a real physical file simply by placing it in the same location as the virtual file.
>The Virtual File Middleware only serves the virtual wwwroot folder contents - just like the other static files. >The Virtual File Middleware only serves the virtual wwwroot folder contents - just like the other static files.
#### Views & Pages
Embedded razor views/pages are available in the application without any configuration. Simply place them into the standard Views/Pages virtual folders of the module being developed.
An embedded view/page can be overrided if a module/application locates a new file into the same location as mentioned above.

76
docs/en/docs-nav.json

@ -180,26 +180,46 @@
"path": "Object-To-Object-Mapping.md" "path": "Object-To-Object-Mapping.md"
}, },
{ {
"text": "Text Templating", "text": "BLOB Storing",
"path": "Text-Templating.md" "items": [
}, {
{ "text": "BLOB Storing System",
"text": "Object Serialization" "path": "Blob-Storing.md"
}, },
{ {
"text": "JSON Serialization" "text": "Storage Providers",
}, "items": [
{ {
"text": "Emailing" "text": "File System Provider",
"path": "Blob-Storing-File-System.md"
},
{
"text": "Database Provider",
"path": "Blob-Storing-Database.md"
},
{
"text": "Azure Provider",
"path": "Blob-Storing-Azure.md"
},
{
"text": "Create a Custom Provider",
"path": "Blob-Storing-Custom-Provider.md"
}
]
}
]
}, },
{ {
"text": "GUIDs" "text": "Text Templating",
"path": "Text-Templating.md"
}, },
{ {
"text": "Threading" "text": "GUID Generation",
"path": "Guid-Generation.md"
}, },
{ {
"text": "Timing" "text": "Timing",
"path": "Timing.md"
} }
] ]
}, },
@ -261,7 +281,8 @@
"path": "Application-Services.md" "path": "Application-Services.md"
}, },
{ {
"text": "Data Transfer Objects" "text": "Data Transfer Objects",
"path": "Data-Transfer-Objects.md"
}, },
{ {
"text": "Unit Of Work" "text": "Unit Of Work"
@ -280,6 +301,15 @@
{ {
"text": "Dynamic C# API Clients", "text": "Dynamic C# API Clients",
"path": "API/Dynamic-CSharp-API-Clients.md" "path": "API/Dynamic-CSharp-API-Clients.md"
},
{
"text": "ABP Endpoints",
"items": [
{
"text": "Application Configuration",
"path": "API/Application-Configuration.md"
}
]
} }
] ]
}, },
@ -440,6 +470,10 @@
"text": "To PostgreSQL", "text": "To PostgreSQL",
"path": "Entity-Framework-Core-PostgreSQL.md" "path": "Entity-Framework-Core-PostgreSQL.md"
}, },
{
"text": "To Oracle",
"path": "Entity-Framework-Core-Oracle.md"
},
{ {
"text": "To SQLite", "text": "To SQLite",
"path": "Entity-Framework-Core-SQLite.md" "path": "Entity-Framework-Core-SQLite.md"
@ -457,6 +491,10 @@
"path": "Dapper.md" "path": "Dapper.md"
} }
] ]
},
{
"text": "Data Seeding",
"path": "Data-Seeding.md"
} }
] ]
}, },
@ -516,6 +554,10 @@
{ {
"text": "Module", "text": "Module",
"path": "Startup-Templates/Module.md" "path": "Startup-Templates/Module.md"
},
{
"text": "Console",
"path": "Startup-Templates/Console.md"
} }
] ]
}, },
@ -551,6 +593,10 @@
{ {
"text": "Contribution Guide", "text": "Contribution Guide",
"path": "Contribution/Index.md" "path": "Contribution/Index.md"
},
{
"text": "API Documentation",
"path": "{ApiDocumentationUrl}"
} }
] ]
} }

BIN
docs/en/images/virtual-file-explorer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

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

@ -222,7 +222,7 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore namespace Acme.BookStore
{ {
public interface IBookAppService : public interface IBookAppService :
ICrudAppService< //Defines CRUD methods ICrudAppService< //Defines CRUD methods
BookDto, //Used to show books BookDto, //Used to show books
Guid, //Primary key of the book entity Guid, //Primary key of the book entity
@ -251,12 +251,12 @@ using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore namespace Acme.BookStore
{ {
public class BookAppService : public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto, CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>, CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService IBookAppService
{ {
public BookAppService(IRepository<Book, Guid> repository) public BookAppService(IRepository<Book, Guid> repository)
: base(repository) : base(repository)
{ {
@ -338,13 +338,11 @@ Abra `Index.cshtml`e altere o conteúdo, como mostrado abaixo:
```html ```html
@page @page
@using Acme.BookStore.Web.Pages.Books @using Acme.BookStore.Web.Pages.Books
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model IndexModel @model IndexModel
<h2>Books</h2> <h2>Books</h2>
``` ```
- Esse código altera a herança padrão do Razor View Page Model para que ele **herda** da `BookStorePage`classe (em vez de `PageModel`). A `BookStorePage`classe que acompanha o modelo de inicialização e fornece algumas propriedades / métodos compartilhados usados por todas as páginas.
- Verifique se o `IndexModel`( *Index.cshtml.cs)* possui o `Acme.BookStore.Pages.Books`espaço para nome ou atualize-o no `Index.cshtml`. - Verifique se o `IndexModel`( *Index.cshtml.cs)* possui o `Acme.BookStore.Pages.Books`espaço para nome ou atualize-o no `Index.cshtml`.
#### Adicionar página de livros ao menu principal #### Adicionar página de livros ao menu principal
@ -395,7 +393,6 @@ Altere o `Pages/Books/Index.cshtml`seguinte:
```html ```html
@page @page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model Acme.BookStore.Web.Pages.Books.IndexModel @model Acme.BookStore.Web.Pages.Books.IndexModel
@section scripts @section scripts
{ {
@ -459,4 +456,4 @@ A interface do usuário final é mostrada abaixo:
### Próxima parte ### Próxima parte
Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II) deste tutorial. Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II) deste tutorial.

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

@ -67,7 +67,6 @@ Abra o `CreateModal.cshtml`arquivo e cole o código abaixo:
```html ```html
@page @page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model Acme.BookStore.Web.Pages.Books.CreateModalModel @model Acme.BookStore.Web.Pages.Books.CreateModalModel
@{ @{
@ -86,13 +85,13 @@ Abra o `CreateModal.cshtml`arquivo e cole o código abaixo:
- Este modal usa o - Este modal usa o
``` ```
abp-dynamic-form abp-dynamic-form
``` ```
auxiliar de marca para criar automaticamente o formulário a partir da auxiliar de marca para criar automaticamente o formulário a partir da
``` ```
CreateBookViewModel CreateBookViewModel
@ -234,7 +233,6 @@ Substitua o `EditModal.cshtml`conteúdo pelo seguinte:
```html ```html
@page @page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Acme.BookStore.Web.Pages.Books @using Acme.BookStore.Web.Pages.Books
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel @model EditModalModel
@ -464,4 +462,4 @@ Execute o aplicativo e tente excluir um livro.
### Próxima parte ### Próxima parte
Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III) deste tutorial. Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III) deste tutorial.

22
docs/zh-Hans/API/Application-Configuration.md

@ -0,0 +1,22 @@
# 应用程序配置端点
ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表:
* [本地化](Localization.md)值, 支持应用程序的当前语言.
* 当前用户可用和已授予的[策略](Authorization.md)(权限).
* 当前用户的[设置](Settings.md)值.
* 关于[当前用户](CurrentUser.md)的信息 (如 id 和用户名).
* 关于当前[租户](Multi-Tenancy.md)的信息 (如 id 和名称).
* 当前用户的[时区](Timing.md)信息和应用程序的[时钟](Timing.md)类型.
## HTTP API
如果您导航到基于ABP框架的web应用程序或HTTP服务的 `/api/abp/application-configuration` URL, 你可以得到JSON对象形式配置. 该端点对于创建应用程序的客户端很有用.
## Script
对于ASP.NET Core MVC(剃刀页)应用程序,同样的配置值在JavaScript端也可用. `/Abp/ApplicationConfigurationScript` 是基于上述HTTP API自动生成的脚本的URL.
参阅 [JavaScript API文档](../UI/AspNetCore/JavaScript-API/Index.md) 了解关于ASP.NET Core UI.
其他UI类型提供相关平台的本地服务. 例如查看[Angular UI本地化文档](../UI/Angular/Localization.md)来学习如何使用这个端点公开的本地化值.

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

@ -2,7 +2,7 @@
应用服务实现应用程序的**用例**, 将**领域层逻辑公开给表示层**. 应用服务实现应用程序的**用例**, 将**领域层逻辑公开给表示层**.
从表示层(可选)调用应用服务,**DTO (数据传对象)** 作为参数. 返回(可选)DTO给表示层. 从表示层(可选)调用应用服务,**DTO ([数据传对象](Data-Transfer-Objects.md))** 作为参数. 返回(可选)DTO给表示层.
## 示例 ## 示例

58
docs/zh-Hans/Blob-Storing-Azure.md

@ -0,0 +1,58 @@
# BLOB Storing Azure提供程序
BLOB存储Azure提供程序可以将BLOB存储在[Azure Blob storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置Azure提供程序.
## 安装
使用ABP CLI添加[Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure)NuGet包到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring.Azure` 包的 `.csproj` 文件目录打开命令行.
* 运行 `Volo.Abp.BlobStoring.Azure` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.Azure` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringAzureModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## 配置
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用Azure存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAzure(azure =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
### 选项
* **ConnectionString** (string): 连接字符串包括应用程序在运行时使用共享密钥授权访问Azure存储帐户中的数据所需的授权信息. 请参考[Azure文档](https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string).
* **ContainerName** (string): 你可以在azure中指定容器名称. 如果没有指定它将使用 `BlogContainerName` 属性定义的BLOB容器的名称(请参阅[BLOB存储文档](Blob-Storing.md)). 请注意Azure有一些**命名容器的规则**,容器名称必须是有效的DNS名称,[符合以下命名规则](https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names):
* 容器名称必须以字母或数字开头或结尾,并且只能包含字母,数字和破折号(-)字符.
* 每个破折号(-)必须紧跟在字母或数字之后;容器名称中不允许使用连续的破折号.
* 容器名称中的所有字母都必须**小写**.
* 容器名称的长度必须在**3**到**63**个字符之间.
* **CreateContainerIfNotExists** (bool): 默认值为 `false`, 如果azure中不存在容器, `AzureBlobProvider` 将尝试创建它.
## Azure BLOB 名称计算器
Azure BLOB提供程序组织BLOB名称并实现一些约定. 默认情况下BLOB的全名由以下规则确定:
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串.
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 字符串.
* 追加 BLOB 名称.
## 其他服务
* `AzureBlobProvider` 是实现Azure BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AzureBlobProvider` 类).
* `IAzureBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAzureBlobNameCalculator` . 如果你想自定义文件路径计算,可以替换/覆盖它.

177
docs/zh-Hans/Blob-Storing-Custom-Provider.md

@ -0,0 +1,177 @@
# BLOB 存储: 创建自定义提供程序
本文档通过一个示例说明如何为BLOB存储系统创建新的存储提供程序.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何创建新存储提供程序.
## 示例实现
第一步是创建一个实现 `IBlobProvider` 接口或 `BlobProviderBase` 抽象类继承的类.
````csharp
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
namespace AbpDemo
{
public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
{
public override Task SaveAsync(BlobProviderSaveArgs args)
{
//TODO...
}
public override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
//TODO...
}
public override Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
//TODO...
}
public override Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
//TODO...
}
}
}
````
* `MyCustomBlobProvider` 继承 `BlobProviderBase` 并覆盖 `abstract` 方法. 实际的实现取决于你.
* 实现 `ITransientDependency` 接口将这个类注做为瞬态服务注册到[依赖注入](Dependency-Injection.md)系统.
> **注意: 命名约定很重要**. 如果类名没有以 `BlobProvider` 结尾,则必须手动注册/公开你的服务为 `IBlobProvider`.
这是所有. 现在你可以配置容器(在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中)使用 `MyCustomBlobProvider` 类:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.ProviderType = typeof(MyCustomBlobProvider);
});
});
````
> 如果你想配置特定的容器,请参阅[BLOB存储文档](Blob-Storing.md).
### BlobContainerConfiguration 扩展方法
如果你想提供一个更简单的配置方式,可以为 `BlobContainerConfiguration` 类创建一个扩展方法:
````csharp
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
this BlobContainerConfiguration containerConfiguration)
{
containerConfiguration.ProviderType = typeof(MyCustomBlobProvider);
return containerConfiguration;
}
}
````
然后你可以使用扩展方法更容易地配置容器:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider();
});
});
````
### 额外的配置选项
`BlobContainerConfiguration` 允许添加/删除提供程序特定的配置对象. 如果你的提供者需要额外的配置,你可以为 `BlobContainerConfiguration` 创建一个包装类提供的类型安全配置选项:
````csharp
public class MyCustomBlobProviderConfiguration
{
public string MyOption1
{
get => _containerConfiguration
.GetConfiguration<string>("MyCustomBlobProvider.MyOption1");
set => _containerConfiguration
.SetConfiguration("MyCustomBlobProvider.MyOption1", value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public MyCustomBlobProviderConfiguration(
BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
````
然后你可以这样更改 `MyBlobContainerConfigurationExtensions` 类:
````csharp
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
this BlobContainerConfiguration containerConfiguration,
Action<MyCustomBlobProviderConfiguration> configureAction)
{
containerConfiguration.ProviderType = typeof(MyCustomBlobProvider);
configureAction.Invoke(
new MyCustomBlobProviderConfiguration(containerConfiguration)
);
return containerConfiguration;
}
public static MyCustomBlobProviderConfiguration GetMyCustomBlobProviderConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new MyCustomBlobProviderConfiguration(containerConfiguration);
}
}
````
* 向 `UseMyCustomBlobProvider` 方法添加了一个参数,允许开发人员设置其他选项.
* 添加了一个新的 `GetMyCustomBlobProviderConfiguration` 方法,该方法将在 `MyCustomBlobProvider` 类内使用获取配置的值.
然后任何人都可以如下设置 `MyOption1`:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider(provider =>
{
provider.MyOption1 = "my value";
});
});
});
````
最后你可以使用 `GetMyCustomBlobProviderConfiguration` 方法访问额外的选项:
````csharp
public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
{
public override Task SaveAsync(BlobProviderSaveArgs args)
{
var config = args.Configuration.GetMyCustomBlobProviderConfiguration();
var value = config.MyOption1;
//...
}
}
````
## 贡献?
如果你创建了一个新的提供程序,并且认为它对其他开发者有用,请考虑为GitHub上的ABP框架做出[贡献](Contribution/Index.md).

96
docs/zh-Hans/Blob-Storing-Database.md

@ -0,0 +1,96 @@
# BLOB存储数据库提供程序
BLOB存储数据库提供程序可以将BLOB存储在关系或非关系数据库中.
有两个数据库提供程序实现;
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) 包实现[EF Core](Entity-Framework-Core.md), 它可以通过EF Core存储BLOB在[任何支持的DBMS](https://docs.microsoft.com/en-us/ef/core/providers/)中.
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) 包实现了[MongoDB](MongoDB.md).
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置数据库提供程序.
## 安装
### 自动安装
如果你已基于[应用程序启动模板](Startup-Templates/Application.md)创建了解决方案,则可以使用 `abp add-module` [CLI](CLI.md)命令将相关软件包自动添加到解决方案中.
在包含解决方案(`.sln`)文件的文件夹中打开命令行运行以下命令:
````bash
abp add-module Volo.Abp.BlobStoring.Database
````
此命令将所有NuGet软件包添加到解决方案的相应层. 如果使用的是EF Core,它会添加必要的配置,添加新的数据库迁移并更新数据库.
### 手动安装
这里是此提供程序定义的所有包:
* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Domain.Shared)
* [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain)
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore)
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB)
你可以只安装 `Volo.Abp.BlobStoring.Database.EntityFrameworkCore` 或 `Volo.Abp.BlobStoring.Database.MongoDB` (根据你的偏好),因为它们依赖其他包.
安装完成后,添加 `DepenedsOn` 属性到相关[模块](Module-Development-Basics.md).下面是由上面列出的相关NuGet包定义的模块类列表:
* `BlobStoringDatabaseDomainModule`
* `BlobStoringDatabaseDomainSharedModule`
* `BlobStoringDatabaseEntityFrameworkCoreModule`
* `BlobStoringDatabaseMongoDbModule`
如果你正在使用EF Core,还需要配置你的**Migration DbContext**将BLOB存储表添加到你的数据库. 在 `OnModelCreating` 方法中调用 `builder.ConfigureBlobStoring()` 扩展方法来包含到DbContext的映射. 你可以使用标准的 `Add-Migration` 和 `Update-Database` [命令](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)在数据库中创建必要的表.
## 配置
### 连接字符串
如果你要使用你的 `Default` 连接字符串,则不需要做任何其他配置.
如果要将BLOB存储到单独的数据库,请在配置文件(`appsettings.json`)中将 `AbpBlobStoring` 用作连接字符串名称. 请阅读[EF Core Migrations](Entity-Framework-Core-Migrations.md)文档了解如何为所需模块创建和使用其他数据库.
### 配置容器
如果只使用数据库存储提供程序,则不需要手动配置,因为它是自动完成的. 如果使用多个存储提供程序,可能需要对其进行配置.
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用数据库系统存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseDatabase();
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
## 附加信息
它需要使用[BLOB存储服务](Blob-Storing.md)来使用BLOB存储系统. 但是如果要处理数据库表/实体,可以使用以下信息.
### 实体
此模块定义的实体:
* `DatabaseBlobContainer` (aggregate root) 表示存储在数据库中的容器.
* `DatabaseBlob` (aggregate root) 表示数据库中的BLOB.
参阅[实体文档](Entities.md)了解什么是实体和聚合根.
### 仓储
* `IDatabaseBlobContainerRepository`
* `IDatabaseBlobRepository`
你还可以使用 `IRepository` 和 `IRepository` 来获得 `IQueryable` 能力. 更多信息请参阅[仓储文档](Repositories.md).
### 其他服务
* `DatabaseBlobProvider` 是实现数据库BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `DatabaseBlobProvider` 类).

58
docs/zh-Hans/Blob-Storing-File-System.md

@ -0,0 +1,58 @@
# BLOB存储文件系统提供程序
文件系统存储提供程序用于将BLOB作为文件夹中的标准文件存储在本地文件系统中.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置文件系统.
## 介绍
使用ABP CLI添加[Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem)NuGet包到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring.FileSystem` 包的 `.csproj` 文件目录打开命令行.
* 运行 `abp add-package Volo.Abp.BlobStoring.FileSystem` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.FileSystem` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringFileSystemModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## 配置
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用文件系统存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = "C:\\my-files";
});
});
});
````
`UseFileSystem` 扩展方法用于为容器设置文件系统提供程序并配置文件系统选项.
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
### 选项
* **BasePath** (string): 存储BLOB的基本文件夹路径,它是必选的.
* **AppendContainerNameToBasePath** (bool; 默认: `true`): 指定是否在基本文件夹中创建具有容器名称的文件夹. 如果你在同一个 `BaseFolder` 中存储多个容器,请将其保留为`true`. 如果你不喜欢不必要的更深层次的文件夹,你可以将它设置为 `false`.
## 文件路径计算
文件系统提供程序在文件夹中组织BLOB文件并实现一些约定. 默认情况下,BLOB文件的完整路径由以下规则确定:
* 它以如上所述配置的 `BasePath` 开始.
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 文件夹.
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 文件夹.
* 如果 `AppendContainerNameToBasePath` 为`true`,则追加容器的名称. 如果容器名称包含 `/`,将导致文件夹嵌套.
* 追加BLOB名称,如果BLOB名称包含 `/` 它创建文件夹. 如果BLOB名称包含 `.` 它将有一个文件扩展名.
## 扩展文件系统提供程序
* `FileSystemBlobProvider` 是实现文件系统存储的主要服务. 你可以从这个类继承并[覆盖](Customizing-Application-Modules-Overriding-Services.md)方法进行自定义.
* `IBlobFilePathCalculator` 服务用于计算文件路径. 默认实现是 `DefaultBlobFilePathCalculator` . 如果你想自定义文件路径计算,可以替换/覆盖它.

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

@ -0,0 +1,306 @@
# BLOB 存储
通常将文件内容存储在应用程序中并根据需要读取这些文件内容. 不仅是文件你可能还需要将各种类型的[BLOB](https://en.wikipedia.org/wiki/Binary_large_object)(大型二进制对象)保存到存储中. 例如你可能要保存用户个人资料图片.
BLOB通常是一个**字节数组**. 有很多地方可以存储BLOB项. 可以选择将其存储在本地文件系统中,共享数据库中或[Azure BLOB存储](https://azure.microsoft.com/zh-cn/services/storage/blobs/)中.
ABP框架为BLOB提供了抽象,并提供了一些可以轻松集成到的预构建存储提供程序. 抽象有一些好处;
* 你可以通过几行配置**轻松的集成**你喜欢的BLOB存储提供程序.
* 你可以**轻松的更改**BLOB存储,而不用改变你的应用程序代码.
* 如果你想创建**可重用的应用程序模块**,无需假设BLOB的存储方式.
ABP BLOG存储系统兼容ABP框架其他功能,如[多租户](Multi-Tenancy.md).
## BLOB 存储提供程序
ABP框架已经有以下存储提供程序的实现;
* [File System](Blob-Storing-File-System.md):将BLOB作为标准文件存储在本地文件系统的文件夹中.
* [Database](Blob-Storing-Database.md): 将BLOB存储在数据库中.
* [Azure](Blob-Storing-Azure.md): 将BLOG存储在 [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中.
以后会实现更多的提供程序,你可以为自己喜欢的提供程序创建[请求](https://github.com/abpframework/abp/issues/new),或者你也可以[自己实现](Blob-Storing-Custom-Provider.md)它并[贡献](Contribution/Index.md)到ABP框架.
可以在**容器系统**的帮助下一起**使用多个提供程序**,其中每个容器可以使用不同的提供程序.
> 除非你**配置存储提供程序**否则BLOB存储系统无法工作. 有关存储提供程序配置请参考链接的文档.
## 安装
[Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring)是定义BLOB存储服务的主要包. 你可以用此包使用BLOB存储系统而不依赖特定存储提供程序.
使用ABP CLI这个包添加到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring` 包的 `.csproj` 文件目录打开命令行.
* 运行 `abp add-package Volo.Abp.BlobStoring` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## IBlobContainer
`IBlobContainer` 是存储和读取BLOB的主要接口. 应用程序可能有多个容器,每个容器都可以单独配置. 有一个**默认容器**可以通过注入 `IBlobContainer` 来简单使用.
**示例: 简单地保存和读取命名BLOB的字节**
````csharp
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IBlobContainer _blobContainer;
public MyService(IBlobContainer blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveBytesAsync(byte[] bytes)
{
await _blobContainer.SaveAsync("my-blob-1", bytes);
}
public async Task<byte[]> GetBytesAsync()
{
return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1");
}
}
}
````
该服务用 `my-blob-1` 名称保存给定的字节,然后以相同的名称获取先前保存的字节.
> 一个BLOB是一个命名对象,**每个BLOB都应该有一个唯一的名称**,它是一个任意的字符串.
`IBlobContainer` 可以处理 `Stream` 和 `byte[]` 对象,在下一节中将详细介绍.
### 保存 BLOB
`SaveAsync` 方法用于保存新的或替换现有的BLOB. 默认情况下,它可以保存 `Stream`,但是有一个快捷的扩展方法来保存字节数组.
`SaveAsync` 有以下参数:
* **name** (string): 唯一的BLOB名称.
* **stream** (Stream) or **byte** (byte[]): 读取BLOB内容或字节数组的流.
* **overrideExisting** (bool): 设置为 `true`,如果BLOB内容已经存在,则替换它. 默认值为 `false`,则抛出 `BlobAlreadyExistsException` 异常.
### 读取/获取 BLOB
* `GetAsync`: 返回给定BLOB名称可用于读取BLOB内容的 `Stream` 对象. 使用后始终要**dispose流**. 如果找不到具有给定名称的BLOB,则抛出异常.
* `GetOrNullAsync`: 与 `GetAsync` 方法相反,如果未找到给定名称的BLOG,则返回 `null`.
* `GetAllBytesAsync`: 返回 `byte[]` 而不是 `Stream`. 如果找不到具有给定名称的BLOB,则抛出异常.
* `GetAllBytesOrNullAsync`: 与 `GetAllBytesAsync` 方法相反,如果未找到给定名称的BLOG,则返回 `null`.
### 删除 BLOB
`DeleteAsync` 使用给定BLOB名称删除BLOB数据. 如果找不到给定的BLOB不会引发任何异常. 相反如果你关心BLOB,它会返回一个 `bool`,表示BLOB实际上是否已删除.
### 其他方法
* `ExistsAsync` 方法简单的检查容器中是否存在具有给定名称的BLOB.
### 关于命名BLOB
没有命名BLOB的规则. BLOB名称只是每个容器(和每个租户-参见"*多租户*"部分)唯一的字符串. 但是不同的存储提供程序可能会按惯例实施某些做法. 例如[文件系统提供程序](Blob-Storing-File-System.md)在BLOB名称中使用目录分隔符 (`/`) 和文件扩展名(如果BLOB名称为 `images/common/x.png` ,则在根容器文件夹下的 `images/common` 文件夹中存储 `x.png`).
## 类型化 IBlobContainer
类型化BLOB容器系统是一种在应用程序中创建和管理**多个容器**的方法;
* **每个容器分别存储**. 这意味着BLOB名称在一个容器中应该是唯一的,两个具有相同名称的BLOB可以存在不同的容器中不会互相影响.
* **每个容器可以单独配置**,因此每个容器可以根据你的配置使用不同的存储提供程序.
要创建类型化容器,需要创建一个简单的用 `BlobContainerName` 属性装饰的类:
````csharp
using Volo.Abp.BlobStoring;
namespace AbpDemo
{
[BlobContainerName("profile-pictures")]
public class ProfilePictureContainer
{
}
}
````
> 如果不使用 `BlobContainerName` attribute,ABP Framework将使用类的全名(带有名称空间),但是始终建议使用稳定的容器名称,即使重命名该类也不会被更改.
创建容器类后,可以为容器类型注入 `IBlobContainer<T>`.
**示例: 用于保存和读取[当前用户](CurrentUser.md)的个人资料图片的[应用服务](Application-Services.md)**
````csharp
[Authorize]
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer<ProfilePictureContainer> _blobContainer;
public ProfileAppService(IBlobContainer<ProfilePictureContainer> blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveProfilePictureAsync(byte[] bytes)
{
var blobName = CurrentUser.GetId().ToString();
await _blobContainer.SaveAsync(blobName, bytes);
}
public async Task<byte[]> GetProfilePictureAsync()
{
var blobName = CurrentUser.GetId().ToString();
return await _blobContainer.GetAllBytesOrNullAsync(blobName);
}
}
````
`IBlobContainer<T>` 有与 `IBlobContainer` 相同的方法.
> 在开发可重复使用的模块时,**始终使用类型化的容器是一个好习惯**,这样最终的应用程序就可以为你的容器配置提供程序,而不会影响其他容器.
### 默认容器
如果不使用泛型参数,直接注入 `IBlobContainer` (如上所述),会得到默认容器. 注入默认容器的另一种方法是使用 `IBlobContainer<DefaultContainer>`,它返回完全相同的容器.
默认容器的名称是 `Default`.
### 命令容器
类型容器只是命名容器的快捷方式. 你可以注入并使用 `IBlobContainerFactory` 来获得一个BLOB容器的名称:
````csharp
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer _blobContainer;
public ProfileAppService(IBlobContainerFactory blobContainerFactory)
{
_blobContainer = blobContainerFactory.Create("profile-pictures");
}
//...
}
````
## IBlobContainerFactory
`IBlobContainerFactory` 是用于创建BLOB容器的服务. 上面提供了一个示例.
**示例: 通过名称创建容器**
````csharp
var blobContainer = blobContainerFactory.Create("profile-pictures");
````
**示例: 通过类型创建容器**
````csharp
var blobContainer = blobContainerFactory.Create<ProfilePictureContainer>();
````
> 通常你不需要使用 `IBlobContainerFactory`, 因为在注入 `IBlobContainer` 或`IBlobContainer<T>` 时会在内部使用它.
## 配置容器
在使用容器之前应先对其进行配置. 最基本的配置是选择一个 **BLOB存储提供程序**(请参阅上面的"*BLOB存储提供程序*"部分).
`AbpBlobStoringOptions` 是用于配置容器的[选项类](Options.md). 你可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中配置选项.
### 配置单个容器
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
//TODO...
});
});
````
这个例子配置 `ProfilePictureContainer`. 你还可以通过容器名称进行配置:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure("profile-pictures", container =>
{
//TODO...
});
});
````
### 配置默认容器
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
//TODO...
});
});
````
> 默认容器有一个特殊情况;如果不为容器指定配置,则**返回到默认容器配置**. 这是一种为所有容器配置默认值并在需要时专门针对特定容器进行配置的好方法.
### 配置所有容器
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
//TODO...
});
});
````
这是配置所有容器的方式.
> 与配置默认容器的主要区别在于, `ConfigureAll` 会覆盖配置,即使它是专门为特定容器配置的.
## 多租户
如果你的应用程序是多租户的,BLOB存储系统可以**与[多租户](Multi-Tenancy.md)无缝协作**. 所有提供程序都将多租户实现为标准功能. 它们将不同租户的**BLOB彼此隔离**,因此它们只能访问自己的BLOB. 这意味着你可以**为不同的租户使用相同的BLOB名称**.
如果应用程序是多租户的,则可能需要单独控制容器的**多租户行为**. 例如你可能希望**禁用特定容器的多租户**,这样容器中的BLOB将对**所有租户可用**. 这是在所有租户之间共享BLOB的一种方法.
**示例: 禁用特定容器的多租户**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
container.IsMultiTenant = false;
});
});
````
> 如果你的应用程序不是多租户的,不用担心,它会正常工作. 你不需要配置 `IsMultiTenant` 选项.
## 扩展BLOB存储系统
大多数时候除了创建定制的BLOB存储提供程序外,你不需要[自定义BLOB存储系统](Blob-Storing-Custom-Provider.md).但是如果需要,你可以替换任何服务(通过[依赖注入](Dependency-Injection.md)). 这里有一些上面没有提到的其他服务,但你可能想知道:
* `IBlobProviderSelector` 用于通过容器名称获取 `IBlobProvider` 实例. 默认实现(`DefaultBlobProviderSelector`)使用配置选择提供程序.
* `IBlobContainerConfigurationProvider` 用于获取给定容器名称的`BlobContainerConfiguration`. 默认实现(`DefaultBlobContainerConfigurationProvider`)从上述 `AbpBlobStoringOptions` 获取配置.
## BLOB 存储 vs 文件管理系统
注意BLOB存储不是一个文件管理系统. 它是一个用于保存,获取和删除命名BLOG的低级别系统. 它不提供目录那样的层次结构,这是典型文件系统所期望的.
如果你想创建文件夹并在文件夹之间移动文件,为文件分配权限并在用户之间共享文件,那么你需要在BLOB存储系统上实现你自己的应用程序.
## 另请参阅
* [创建自定义BLOB存储提供程序](Blob-Storing-Custom-Provider.md)

294
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/Post.md

@ -0,0 +1,294 @@
# ABP框架v2.9已经发布
**ABP框架**和**ABP商业版**2.9已经发布,这是3.0之前的最后一个版本!这篇文章将涵盖本次发布中的**新增内容**.
## ABP框架2.9有哪些新增内容?
你可以中[GitHub的发行说明](https://github.com/abpframework/abp/releases/tag/2.9.0)中看到所有的变更.这篇文章将只包括重要特征/变更.
### 预编译Razor Pages
在之前的版本, 预构建的页面(为[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index))和视图组件是在运行时编译. 现在,它们使用了预编译. 我们测量的应用程序启动时间(尤其是MVC UI)已经减少了50%以上.换句话说,它比之前的版本快**两倍**.速度变化也影响你第一次访问某一个页面时.
这是一个v2.8和v2.9启动应用程序模板的对比结果:
````
### v2.8
2020-06-04 22:59:04.891 +08:00 [INF] Starting web host.
2020-06-04 22:59:07.662 +08:00 [INF] Now listening on: https://localhost:44391
2020-06-04 22:59:17.315 +08:00 [INF] Request finished in 7756.6218ms 200 text/html;
Total: 12.42s
### v2.9
2020-06-04 22:59:13.720 +08:00 [INF] Starting web host.
2020-06-04 22:59:16.639 +08:00 [INF] Now listening on: https://localhost:44369
2020-06-04 22:59:18.957 +08:00 [INF] Request finished in 1780.5461ms 200 text/html;
Total: 5.24s
````
你不用做任何改动就能获得新方法带来的益处.[重写UI页/组件](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface)和之前一样也能正常工作.我们将在v3.0中继续致力于性能上的提升.
### 组织单元系统
[Identity模块](https://docs.abp.io/en/abp/latest/Modules/Identity)现在有了呼声最高的功能: 组织单元!
组织单元系统用来在应用程序中创建分层组织树.这样你可以使用该组织树来授权应用程序中的数据和功能.
文档将很快到来......
### 新的Blob存储包
我们创建了一个新的[Blob存储包](https://www.nuget.org/packages/Volo.Abp.BlobStoring)用来存储任意二进制对象.它一般用于在应用程序中存储文件.这个包提供了一个抽象,因此任何应用程序或[模块](https://docs.abp.io/en/abp/latest/Module-Development-Basics)都能以存储提供器无关的方式来保存和获取文件.
目前实现了两个存储提供器:
* [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem)包, 在本地文件系统中存储对象/文件.
* [Volo.Abp.BlobStoring.Database](https://github.com/abpframework/abp/tree/dev/modules/blob-storing-database)模块, 在数据库中存储对象/文件.目前支持[Entity Framework Core](https://docs.abp.io/en/abp/latest/Entity-Framework-Core)(因此,你可以使用[任何关系数据库](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS)和[MongoDB](https://docs.abp.io/en/abp/latest/MongoDB)).
[Azure BLOB提供器](https://github.com/abpframework/abp/issues/4098)将会在3.0中可用. 你可请求其他的云提供器或在[GitHub库](https://github.com/abpframework/abp/issues/new)上提交你自己的贡献.
Blob存储系统的一个好处是,它允许你创建多个容器(每个容器是一个Blob存储),并为每个容器使用不同的存储提供器.
**示例:使用默认的容器保存和取得一个字节数组**
````csharp
public class MyService : ITransientDependency
{
private readonly IBlobContainer _container;
public MyService(IBlobContainer container)
{
_container = container;
}
public async Task FooAsync()
{
//保存一个BLOB
byte[] bytes = GetBytesFromSomeWhere();
await _container.SaveAsync("my-unique-blob-name", bytes);
//获取一个BLOB
bytes = await _container.GetAllBytesAsync("my-unique-blob-name");
}
}
````
它可以使用`byte[]`和`Stream`对象.
**示例:使用类型化(命名)容器来保存和获取stream**
````csharp
public class MyService : ITransientDependency
{
private readonly IBlobContainer<TestContainer> _container;
public MyService(IBlobContainer<TestContainer> container)
{
_container = container;
}
public async Task FooAsync()
{
//保存一个BLOB
Stream stream = GetStreamFromSomeWhere();
await _container.SaveAsync("my-unique-blob-name", stream);
//获取一个BLOB
stream = await _container.GetAsync("my-unique-blob-name");
}
}
````
`TestContainer`只是一个用来标识容器的空类:
````csharp
[BlobContainerName("test")] //指定容器的名字
public class TestContainer
{
}
````
类型化(命名)容器可被配置为使用不同的存储提供器而不是默认的.在开发可复用的模块时, 始终使用类型化的容器是一个很好的做法,这样最终应用程序可以为这个容器配置提供器,而不影响其他容器.
**示例:为`TestContainer`配置文件系统提供器**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<TestContainer>(configuration =>
{
configuration.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = "C:\\MyStorageFolder";
});
});
});
````
查看[blob存储文档](https://docs.abp.io/en/abp/latest/Blob-Storing)以获取更多的信息.
### Entity Framework Core的Oracle集成包
我们创建了一个[Oralce集成包](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart),这样你就可以为EF Core轻松地切换到Oracle.它已经为框架和预构建的模块进行了测试.
[查看文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle)开始使用Oracle集成包.
### 自动判断数据库提供器
当你用EF Core开发一个**可复用的应用程序模块**时,你通常要将你的模块开发为**DBMS无关**的.但是,不同的DBMS有一些微小的(有时是很大的)区别.现在如何你执行基于DBMS的自定义映射,可以使用`ModelBuilder.IsUsingXXX()`扩展方法:
````csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Phone>(b =>
{
//...
if (modelBuilder.IsUsingPostgreSql()) //检查是否在使用PostgreSQL!
{
b.Property(x => x.Number).HasMaxLength(20);
}
else
{
b.Property(x => x.Number).HasMaxLength(32);
}
});
}
````
除了上面这种的傻傻的例子,你可以任意配置你的映射!
### ABP CLI:翻译命令
`abp translate`是一个新的命令,当你的源代码库中包含多个JSON本地化文件时, 它可用来简化翻译[本地化](https://docs.abp.io/en/abp/latest/Localization)文件,
该命令的主要目的是**翻译ABP框架**的本地化文件(因为[abp库](https://github.com/abpframework/abp)在不同的文件中含有成千上万个本地化文件需要翻译).
非常感谢如果你使用这个命令将框架资源翻译**为你的母语**.
查看[文档](https://docs.abp.io/en/abp/latest/CLI#translate)来学习如何使用它.也可查看[贡献指南](https://docs.abp.io/en/abp/latest/Contribution/Index).
### 新的虚拟文件系统浏览器模块
感谢[@liangshiw](https://github.com/liangshiw)创建并贡献了一个新的模块用来浏览[虚拟文件系统](https://docs.abp.io/en/abp/latest/Virtual-File-System)中的文件.它适用于MVC UI并显示所有应用程序中的虚拟文件.示例截图:
![virtual-file-explorer-1](virtual-file-explorer-1.png)
![virtual-file-explorer-2](virtual-file-explorer-2.png)
[查看文档](https://docs.abp.io/en/abp/latest/Modules/Virtual-File-Explorer)学习如何使用它.
### 示例应用程序:SignalR与分层架构
在分布式/分层架构中实施SignalR是具有挑战性的.我们创建了一个示例应用程序演示如何轻松地使用[SignalR集成](https://docs.abp.io/en/abp/latest/SignalR-Integration)和[分布式事件总线](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus)系统
查看示例解决方案的[源代码](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo).
**一篇正在路上的文章**将深入地解释该解决方案.关注[@abpframework](https://twitter.com/abpframework)的Twitter帐号.
![signalr-tiered-demo](signalr-tiered-demo.png)
*一张文章中的图片,显示了该解决方案的通信图*
### 关于gRPC
我们创建了一个示例应用程序来说明如何在基于ABP的应用程序中创建和使用gRPC端点.
查看GitHub上的[源码](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo).
我们本来计划为所有预构建的应用程序模块创建gRPC端点,但我们发现ASP.NET Core gRPC集成还不够成熟,不支持一些常见的部署场景.所以推迟到了下一个版本(更多内容[查看此评论](https://github.com/abpframework/abp/issues/2882#issuecomment-633080242)).但是,在你的应用程序中使用gRPC是非常标准的. ABP框架与gRPC没有问题.看一下[示例应用程序](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo).
### 其它
* [时区系统](https://github.com/abpframework/abp/pull/3933)为应用程序支持不同的时区.
* 在IIS上支持[虚拟路径部署](https://github.com/abpframework/abp/issues/4089).
* 为Angular UI支持RTL.
其它更新请查看[GitHub发行说明](https://github.com/abpframework/abp/releases/tag/2.9.0).
## ABP商业版2.9有哪些新增内容
与往常一样, 除了ABP框架所有这些功能以外,ABP商业版在本次发布还有一些额外的功能.本节介绍[ABP商业版](https://commercial.abp.io/)在2.9版本中的亮点.
### 组织单元管理UI
我们为组织单元创建了UI,管理ABP商业版[Identity模块](https://commercial.abp.io/modules/Volo.Identity.Pro)的成员和角色:
![organization-units](organization-units.png)
OU管理适用于MVC(Razor Pages)和Angular用户界面.
### 聊天模块Angular UI
我们在前一个版本介绍了新的[聊天模块](https://commercial.abp.io/modules/Volo.Chat), 当时它只有ASP.NET Core MVC / Razor Pages UI. 现在它也包含了一个Angular UI选项.
![abp-chat-module](abp-chat-module.png)
*聊天模块的截图 - 两个用户互相发消息*
### Easy CRM Angular UI
Easy CRM是建立在ABP商业版上的一个示例应用程序, 用来为ABP商业版客户提供一个相对复杂的应用程序.在2.7版本中,我们已经发布了MVC / Razor Pages UI. 这次2.9版中, 我们为Easy CRM应用程序发布了Angular UI.
![easy-crm](easy-crm.png)
*Easy CRM应用程序中"订单详细"的截图.*
查看[Easy CRM文档](https://docs.abp.io/en/commercial/latest/samples/easy-crm)学习如何下载并运行它.
### ABP Suite模块代码生成
[ABP Suite](https://commercial.abp.io/tools/suite)是一个工具,主要功能是用来为一个实体[生成代码](https://docs.abp.io/en/commercial/latest/abp-suite/generating-crud-page), 从数据库到UI层具有完整的CRUD功能.
![suite](suite.png)
*ABP Suite的截图: 定义新实体的属性并且为你生成应用程序代码!*
在本次发布之前它只工作于[应用程序模板](https://docs.abp.io/en/commercial/latest/startup-templates/application/index).现在,它支持为[模块项目](https://docs.abp.io/en/commercial/latest/startup-templates/module/index)生成代码.利用代码生成的威力来创建可复用应用程序模块是很棒的一个做法.
除了这个主要功能,我们在这个版本中向ABP Suite添加了许多细微的改进.
>注意:模块模板代码生成目前处于测试阶段.如果你发现任何bug,请告知我们.
### Lepton主题
[Lepton主题](https://commercial.abp.io/themes)是我们为ABP商业版开发的一个商业主题.
* 与Bootstrap 100%兼容 - 让你不写主题特定的HTML!
* 提供不同类型的风格 - 看一下下图中的Material风格.
* 提供不同类型的布局(侧/顶部菜单,流式/盒式布局...).
* 轻量化,响应式和现代化.
* 还有...它是可升级的,没有成本!你只需更新NuGet / NPM包来获得新的功能.
我们创建了它的专属网站:[http://leptontheme.com/](http://leptontheme.com/)
在这里你可以查看所有的组件, 无需单独的应用程序.
![lepton-theme](lepton-theme.png)
这个网站目前正处于一个非常早期的阶段.我们将创建文档和和改进网站, 来为你的开发提供参考和探索主题的功能.
### 即将推出:文件管理模块
基于新的blob存储系统(上面介绍的),我们已经开始构建一个文件管理模块用来管理(浏览/上传/下载)你应用程序中分层文件系统并在用户与客户之间分享文件.
我们计划在ABP商业版v3.0中发行最初版本,并继续进行后续版本的改进.
## 关于下一个版本:3.0
我们在[v2.8](https://blog.abp.io/abp/ABP-v2.8.0-Releases-%26-Road-Map)和v2.9中增加了许多新的功能.在下一个版本中,我们将完全专注于**文档,性能优化**和其它改进,如bug修复.
长期以来,我们每2周发布一个新功能版本.我们在v3.0以后继续这种方式.但是,v3.0是一个例外,开发周期大概为4周.**v3.0的计划发布日期是2020年7月1日**.
## 彩蛋:文章!
除了开发我们的产品,我们的团队都在不断地撰写各种主题的文章/教程.你可以看一下最新的文章:
* [ASP.NET Core 3.1使用Pub/Sub实现WebHook](https://volosoft.com/blog/ASP.NET-CORE-3.1-Webhook-Implementation-Using-Pub-Sub)
* [ASP.NET Core使用Azure Key Vault](https://volosoft.com/blog/Using-Azure-Key-Vault-with-ASP.NET-Core)

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/abp-chat-module.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/easy-crm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/lepton-theme.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/organization-units.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/signalr-tiered-demo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/suite.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/zh-Hans/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

32
docs/zh-Hans/CLI.md

@ -18,6 +18,37 @@ dotnet tool update -g Volo.Abp.Cli
## Commands ## Commands
这里是所有可用的命令列表:
* **`help`**: 展示ABP CLI的用法帮助信息.
* **`new`**:生成基于ABP的[启动模板](Startup-Templates/Index.md).
* **`update`**:自动更新的ABP解决方案ABP相关的NuGet和NPM包.
* **`add-package`**: 添加ABP包到项目.
* **`add-module`**: 添加[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index)到解决方案.
* **`generate-proxy`**: 生成客户端代理以使用服务器上的HTTP API端点.
* **`switch-to-preview`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本.
* **`switch-to-stable`**: 切换解决方案所有ABP相关包为最新的稳定版本.
* **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程.
* **`login`**: 使用你在[abp.io](https://abp.io/)的用户名和密码在你的计算机上认证.
* **`logout`**: 在你的计算机注销认证.
### help
展示ABP CLI的基本用法:
用法:
````bash
abp help [command-name]
````
示例:
````bash
abp help # Shows a general help.
abp help new # Shows help about the "new" command.
````
### new ### new
生成基于ABP[启动模板](Startup-Templates/Index.md)的新解决方案. 生成基于ABP[启动模板](Startup-Templates/Index.md)的新解决方案.
@ -56,6 +87,7 @@ abp new Acme.BookStore
* `mongodb`: MongoDB. * `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). 其他选项: * `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI). * `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
* **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`). * `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`).

14
docs/zh-Hans/Connection-Strings.md

@ -73,8 +73,18 @@ public class IdentityServerDbContext
启动模板(使用 EF Core ORM) 带有一个数据库和一个 `.EntityFrameworkCore.DbMigrations` 项目,其中包含数据库的迁移文件. 该项目主要定义了一个*YourProjectName*MigrationsDbContext,它调用所有模块的 `Configure...()` 方法,例如 `builder.ConfigurePermissionManagement()`. 启动模板(使用 EF Core ORM) 带有一个数据库和一个 `.EntityFrameworkCore.DbMigrations` 项目,其中包含数据库的迁移文件. 该项目主要定义了一个*YourProjectName*MigrationsDbContext,它调用所有模块的 `Configure...()` 方法,例如 `builder.ConfigurePermissionManagement()`.
一旦要分离模块的数据库,通常需要创建第二个迁移路径. 最简单的方法是创建一个带有 `DbContext` 的 `.EntityFrameworkCore.DbMigrations` 项目副本, 更改为只调用需要存储在第二个数据库中的模块的 `Configure...()` 方法并重新创建迁移. 这时你还需要更改 `.DbMigrator` 应用程序使其兼容第二个数据库,这样每个数据库将有一个单独的迁移DbContext. 一旦要分离模块的数据库,通常需要创建第二个迁移路径. 请参阅[EF Core迁移文档](Entity-Framework-Core-Migrations.md)了解如何为所需模块创建和使用其他数据库.
## 多租户 ## 多租户
参阅 [多租户文档](Multi-Tenancy.md)了解如何为租户使用单独的数据库. 参阅 [多租户文档](Multi-Tenancy.md)了解如何为租户使用单独的数据库.
## 替换连接字符串解析器
ABP定义了 `IConnectionStringResolver`,并在需要连接字符串时使用它. 有两个预构建的实现:
* `DefaultConnectionStringResolver` 根据上面"配置连接字符串"一节中定义的规则,使用 `AbpDbConnectionOptions` 选择连接字符串.
* `MultiTenantConnectionStringResolver` used for multi-tenant applications and tries to get the configured connection string for the current tenant if available. It uses the `ITenantStore` to find the connection strings. It inherits from the `DefaultConnectionStringResolver` and fallbacks to the base logic if no connection string specified for the current tenant.
* `MultiTenantConnectionStringResolver` 用于多租户应用程序,并尝试获取当前租户的已配置连接字符串(如果有). 它使用 `ITenantStore` 查找连接字符串. 它继承了 `DefaultConnectionStringResolver`, 如果没有为当前租户指定连接字符串则回退到基本逻辑.
如果需要自定义逻辑来确定连接字符串,可以实现 `IConnectionStringResolver` 接口(也可以从现有类派生)并使用[依赖注入](Dependency-Injection.md)系统替换现有实现.

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

@ -43,18 +43,20 @@ ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自
除此之外,框架和预构建模块已经本地化了文本.请参阅[Volo.Abp.UI包的本地化文本](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). 除此之外,框架和预构建模块已经本地化了文本.请参阅[Volo.Abp.UI包的本地化文本](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
你可以在[相同文件夹](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi)中创建一个新文件进行翻译. #### 使用 "abp translate" 命令
* 从Github克隆[ABP存储库](https://github.com/abpframework/abp/). 这是推荐的方法,因为它会自动查找所有缺少的文本的特定文化,让你在一个地方翻译.
* 为本地化文本(json)文件(en.json文件同目录下)创建目标语言的新文件.
* 复制en.json文件中的所有文本.
* 翻译文本.
* 在Github上发送拉取请求(Pull request).
* 从Github克隆[ABP存储库](https://github.com/abpframework/abp/).
* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI).
* 在abp仓储的根文件夹为你的语言运行`abp translate -c <culture-name>`命令. 例如对法语使用 `abp translate -c fr`, 检查[文档](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes)找到你所用语言的文化代码.
* 命令会在同一文件夹下创建 `abp-translation.json` 文件, 使用你喜欢的编辑器打开这个文件并填写缺少的文本值.
* 一旦你完成了翻译,使用 `abp translate -a` 命令应用更改到相关的文件.
* 在GitHub上发送PR.
你还可以使用[ABP CLI](CLI.md)的`abp translation`命令来翻译本地化文本. #### 手动翻译
ABP是一个模块化框架. 所以有很多本地化文本资源, 每个模块都有一个. 要查找所有.json文件,可以在克隆存储库后搜索"en.json". 你还可以检查[此列表](Localization-Text-Files.md)以获取本地化文本文件列表. 如果你想更改特定的资源文件,你可以自己找到这个文件进行必要的更改(或为你的语言创建新文件),并在GitHub上发送PR。
### 博客文章和教程 ### 博客文章和教程

168
docs/zh-Hans/CurrentUser.md

@ -1,3 +1,167 @@
# Current User # 当前用户
TODO! 在Web应用程序中检索有关已登录用户的信息是很常见的. 当前用户是与Web应用程序中的当前请求相关的活动用户.
## ICurrentUser
`ICurrentUser` 是主要的服务,用于获取有关当前活动的用户信息.
示例: [注入](Dependency-Injection.md) `ICurrentUser` 到服务中:
````csharp
using System;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly ICurrentUser _currentUser;
public MyService(ICurrentUser currentUser)
{
_currentUser = currentUser;
}
public void Foo()
{
Guid? userId = _currentUser.Id;
}
}
}
````
公共基类已经将此服务作为基本属性注入. 例如你可以直接在[应用服务](Application-Services.md)中使用 `CurrentUser` 属性:
````csharp
using System;
using Volo.Abp.Application.Services;
namespace AbpDemo
{
public class MyAppService : ApplicationService
{
public void Foo()
{
Guid? userId = CurrentUser.Id;
}
}
}
````
### 属性
以下是 `ICurrentUser` 接口的基本属性:
* **IsAuthenticated** 如果当前用户已登录(已认证),则返回 `true`. 如果用户尚未登录,则 `Id` 和 `UserName` 将返回 `null`.
* **Id** (Guid?): 当前用户的Id,如果用户未登录,返回 `null`.
* **UserName** (string): 当前用户的用户名称. 如果用户未登录,返回 `null`.
* **TenantId** (Guid?): 当前用户的租户Id. 对于[多租户](Multi-Tenancy.md) 应用程序很有用. 如果当前用户未分配给租户,返回 `null`.
* **Email** (string): 当前用户的电子邮件地址. 如果当前用户尚未登录或未设置电子邮件地址,返回 `null`.
* **EmailVerified** (bool): 如果当前用户的电子邮件地址已经过验证,返回 `true`.
* **PhoneNumber** (string): 当前用户的电话号码. 如果当前用户尚未登录或未设置电话号码,返回 `null`.
* **PhoneNumberVerified** (bool): 如果当前用户的电话号码已经过验证,返回 `true`.
* **Roles** (string[]): 当前用户的角色. 返回当前用户角色名称的字符串数组.
### Methods
`ICurrentUser` 是在 `ICurrentPrincipalAccessor` 上实现的(请参阅以下部分),并可以处理声明. 实际上所有上述属性都是从当前经过身份验证的用户的声明中检索的.
如果你有自定义声明或获取其他非常见声明类型, `ICurrentUser` 有一些直接使用声明的方法.
* **FindClaim**: 获取给定名称的声明,如果未找到返回 `null`.
* **FindClaims**: 获取具有给定名称的所有声明(允许具有相同名称的多个声明值).
* **GetAllClaims**: 获取所有声明.
* **IsInRole**: 一种检查当前用户是否在指定角色中的简化方法.
除了这些标准方法,还有一些扩展方法:
* **FindClaimValue**: 获取具有给定名称的声明的值,如果未找到返回 `null`. 它有一个泛型重载将值强制转换为特定类型.
* **GetId**: 返回当前用户的 `Id`. 如果当前用户没有登录它会抛出一个异常(而不是返回`null`). 仅在你确定用户已经在你的代码上下文中进行了身份验证时才使用此选项.
### 验证和授权
`ICurrentUser` 的工作方式与用户的身份验证或授权方式无关. 它可以与使用当前主体的任何身份验证系统无缝地配合使用(请参阅下面的部分).
## ICurrentPrincipalAccessor
`ICurrentPrincipalAccessor` 是当需要当前用户的principle时使用的服务(由ABP框架和你的应用程序代码使用).
对于Web应用程序, 它获取当前 `HttpContext` 的 `User` 属性,对于非Web应用程序它将返回 `Thread.CurrentPrincipal`.
> 通常你不需要这种低级别的 `ICurrentPrincipalAccessor` 服务,直接使用上述的 `ICurrentUser` 即可.
### 基本用法
你可以注入 `ICurrentPrincipalAccessor` 并且使用 `Principal` 属性获取当前principal:
````csharp
public class MyService : ITransientDependency
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public MyService(ICurrentPrincipalAccessor currentPrincipalAccessor)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public void Foo()
{
var allClaims = _currentPrincipalAccessor.Principal.Claims.ToList();
//...
}
}
````
### 更改当前Principle
除了某些高级场景外,你不需要设置或更改当前principle. 如果需要可以使用 `ICurrentPrincipalAccessor` 的 `Change` 方法. 它接受一个 `ClaimsPrinciple` 对象并使其成为作用域的"当前"对象.
示例:
````csharp
public class MyAppService : ApplicationService
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public MyAppService(ICurrentPrincipalAccessor currentPrincipalAccessor)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public void Foo()
{
var newPrinciple = new ClaimsPrincipal(
new ClaimsIdentity(
new Claim[]
{
new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString()),
new Claim(AbpClaimTypes.UserName, "john"),
new Claim("MyCustomCliam", "42")
}
)
);
using (_currentPrincipalAccessor.Change(newPrinciple))
{
var userName = CurrentUser.UserName; //returns "john"
//...
}
}
}
````
始终在 `using` 语句中使用 `Change` 方法,在 `using` 范围结束后它将恢复为原始值.
这可以是一种模拟用户登录的应用程序代码范围的方法,但是请尝试谨慎使用它.
## AbpClaimTypes
`AbpClaimTypes` 是一个静态类它定义了标准声明的名称被ABP框架使用.
* `UserName`, `UserId`, `Role` 和 `Email` 属性的默认值是通常[System.Security.Claims.ClaimTypes](https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes)类设置的, 但你可以改变它们.
* 其他属性,如 `EmailVerified`, `PhoneNumber`, `TenantId` ...是由ABP框架通过尽可能遵循标准名称来定义的.
建议使用这个类的属性来代替声明名称的魔术字符串.

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

Loading…
Cancel
Save