Browse Source

Merge pull request #1 from abpframework/dev

Sync the latest
pull/6505/head
Ak.InI 6 years ago
committed by GitHub
parent
commit
c9c54cfa12
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 99
      README.md
  2. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  3. 12
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  4. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
  5. 2
      docs/en/AspNet-Boilerplate-Migration-Guide.md
  6. 31
      docs/en/Authentication/Social-External-Logins.md
  7. 2
      docs/en/Authorization.md
  8. 87
      docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/POST.md
  9. BIN
      docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/abp-contribution-graph-4-years.png
  10. BIN
      docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/ddd-implementation-guide-sample.png
  11. 1
      docs/en/CLI.md
  12. 2
      docs/en/CSRF-Anti-Forgery.md
  13. 936
      docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md
  14. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-create-ui.png
  15. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-detail-ui.png
  16. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-list-ui.png
  17. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/index-title.png
  18. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-all.png
  19. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-create.png
  20. BIN
      docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-upcoming.png
  21. 27
      docs/en/Contribution/Index.md
  22. 10
      docs/en/Data-Access.md
  23. 884
      docs/en/Domain-Driven-Design-Implementation-Guide.md
  24. 14
      docs/en/Domain-Driven-Design.md
  25. 65
      docs/en/Getting-Started-Create-Solution.md
  26. 4
      docs/en/Getting-Started-React-Native.md
  27. 217
      docs/en/Getting-Started-Running-Solution.md
  28. 56
      docs/en/Getting-Started-Setup-Environment.md
  29. 304
      docs/en/Getting-Started.md
  30. 68
      docs/en/Index.md
  31. 19
      docs/en/Migration-Guides/Abp-4_0.md
  32. 5
      docs/en/Migration-Guides/Index.md
  33. 37
      docs/en/Modules/Account.md
  34. 3
      docs/en/Modules/Client-Simulation.md
  35. 3
      docs/en/Modules/Cms-Kit.md
  36. 4
      docs/en/Modules/Docs.md
  37. 50
      docs/en/Modules/Identity.md
  38. 26
      docs/en/Modules/Index.md
  39. 47
      docs/en/Modules/Organization-Units.md
  40. 3
      docs/en/Modules/Users.md
  41. 2
      docs/en/Startup-Templates/Application.md
  42. 3
      docs/en/Swagger.md
  43. 2
      docs/en/UI/AspNetCore/Data-Tables.md
  44. 2
      docs/en/UI/AspNetCore/Navigation-Menu.md
  45. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  46. 8
      docs/en/Upgrading.md
  47. BIN
      docs/en/_resources/ddd-microservice-simple.psd
  48. BIN
      docs/en/_resources/ui-db-options.psd
  49. 594
      docs/en/docs-nav.json
  50. BIN
      docs/en/images/db-options.png
  51. BIN
      docs/en/images/ddd-microservice-simple.png
  52. BIN
      docs/en/images/domain-driven-design-domain-vs-application-logic.png
  53. BIN
      docs/en/images/domain-driven-design-multiple-applications.png
  54. BIN
      docs/en/images/ui-options.png
  55. 32
      docs/zh-Hans/Authentication/Social-External-Logins.md
  56. 2
      docs/zh-Hans/CLI.md
  57. 6
      docs/zh-Hans/Data-Access.md
  58. 36
      docs/zh-Hans/Modules/Account.md
  59. 1
      docs/zh-Hans/Modules/Blogging.md
  60. 3
      docs/zh-Hans/Modules/Client-Simulation.md
  61. 3
      docs/zh-Hans/Modules/Cms-Kit.md
  62. 1
      docs/zh-Hans/Modules/Feature-Management.md
  63. 2
      docs/zh-Hans/Modules/Identity.md
  64. 1
      docs/zh-Hans/Modules/IdentityServer.md
  65. 22
      docs/zh-Hans/Modules/Index.md
  66. 1
      docs/zh-Hans/Modules/Users.md
  67. 456
      docs/zh-Hans/docs-nav.json
  68. 8
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs
  69. 28
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationOptionsFactory.cs
  70. 32
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationOptionsManager.cs
  71. 10
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
  72. 4
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/IAbpRequestLocalizationOptionsProvider.cs
  73. 2
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs
  74. 27
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
  75. 12
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
  76. 8
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  77. 6
      framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj
  78. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  79. 21
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleOptions.cs
  80. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleTypeDefinition.cs
  81. 116
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlerBase.cs
  82. 11
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingConsts.cs
  83. 106
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs
  84. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundler.cs
  85. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs
  86. 23
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/PathHelper.cs
  87. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/IScriptBundler.cs
  88. 34
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/ScriptBundler.cs
  89. 77
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs
  90. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/IStyleBundler.cs
  91. 37
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs
  92. 44
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs
  93. 49
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs
  94. 39
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  95. 128
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs
  96. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  97. 44
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/ReactEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs
  98. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs
  99. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpDynamicOptionsManager.cs
  100. 50
      framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpOptionsFactory.cs

99
README.md

@ -5,19 +5,102 @@
[![MyGet (with prereleases)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds)
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
ABP is an **open source application framework** focused on ASP.NET Core based web application development, but also supports developing other type of applications.
ABP Framework is a complete **infrastructure** based on the **ASP.NET Core** to create **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**.
## Links
## Getting Started
* <a href="https://abp.io/" target="_blank">Official Web Site</a>
- [Getting Started Guide](https://docs.abp.io/en/abp/latest/Getting-Started) is the easiest way to start a new web application with the ABP Framework.
- [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1) is a complete tutorial to develop a full stack web application.
### Quick Start
Install the ABP CLI:
````bash
> dotnet tool install -g Volo.Abp.Cli
````
Create a new solution:
````bash
> abp new BookStore -u mvc -d ef
````
> See the [CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all available options.
### UI Framework Options
<img width="500" src="docs/en/images/ui-options.png">
### Database Provider Options
<img width="500" src="docs/en/images/db-options.png">
## What ABP Provides?
ABP provides a **full stack developer experience**.
### Architecture
<img src="docs/en/images/ddd-microservice-simple.png">
ABP offers a complete, **modular** and **layered** software architecture based on **[Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design)** principles and patterns. It also provides the necessary infrastructure and guiding to [implement this architecture](https://docs.abp.io/en/abp/latest/Domain-Driven-Design-Implementation-Guide).
ABP Framework is suitable for **[microservice solutions](https://docs.abp.io/en/abp/latest/Microservice-Architecture)** as well as monolithic applications.
### Infrastructure
There are a lot of features provided by the ABP Framework to achieve real world scenarios easier, like [Event Bus](https://docs.abp.io/en/abp/latest/Event-Bus), [Background Job System](https://docs.abp.io/en/abp/latest/Background-Jobs), [Audit Logging](https://docs.abp.io/en/abp/latest/Audit-Logging), [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing), [Data Seeding](https://docs.abp.io/en/abp/latest/Data-Seeding), [Data Filtering](https://docs.abp.io/en/abp/latest/Data-Filtering), etc.
### Cross Cutting Concerns
ABP also simplifies (and even automates wherever possible) cross cutting concerns and common non-functional requirements like [Exception Handling](https://docs.abp.io/en/abp/latest/Exception-Handling), [Validation](https://docs.abp.io/en/abp/latest/Validation), [Authorization](https://docs.abp.io/en/abp/latest/Authorization), [Localization](https://docs.abp.io/en/abp/latest/Localization), [Caching](https://docs.abp.io/en/abp/latest/Caching), [Dependency Injection](https://docs.abp.io/en/abp/latest/Dependency-Injection), [Setting Management](https://docs.abp.io/en/abp/latest/Settings), etc.
### Application Modules
ABP is a modular framework and the Application Modules provide **pre-built application functionalities**;
- [**Account**](https://docs.abp.io/en/abp/latest/Modules/Account): Provides UI for the account management and allows user to login/register to the application.
- **[Identity](https://docs.abp.io/en/abp/latest/Modules/Identity)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library.
- [**IdentityServer**](https://docs.abp.io/en/abp/latest/Modules/IdentityServer): Integrates to IdentityServer4.
- [**Tenant Management**](https://docs.abp.io/en/abp/latest/Modules/Tenant-Management): Manages tenants for a [multi-tenant](https://docs.abp.io/en/abp/latest/Multi-Tenancy) (SaaS) application.
See the [Application Modules](https://docs.abp.io/en/abp/latest/Modules/Index) document for all pre-built modules.
### Startup Templates
The [Startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Index) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
## ABP Community
### ABP Community Web Site
The [ABP Community](https://community.abp.io/) is a website to publish **articles** and share **knowledge** about the ABP Framework. You can also create content for the community!
### Blog
Follow the [ABP Blog](https://blog.abp.io/) to learn the latest happenings in the ABP Framework.
### Samples
See the [sample projects](https://docs.abp.io/en/abp/latest/Samples/Index) built with the ABP Framework.
### Want to Contribute?
ABP is a community-driven open source project. See [the contribution guide](https://docs.abp.io/en/abp/latest/Contribution/Index) if you want to be a part of this project.
## Official Links
* <a href="https://abp.io/" target="_blank">Main Web Site</a>
* <a href="https://abp.io/get-started" target="_blank">Get Started</a>
* <a href="https://abp.io/features" target="_blank">Features</a>
* <a href="https://docs.abp.io/" target="_blank">Documentation</a>
* <a href="https://docs.abp.io/en/abp/latest/Samples/Index" target="_blank">Samples</a>
* <a href="https://blog.abp.io/" target="_blank">Blog</a>
* <a href="https://docs.abp.io/" target="_blank">Documentation</a>
* <a href="https://docs.abp.io/en/abp/latest/Samples/Index" target="_blank">Samples</a>
* <a href="https://blog.abp.io/" target="_blank">Blog</a>
* <a href="https://community.abp.io/" target="_blank">Community</a>
* <a href="https://stackoverflow.com/questions/tagged/abp" target="_blank">Stack overflow</a>
* <a href="https://twitter.com/abpframework" target="_blank">Twitter</a>
## Contribution
## Support the ABP Framework
ABP is an open source platform. Check [the contribution guide](docs/en/Contribution/Index.md) if you want to contribute to the project.
Love ABP Framework? **Please give a star** to this repository :star:

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

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

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

@ -45,7 +45,7 @@
"CompleteArchitectureInfo": "Modern architecture to create maintainable software solutions.",
"DomainDrivenDesignBasedLayeringModelExplanation": "Helps you to implement a DDD based layered architecture and build a maintainable code base.",
"DomainDrivenDesignBasedLayeringModelExplanationCont": "Provides startup templates, abstractions, base classes, services, documentation and guides to help you to develop your application based on DDD patterns & principles.",
"MicroserviceCompatibleModelExplanation": "The core framework & pre-build modules are designed the microservice architecture in mind.",
"MicroserviceCompatibleModelExplanation": "The core framework & pre-build modules are designed with microservice architecture in mind.",
"MicroserviceCompatibleModelExplanationCont": "Provides infrastructure, integrations, samples and documentation to implement microservice solutions easier, while it doesn\u2019t bring additional complexity if you want a monolithic application.",
"ModularInfo": "ABP provides a module system that allows you to develop reusable application modules, tie into application lifecycle events, and express dependencies between core parts of your system.",
"PreBuiltModulesThemes": "Pre-Built Modules & Themes",
@ -184,6 +184,14 @@
"ABPCLIExamplesInfo": "<strong>new</strong> command creates a <strong>layered MVC application</strong> with <strong>Entity Framework Core</strong> as the database provider. However, it has additional options. Examples:",
"SeeCliDocumentForMoreInformation": "See the <a href=\"{0}\">ABP CLI document</a> for more options or select the \"Direct Download\" tab above.",
"Optional": "Optional",
"LocalFrameworkRef": "Keep local project reference for the framework packages."
"LocalFrameworkRef": "Keep local project reference for the framework packages.",
"BlobStoring": "BLOB Storing",
"BlobStoringExplanation": "BLOB Storing system provides an abstraction to work with BLOBs. ABP provides some pre-built storage provider integrations (Azure, AWS, File System, Database, etc.) that you can easily use in your applications.",
"TextTemplating": "Text Templating",
"TextTemplatingExplanation": "Text templating is used to dynamically render contents based on a template and a model (a data object). For example, you can use it to create dynamic email contents with a pre-built template.",
"MultipleUIOptions": "Multiple UI Options",
"MultipleDBOptions": "Multiple Database Providers",
"MultipleUIOptionsExplanation": "The core framework is designed as UI independent and can work with any type of UI system, while there are multiple pre-built and integrated options are provided out of the box.",
"MultipleDBOptionsExplanation": "The framework can work with any data source, while the following providers are officially developed and supported;"
}
}

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

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

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

@ -438,7 +438,7 @@ ABP Framework uses and extends ASP.NET Core's [distributed caching abstraction](
### Logging
ASP.NET Boilerplate uses Castle Windsor's [logging facility](http://docs.castleproject.org/Windsor.Logging-Facility.ashx) as an abstraction and supports multiple logging providers including Log4Net (the default one comes with the startup projects) and Serilog. You typically property-inject the logger:
ASP.NET Boilerplate uses Castle Windsor's [logging facility](https://github.com/castleproject/Windsor/blob/master/docs/logging-facility.md) as an abstraction and supports multiple logging providers including Log4Net (the default one comes with the startup projects) and Serilog. You typically property-inject the logger:
````csharp
using Castle.Core.Logging; //1: Import Logging namespace

31
docs/en/Authentication/Social-External-Logins.md

@ -1,32 +1,3 @@
# Social/External Logins
The [Account Module](../Modules/Account.md) has already configured to handle social or external logins out of the box. You can follow the ASP.NET Core documentation to add a social/external login provider to your application.
## Example: Facebook Authentication
Follow the [ASP.NET Core Facebook integration document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to support the Facebook login for your application.
#### Add the NuGet Package
Add the [Microsoft.AspNetCore.Authentication.Facebook](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Facebook) package to your project. Based on your architecture, this can be `.Web`, `.IdentityServer` (for tiered setup) or `.Host` project.
#### Configure the Provider
Use the `.AddFacebook(...)` extension method in the `ConfigureServices` method of your [module](../Module-Development-Basics.md), to configure the client:
````csharp
context.Services.AddAuthentication()
.AddFacebook(facebook =>
{
facebook.AppId = "...";
facebook.AppSecret = "...";
facebook.Scope.Add("email");
facebook.Scope.Add("public_profile");
});
````
> It would be a better practice to use the `appsettings.json` or the ASP.NET Core User Secrets system to store your credentials, instead of a hard-coded value like that. Follow the [Microsoft's document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to learn the user secrets usage.
## Angular UI
Beginning from the v3.1, the Angular UI uses authorization code flow (as a best practice) to authenticate the user by redirecting to the MVC UI login page. So, even if you are using the Angular UI, social/external login integration is same as explained above and it will work out of the box.
> This document has been moved. See the [Account Module](../Modules/Account.md) documentation.

2
docs/en/Authorization.md

@ -406,5 +406,5 @@ This is already done for the startup template integration tests.
## See Also
* [Permission Management Module](Modules/Permission-Management.md)
* [ASP.NET Core MVC / Razor Pages JavaScript Auth API](API/JavaScript-API/Auth.md)
* [ASP.NET Core MVC / Razor Pages JavaScript Auth API](UI/AspNetCore/JavaScript-API/Auth.md)
* [Permission Management in Angular UI](UI/Angular/Permission-Management.md)

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

1
docs/en/CLI.md

@ -107,6 +107,7 @@ abp new Acme.BookStore
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution).
* `--local-framework-ref --abp-path`: Uses local projects references to the ABP framework instead of using the NuGet packages. This can be useful if you download the ABP Framework source code and have a local reference to the framework from your application.
* `--no-random-port`: Uses template's default ports.
### update

2
docs/en/CSRF-Anti-Forgery.md

@ -25,7 +25,7 @@ ABP Framework provides `[AbpValidateAntiForgeryToken]` and `[AbpAutoValidateAnti
ABP Framework also automates the following infrastructure;
* Server side sets a **special cookie**, named `XSRF-TOKEN` by default, that is used make the antiforgery token value available to the browser. This is **done automatically** (by the [application configuration](Application-Configuration.md) endpoint). Nothing to do in the client side.
* Server side sets a **special cookie**, named `XSRF-TOKEN` by default, that is used make the antiforgery token value available to the browser. This is **done automatically** (by the [application configuration](API/Application-Configuration.md) endpoint). Nothing to do in the client side.
* In the client side, it reads the token from the cookie and sends it in the **HTTP header** (named `RequestVerificationToken` by default). This is implemented for all the supported UI types.
* Server side validates the antiforgery token **only for same and cross site requests** made by the browser. It bypasses the validation for non-browser clients.

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

27
docs/en/Contribution/Index.md

@ -2,31 +2,36 @@
ABP is an [open source](https://github.com/abpframework) and community driven project. This guide is aims to help anyone wants to contribute to the project.
## community.abp.io
## ABP Community Website
If you want to write articles or "how to" guides related to the ABP Framework and ASP.NET Core, please submit your article to the [community.abp.io](https://community.abp.io/) web site.
If you want to write **articles** or **how to guides** related to the ABP Framework and ASP.NET Core, please submit your article to the [community.abp.io](https://community.abp.io/) website.
## Code Contribution
You can always send pull requests to the Github repository.
You can always send pull requests to the GitHub repository.
- Clone the [ABP repository](https://github.com/abpframework/abp/) from Github.
- Make the required changes.
- Clone the [ABP repository](https://github.com/abpframework/abp/) from GitHub.
- Build the repository using the `/build/build-all.ps1 -f` for one time.
- Make the necessary changes, including unit/integration tests.
- Send a pull request.
> When you open a solution in Visual Studio, you may need to execute `dotnet restore` in the root folder of the solution for one time, after it is fully opened in the Visual Studio. This is needed since VS can't properly resolves local references to projects out of the solution.
### GitHub Issues
Before making any change, please discuss it on the [Github issues](https://github.com/abpframework/abp/issues). In this way, no other developer will work on the same issue and your PR will have a better chance to be accepted.
### Bug Fixes & Enhancements
#### Bug Fixes & Enhancements
You may want to fix a known bug or work on a planned enhancement. See [the issue list](https://github.com/abpframework/abp/issues) on Github.
### Feature Requests
#### Feature Requests
If you have a feature idea for the framework or modules, [create an issue](https://github.com/abpframework/abp/issues/new) on Github or attend to an existing discussion. Then you can implement it if it's embraced by the community.
## Document Translation
You may want to translate the complete [documentation](https://abp.io/documents/) (including this one) to your mother language. If so, follow these steps:
You may want to translate the complete [documentation](https://docs.abp.io) (including this one) to your mother language. If so, follow these steps:
* Clone the [ABP repository](https://github.com/abpframework/abp/) from Github.
* To add a new language, create a new folder inside the [docs](https://github.com/abpframework/abp/tree/master/docs) folder. Folder names can be "en", "es", "fr", "tr" and so on based on the language (see [all culture codes](https://msdn.microsoft.com/en-us/library/hh441729.aspx)).
@ -35,9 +40,9 @@ You may want to translate the complete [documentation](https://abp.io/documents/
There are some fundamental documents need to be translated before publishing a language on the [ABP documentation web site](https://docs.abp.io):
* Getting Started documents
* Tutorials
* CLI
* Index (Home)
* Getting Started
* Web Application Development Tutorial
A new language is published after these minimum translations have been completed.

10
docs/en/Data-Access.md

@ -1,15 +1,13 @@
# Data Access
## Database Providers
ABP framework was designed as database agnostic. It can work any type of data source by the help of the [repository](Repositories.md) and [unit of work](Unit-Of-Work.md) abstractions. However, currently the following providers are implemented:
ABP framework was designed as database agnostic. It can work any type of data source by the help of the [repository](Repositories.md) and [unit of work](Unit-Of-Work.md) abstractions. Currently, the following providers are implemented as official:
* [Entity Framework Core](Entity-Framework-Core.md) (works with [various DBMS and providers](https://docs.microsoft.com/en-us/ef/core/providers/).)
* [MongoDB](MongoDB.md)
* [Dapper](Dapper.md)
More providers will be added in the future.
## See Also
* [Connection Strings](Connection-Strings.md)
* [Connection Strings](Connection-Strings.md)
* [Data Seeding](Data-Seeding.md)
* [Data Filtering](Data-Filtering.md)

884
docs/en/Domain-Driven-Design-Implementation-Guide.md

File diff suppressed because it is too large

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

@ -10,7 +10,7 @@ ABP framework provides an **infrastructure** to make **Domain Driven Design** ba
> - Basing complex designs on a model of the domain;
> - Initiating a creative collaboration between technical and domain experts to iteratively refine a conceptual model that addresses particular domain problems.
### Layers
## Layers & Building Blocks
ABP follows DDD principles and patterns to achieve a layered application model which consists of four fundamental layers:
@ -19,11 +19,7 @@ ABP follows DDD principles and patterns to achieve a layered application model w
- **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.
DDD mostly interest in the **Domain** and the **Application** layers, rather than the Infrastructure and the Presentation layers.
## Contents
See the following documents to learn what ABP Framework provides to you to implement DDD in your project.
DDD mostly interest in the **Domain** and the **Application** layers, rather than the Infrastructure and the Presentation layers. The following documents explains the **infrastructure** provided by the ABP Framework to implement **Building Blocks** of the DDD:
* **Domain Layer**
* [Entities & Aggregate Roots](Entities.md)
@ -34,4 +30,8 @@ See the following documents to learn what ABP Framework provides to you to imple
* **Application Layer**
* [Application Services](Application-Services.md)
* [Data Transfer Objects (DTOs)](Data-Transfer-Objects.md)
* [Unit of Work](Unit-Of-Work.md)
* [Unit of Work](Unit-Of-Work.md)
## The Ultimate DDD Implementation Guide
See the [Implementing Domain Driven Design](Domain-Driven-Design-Implementation-Guide.md) guide as a **complete reference**. The Guide explains the Domain Driven Design and introduces explicit **rules and examples** to give a deep understanding of the **implementation details**.

65
docs/en/Getting-Started-Create-Solution.md

@ -0,0 +1,65 @@
# Getting Started
````json
//[doc-params]
{
"UI": ["MVC", "Blazor", "NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Create a New Project
Use the `new` command of the ABP CLI to create a new project:
````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
````
*You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.*
{{ if Tiered == "Yes" }}
{{ if UI == "MVC" }}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ else }}
* `--separate-identity-server` argument is used to separate the identity server application from the API host application. If not specified, you will have a single endpoint on the server.
{{ end }}
{{ end }}
> [ABP CLI document](./CLI.md) covers all of the available commands and options.
> Alternatively, you can **create and download** projects from [ABP Framework website](https://abp.io/get-started) by easily selecting the all the options from the page.
### The Solution Structure
The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects. See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
{{ if DB == "Mongo" }}
#### MongoDB Transactions
The [startup template](Startup-templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable the it in the *YourProjectMongoDbModule* class's `ConfigureServices` method:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
```
> Or you can delete that code since `Auto` is already the default behavior.
{{ end }}
## Next Step
* [Running the solution](Getting-Started-Running-Solution.md)

4
docs/en/Getting-Started-React-Native.md

@ -27,13 +27,13 @@ You have multiple options to initiate a new React Native project that works with
### 1. Using ABP CLI
ABP CLI is probably the most convenient and flexible way to initiate an ABP solution with a React Native application. Simply [install the ABP CLI](../../CLI.md) and run the following command in your terminal:
ABP CLI is probably the most convenient and flexible way to initiate an ABP solution with a React Native application. Simply [install the ABP CLI](CLI.md) and run the following command in your terminal:
```shell
abp new MyCompanyName.MyProjectName -csf -u <angular or mvc> -m react-native
```
> To see further options in the CLI, please visit the [CLI manual](../../CLI.md).
> To see further options in the CLI, please visit the [CLI manual](CLI.md).
This command will prepare a solution with an **Angular** or an **MVC** (depends on your choice), a **.NET Core**, and a **React Native** project in it.

217
docs/en/Getting-Started-Running-Solution.md

@ -0,0 +1,217 @@
# Getting Started
````json
//[doc-params]
{
"UI": ["MVC", "Blazor", "NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Create the Database
### Connection String
Check the **connection string** in the `appsettings.json` file under the {{if Tiered == "Yes"}}`.IdentityServer` and `.HttpApi.Host` projects{{else}}{{if UI=="MVC"}}`.Web` project{{else}}`.HttpApi.Host` project{{end}}{{end}}
{{ if DB == "EF" }}
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server** by default. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use any supported DBMS. See [the Entity Framework integration document](Entity-Framework-Core.md) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
### Apply the Migrations
The solution uses the [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). So, you need to apply migrations to create the database. There are two ways of applying the database migrations.
#### Apply Migrations Using the DbMigrator
The solution comes with a `.DbMigrator` console application which applies migrations and also **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
#### Using EF Core Update-Database Command
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations.
{{ if UI == "MVC" }}
Right click to the {{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}} project and select **Set as StartUp project**:
{{ else if UI != "MVC" }}
Right click to the `.HttpApi.Host` project and select **Set as StartUp Project**:
{{ end }}
![set-as-startup-project](images/set-as-startup-project.png)
Open the **Package Manager Console**, select `.EntityFrameworkCore.DbMigrations` project as the **Default Project** and run the `Update-Database` command:
![package-manager-console-update-database](images/package-manager-console-update-database.png)
This will create a new database based on the configured connection string.
> **Using the `.DbMigrator` tool is the suggested way**, because it also seeds the initial data to be able to properly run the web application.
>
> If you just use the `Update-Database` command, you will have an empty database, so you can not login to the application since there is no initial admin user in the database. You can use the `Update-Database` command in development time when you don't need to seed the database. However, using the `.DbMigrator` application is easier and you can always use it to migrate the schema and seed the database.
{{ else if DB == "Mongo" }}
````json
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/BookStore"
}
````
The solution is configured to use **MongoDB** in your local computer, so you need to have a MongoDB server instance up and running or change the connection string to another MongoDB server.
### Seed Initial Data
The solution comes with a `.DbMigrator` console application which **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
{{ end }}
## Run the Application
{{ if UI == "MVC" }}
{{ if Tiered == "Yes" }}
> Tiered solutions use **Redis** as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
1. Ensure that the `.IdentityServer` project is the startup project. Run this application that will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is **just the authentication server**.
2. Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser.
![swagger-ui](images/swagger-ui.png)
This is the HTTP API that is used by the web application.
3. Lastly, ensure that the `.Web` project is the startup project and run the application which will open a **welcome** page in your browser
![mvc-tiered-app-home](images/bookstore-home.png)
Click to the **login** button which will redirect you to the *authentication server* to login to the application:
![bookstore-login](images/bookstore-login.png)
{{ else # Tiered != "Yes" }}
Ensure that the `.Web` project is the startup project. Run the application which will open the **login** page in your browser:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
![bookstore-login](images/bookstore-login.png)
{{ end # Tiered }}
{{ else # UI != "MVC" }}
### Running the HTTP API Host (Server Side)
{{ if Tiered == "Yes" }}
> Tiered solutions use Redis as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
Ensure that the `.IdentityServer` project is the startup project. Run the application which will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is just the authentication server.
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
{{ else # Tiered == "No" }}
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
{{ end # Tiered }}
![swagger-ui](images/swagger-ui.png)
You can see the application APIs and test them here. Get [more info](https://swagger.io/tools/swagger-ui/) about the Swagger UI.
{{ end # UI }}
{{ if UI == "Blazor" }}
### Running the Blazor Application (Client Side)
Ensure that the `.Blazor` project is the startup project and run the application.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
Once the application starts, click to the **Login** link on to header, which redirects you to the authentication server to enter a username and password:
![bookstore-login](images/bookstore-login.png)
{{ else if UI == "NG" }}
### Running the Angular Application (Client Side)
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com/) package manager while `npm install` will also work)
```bash
yarn
```
Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
```bash
yarn start
```
It may take a longer time for the first build. Once it finishes, it opens the Angular UI in your default browser with the [localhost:4200](http://localhost:4200/) address.
![bookstore-login](images/bookstore-login.png)
{{ end }}
Enter **admin** as the username and **1q2w3E*** as the password to login to the application. The application is up and running. You can start developing your application based on this startup template.
## Mobile Development
If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application.
## See Also
* [Web Application Development Tutorial](Tutorials/Part-1.md)
* [Application Startup Template](Startup-Templates/Application.md)

56
docs/en/Getting-Started-Setup-Environment.md

@ -0,0 +1,56 @@
# Getting Started
````json
//[doc-params]
{
"UI": ["MVC", "Blazor", "NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Setup Your Development Environment
First things first! Let's setup your development environment before creating the project.
### Pre-Requirements
The following tools should be installed on your development machine:
* [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) (v16.8+) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). <sup id="a-editor">[1](#f-editor)</sup>
* [.NET Core 5.0+](https://www.microsoft.com/net/download/dotnet-core/)
{{ if UI != "Blazor" }}
* [Node v12 or v14](https://nodejs.org/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> or npm v6+ (already installed with Node)
{{ end }}
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/) (the startup solution uses the Redis as the [distributed cache](Caching.md)).
{{ end }}
<sup id="f-editor"><b>1</b></sup> _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ <sup>[↩](#a-editor)</sup>
{{ if UI != "Blazor" }}
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 works differently and is not supported._ <sup>[↩](#a-yarn)</sup>
{{ end }}
### Install the ABP CLI
[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions. First, you need to install the ABP CLI using the following command:
````shell
dotnet tool install -g Volo.Abp.Cli
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update -g Volo.Abp.Cli
````
## Next Step
* [Creating a new solution](Getting-Started-Create-Solution.md)

304
docs/en/Getting-Started.md

@ -9,306 +9,12 @@
}
````
This tutorial explains how to create a new web application using the [application startup template](Startup-Templates/Application.md).
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Contents
## Setup Your Development Environment
First things first! Let's setup your development environment before creating the first project.
### Pre-Requirements
The following tools should be installed on your development machine:
* [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) (v16.8+) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). <sup id="a-editor">[1](#f-editor)</sup>
* [.NET Core 5.0+](https://www.microsoft.com/net/download/dotnet-core/)
{{ if UI != "Blazor" }}
* [Node v12 or v14](https://nodejs.org/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> or npm v6+ (already installed with Node)
{{ end }}
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/) (the startup solution uses the Redis as the [distributed cache](Caching.md)).
{{ end }}
<sup id="f-editor"><b>1</b></sup> _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ <sup>[↩](#a-editor)</sup>
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 works differently and is not supported._ <sup>[↩](#a-yarn)</sup>
### Install the ABP CLI
[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions.
> ABP CLI is a free & open source tool for the ABP framework.
First, you need to install the ABP CLI using the following command:
````shell
dotnet tool install -g Volo.Abp.Cli
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update -g Volo.Abp.Cli
````
## Create a New Project
Use the `new` command of the ABP CLI to create a new project:
````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
````
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
> Alternatively, you can select the "Direct Download" tab from the [ABP Framework web site](https://abp.io/get-started) to create a new solution.
{{ if Tiered == "Yes" }}
{{ if UI == "MVC" }}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ else }}
* `--separate-identity-server` argument is used to separate the identity server application from the API host application. If not specified, you will have a single endpoint on the server.
{{ end }}
{{ end }}
### ABP CLI Commands & Options
[ABP CLI document](./CLI.md) covers all of the available commands and options for the ABP CLI. This document uses the [application startup template](Startup-Templates/Application.md) to create a new web application. See the [ABP Startup Templates](Startup-Templates/Index.md) document for other templates.
### The Solution Structure
The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects. See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
{{ if DB == "Mongo" }}
#### MongoDB Transactions
The [startup template](Startup-templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable the it in the *YourProjectMongoDbModule* class:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
```
> Or you can delete this code since this is already the default behavior.
{{ end }}
## Create the Database
### Connection String
Check the **connection string** in the `appsettings.json` file under the {{if Tiered == "Yes"}}`.IdentityServer` and `.HttpApi.Host` projects{{else}}{{if UI=="MVC"}}`.Web` project{{else}}`.HttpApi.Host` project{{end}}{{end}}
{{ if DB == "EF" }}
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server** by default. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use any supported DBMS. See [the Entity Framework integration document](Entity-Framework-Core.md) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
### Apply the Migrations
The solution uses the [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). So, you need to apply migrations to create the database. There are two ways of applying the database migrations.
#### Apply Migrations Using the DbMigrator
The solution comes with a `.DbMigrator` console application which applies migrations and also **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
#### Using EF Core Update-Database Command
Ef Core has `Update-Database` command which creates database if necessary and applies pending migrations.
{{ if UI == "MVC" }}
Right click to the {{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}} project and select **Set as StartUp project**:
{{ else if UI != "MVC" }}
Right click to the `.HttpApi.Host` project and select **Set as StartUp Project**:
{{ end }}
![set-as-startup-project](images/set-as-startup-project.png)
Open the **Package Manager Console**, select `.EntityFrameworkCore.DbMigrations` project as the **Default Project** and run the `Update-Database` command:
![package-manager-console-update-database](images/package-manager-console-update-database.png)
This will create a new database based on the configured connection string.
> **Using the `.DbMigrator` tool is the suggested way**, because it also seeds the initial data to be able to properly run the web application.
>
> If you just use the `Update-Database` command, you will have an empty database, so you can not login to the application since there is no initial admin user in the database. You can use the `Update-Database` command in development time when you don't need to seed the database. However, using the `.DbMigrator` application is easier and you can always use it to migrate the schema and seed the database.
{{ else if DB == "Mongo" }}
````json
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/BookStore"
}
````
The solution is configured to use **MongoDB** in your local computer, so you need to have a MongoDB server instance up and running or change the connection string to another MongoDB server.
### Seed Initial Data
The solution comes with a `.DbMigrator` console application which **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![set-as-startup-project](images/set-as-startup-project.png)
Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below:
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
{{ end }}
## Run the Application
{{ if UI == "MVC" }}
{{ if Tiered == "Yes" }}
> Tiered solutions use Redis as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
1. Ensure that the `.IdentityServer` project is the startup project. Run this application that will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is **just the authentication server**.
2. Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser.
![swagger-ui](images/swagger-ui.png)
This is the HTTP API that is used by the web application.
3. Lastly, ensure that the `.Web` project is the startup project and run the application which will open a **welcome** page in your browser
![mvc-tiered-app-home](images/bookstore-home.png)
Click to the **login** button which will redirect you to the *authentication server* to login to the application:
![bookstore-login](images/bookstore-login.png)
{{ else # Tiered != "Yes" }}
Ensure that the `.Web` project is the startup project. Run the application which will open the **login** page in your browser:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
![bookstore-login](images/bookstore-login.png)
{{ end # Tiered }}
{{ else # UI != "MVC" }}
### Running the HTTP API Host (Server Side)
{{ if Tiered == "Yes" }}
> Tiered solutions use Redis as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
Ensure that the `.IdentityServer` project is the startup project. Run the application which will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is just the authentication server.
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
{{ else # Tiered == "No" }}
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
{{ end # Tiered }}
![swagger-ui](images/swagger-ui.png)
You can see the application APIs and test them here. Get [more info](https://swagger.io/tools/swagger-ui/) about the Swagger UI.
> ##### Authorization for the Swagger UI
>
> Most of the HTTP APIs require authentication & authorization. If you want to test authorized APIs, manually go to the `/Account/Login` page, enter `admin` as the username and `1q2w3E*` as the password to login to the application. Then you will be able to execute authorized APIs too.
{{ end # UI }}
{{ if UI == "Blazor" }}
### Running the Blazor Application (Client Side)
Ensure that the `.Blazor` project is the startup project and run the application.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
Once the application starts, click to the **Login** link on to header, which redirects you to the authentication server to enter a username and password:
![bookstore-login](images/bookstore-login.png)
{{ else if UI == "NG" }}
### Running the Angular Application (Client Side)
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com/) package manager while `npm install` will also work)
```bash
yarn
```
Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
```bash
yarn start
```
It may take a longer time for the first build. Once it finishes, it opens the Angular UI in your default browser with the [localhost:4200](http://localhost:4200/) address.
![bookstore-login](images/bookstore-login.png)
{{ end }}
Enter **admin** as the username and **1q2w3E*** as the password to login to the application. The application is up and running. You can start developing your application based on this startup template.
## Mobile Development
If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application.
## Next
This tutorial explains how to **create and run** a new web application using the ABP Framework. Follow the steps below;
* [Web Application Development Tutorial](Tutorials/Part-1.md)
1. [Setup your development environment](Getting-Started-Setup-Environment)
2. [Creating a new solution](Getting-Started-Create-Solution.md)
3. [Running the solution](Getting-Started-Running-Solution.md)

68
docs/en/Index.md

@ -1,31 +1,73 @@
# ABP Documentation
ABP is an **open source application framework** focused on **ASP.NET Core** based **web application development**. It also supports developing other type of applications.
Explore the navigation menu to deep dive in the documentation.
ABP Framework is a complete **infrastructure** based on the **ASP.NET Core** to create **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**.
## Getting Started
The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) guide.
* [Getting Started Guide](Getting-Started.md) is the easiest way to start a new web application with the ABP Framework.
* [Web Application Development Tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack web application.
## Tutorials / Articles
### UI Framework Options
### Web Application Development
<img width="500" src="images/ui-options.png">
[Web application development tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack application using the ABP Framework.
### Database Provider Options
### ABP Community Articles
<img width="500" src="images/db-options.png">
See also the [ABP Community](https://community.abp.io/) articles.
## Exploring the Documentation
## Samples
ABP has a **comprehensive documentation** that not only explains the ABP Framework, but also includes **guides** and **samples** to help you on creating a **maintainable solution** by introducing and discussing common **software development principle and best practices**.
See the [sample projects](Samples/Index.md) built with the ABP Framework.
### Architecture
ABP offers a complete, modular and layered software architecture based on [Domain Driven Design](Domain-Driven-Design.md) principles and patterns. It also provides the necessary infrastructure to implement this architecture.
* See the [Modularity](Module-Development-Basics.md) document to understand the module system.
* [Implementing Domain Driven Design](Domain-Driven-Design-Implementation-Guide.md) document is an ultimate guide for who want to understand and implement the DDD.
* [Microservice Architecture](Microservice-Architecture.md) document explains how ABP helps to create a microservice solution.
### Infrastructure
There are a lot of features provided by the ABP Framework to achieve real world scenarios easier, like [Event Bus](Event-Bus.md), [Background Job System](Background-Jobs.md), [Audit Logging](Audit-Logging.md), [BLOB Storing](Blob-Storing.md), [Data Seeding](Data-Seeding.md), [Data Filtering](Data-Filtering.md).
### Cross Cutting Concerns
ABP also simplifies (and even automates wherever possible) cross cutting concerns and common non-functional requirements like [Exception Handling](Exception-Handling.md), [Validation](Validation.md), [Authorization](Authorization.md), [Localization](Localization.md), [Caching](Caching.md), [Dependency Injection](Dependency-Injection.md), [Setting Management](Settings.md), etc.
### Application Modules
Application Modules provides pre-built application functionalities;
* [**Account**](Modules/Account.md): Provides UI for the account management and allows user to login/register to the application.
* **[Identity](Modules/Identity.md)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library.
* [**IdentityServer**](Modules/IdentityServer.md): Integrates to IdentityServer4.
* [**Tenant Management**](Modules/Tenant-Management.md): Manages tenants for a [multi-tenant](Multi-Tenancy.md) (SaaS) application.
## Source Code
See the [Application Modules](Modules/Index.md) document for all pre-built modules.
### Startup Templates
The [Startup templates](Startup-Templates/Index.md) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
## ABP Community
### The Source Code
ABP is hosted on GitHub. See [the source code](https://github.com/abpframework).
## Want to Contribute?
### ABP Community Web Site
The [ABP Community](https://community.abp.io/) is a website to publish articles and share knowledge about the ABP Framework. You can also create content for the community!
### Blog
Follow the [ABP Blog](https://blog.abp.io/) to learn the latest happenings in the ABP Framework.
### Samples
See the [sample projects](Samples/Index.md) built with the ABP Framework.
### Want to Contribute?
ABP is a community-driven open source project. See [the contribution guide](Contribution/Index.md) if you want to be a part of this project.

19
docs/en/Migration-Guides/Abp-4_0.md

@ -244,13 +244,28 @@ public class MyService : ITransientDependency
public async Task DoItAsync()
{
await _options.SetAsync();
var requiredLength = _options.Value.Password.RequiredLength;
}
}
````
Pre-built modules already handles this. However, if you have used `IdentityOptions` directly in your code, you also need to follow this new pattern.
Please make sure that the injected `IOptions<IdentityOptions>` service and the service consuming it are in the same scope of dependency injection container.
### LDAP module full async
In order to solve the problem of async over sync, `ILdapManager` uses async method instead of sync. And use [`ldap4net`](https://github.com/flamencist/ldap4net) to replace [`Novell.Directory.Ldap.NETStandard`](https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard) package.
### Dynamic external login provider system
You need to change the `WithDynamicOptions` method and pass the `Handler` class of the external login provider.
Use the `goto definition` function in Visual Studio or Rider to check `Handler` in the extension method like `AddGoogle`.
```csharp
- WithDynamicOptions<GoogleOptions>()
+ WithDynamicOptions<GoogleOptions, GoogleHandler>()
````
## ASP.NET Core MVC / Razor Pages UI
@ -262,4 +277,4 @@ See the [Angular UI Migration Guide](Abp-4_0-Angular.md).
## Blazor UI
See the [Blazor UI Migration Guide](Abp-4_0-Blazor.md).
See the [Blazor UI Migration Guide](Abp-4_0-Blazor.md).

5
docs/en/Migration-Guides/Index.md

@ -0,0 +1,5 @@
# ABP Framework Migration Guides
* [3.3.x to 4.0 Migration Guide](Abp-4_0.md)
* [2.9.x to 3.0 Migration Guide](../UI/Angular/Migration-Guide-v3.md)

37
docs/en/Modules/Account.md

@ -1,3 +1,38 @@
# Account Module
TODO
This module provides necessary UI pages/components to make the user login and register to the application.
> This document is incomplete.
## Social/External Logins
The [Account Module](../Modules/Account.md) has already configured to handle social or external logins out of the box. You can follow the ASP.NET Core documentation to add a social/external login provider to your application.
### Example: Facebook Authentication
Follow the [ASP.NET Core Facebook integration document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to support the Facebook login for your application.
#### Add the NuGet Package
Add the [Microsoft.AspNetCore.Authentication.Facebook](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Facebook) package to your project. Based on your architecture, this can be `.Web`, `.IdentityServer` (for tiered setup) or `.Host` project.
#### Configure the Provider
Use the `.AddFacebook(...)` extension method in the `ConfigureServices` method of your [module](../Module-Development-Basics.md), to configure the client:
````csharp
context.Services.AddAuthentication()
.AddFacebook(facebook =>
{
facebook.AppId = "...";
facebook.AppSecret = "...";
facebook.Scope.Add("email");
facebook.Scope.Add("public_profile");
});
````
> It would be a better practice to use the `appsettings.json` or the ASP.NET Core User Secrets system to store your credentials, instead of a hard-coded value like that. Follow the [Microsoft's document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to learn the user secrets usage.
### Other UI Types
Beginning from the v3.1, the [Angular UI](../UI/Angular/Quick-Start.md) uses authorization code flow (as a best practice) to authenticate the user by redirecting to the MVC UI login page. So, even if you are using the Angular UI, social/external login integration is same as explained above and it will work out of the box. As similar, The [Blazor UI](../UI/Blazor/Overall.md) also uses the MVC UI to logic.

3
docs/en/Modules/Client-Simulation.md

@ -0,0 +1,3 @@
# Client Simulation Module
TODO

3
docs/en/Modules/Cms-Kit.md

@ -0,0 +1,3 @@
# CMS Kit Module
TODO

4
docs/en/Modules/Docs.md

@ -446,7 +446,7 @@ As an example you can see ABP Framework documentation:
#### Conditional sections feature (Using Scriban)
Docs module uses [Scriban](<https://github.com/lunet-io/scriban/tree/master/doc> ) for conditionally show or hide some parts of a document. In order to use that feature, you have to create a JSON file as **Parameter document** per every language. It will contain all the key-values, as well as their display names.
Docs module uses [Scriban](https://github.com/lunet-io/scriban/tree/master/doc) for conditionally show or hide some parts of a document. In order to use that feature, you have to create a JSON file as **Parameter document** per every language. It will contain all the key-values, as well as their display names.
For example, [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/blob/master/en/docs-params.json):
@ -537,7 +537,7 @@ Also, **Document_Language_Code** and **Document_Version** keys are pre-defined i
------
**IMPORTANT NOTICE**: Scriban uses "{{" and "}}" for syntax. Therefore, you must use escape blocks if you are going to use those in your document (an Angular document, for example). See [Scriban docs](<https://github.com/lunet-io/scriban/blob/master/doc/language.md#13-escape-block> ) for more information.
**IMPORTANT NOTICE**: Scriban uses "{{" and "}}" for syntax. Therefore, you must use escape blocks if you are going to use those in your document (an Angular document, for example). See [Scriban docs](https://github.com/lunet-io/scriban/blob/master/doc/language.md#13-escape-block) for more information.
### 8- Creating the Navigation Document

50
docs/en/Modules/Identity.md

@ -2,7 +2,7 @@
Identity module is used to manage [organization units](Organization-Units.md), roles, users and their permissions, based on the Microsoft Identity library.
**See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...**
> **See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...**
## Identity Security Log
@ -27,3 +27,51 @@ Configure<AbpSecurityLogOptions>(options =>
options.ApplicationName = "AbpSecurityTest";
});
```
## Organization Unit Management
Organization units (OU) is a part of **Identity Module** and can be used to **hierarchically group users and entities**.
### OrganizationUnit Entity
An OU is represented by the **OrganizationUnit** entity. The fundamental properties of this entity are:
- **TenantId**: Tenant's Id of this OU. Can be null for host OUs.
- **ParentId**: Parent OU's Id. Can be null if this is a root OU.
- **Code**: A hierarchical string code that is unique for a tenant.
- **DisplayName**: Shown name of the OU.
The OrganizationUnit entity's primary key (Id) is a **Guid** type and it derives from the [**FullAuditedAggregateRoot**](../Entities.md) class.
#### Organization Tree
Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. There are some rules for this tree;
- There can be more than one root (where the `ParentId` is `null`).
- There is a limit for the first-level children count of an OU (because of the fixed OU Code unit length explained below).
#### OU Code
OU code is automatically generated and maintained by the OrganizationUnit Manager. It's a string that looks something like this:
"**00001.00042.00005**"
This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code:
- It must be **unique** for a [tenant](../Multi-Tenancy.md).
- All the children of the same OU have codes that **start with the parent OU's code**.
- It's **fixed length** and based on the level of the OU in the tree, as shown in the sample.
- While the OU code is unique, it can be **changeable** if you move an OU.
- You must reference an OU by Id, not Code.
### OrganizationUnit Manager
The **OrganizationUnitManager** class can be [injected](../Dependency-Injection.md) and used to manage OUs. Common use cases are:
- Create, Update or Delete an OU
- Move an OU in the OU tree.
- Getting information about the OU tree and its items.
#### Multi-Tenancy
The `OrganizationUnitManager` is designed to work for a **single tenant** at a time. It works for the **current tenant** by default.

26
docs/en/Modules/Index.md

@ -1,6 +1,6 @@
# Application Modules
ABP is a **modular application framework** which consists of dozens of **nuget packages**. It also provides a complete infrastructure to build your own application modules which may have entities, services, database integration, APIs, UI components and so on.
ABP is a **modular application framework** which consists of dozens of **NuGet & NPM packages**. It also provides a complete infrastructure to build your own application modules which may have entities, services, database integration, APIs, UI components and so on.
There are **two types of modules.** They don't have any structural difference but categorized by functionality and purpose:
@ -9,24 +9,26 @@ There are **two types of modules.** They don't have any structural difference bu
## Open Source Application Modules
There are some **free and open source** application modules developed and maintained by the ABP community:
There are some **free and open source** application modules developed and maintained as a part of the ABP Framework.
* **Account**: Provides UI for the account management and allows user to login/register to the application.
* [**Account**](Account.md): Provides UI for the account management and allows user to login/register to the application.
* [**Audit Logging**](Audit-Logging.md): Persists audit logs to a database.
* **Background Jobs**: Persist background jobs when using the default background job manager.
* **Blogging**: Used to create fancy blogs. ABP's [own blog](https://blog.abp.io/) already using this module.
* [**Docs**](Docs.md): Used to create technical documentation pages. ABP's [own documentation](https://docs.abp.io) already using this module.
* **Feature Management**: Used to persist and manage the [features](../Features.md).
* [**Background Jobs**](Background-Jobs.md): Persist background jobs when using the default background job manager.
* [**Blogging**](Blogging.md): Used to create fancy blogs. ABP's [own blog](https://blog.abp.io/) already using this module.
* [**Client Simulation**](Client-Simulation.md): A simple web UI to stress test HTTP APIs by simulating concurrent clients.
* [**CMS Kit**](Cms-Kit.md): A set of reusable *Content Management System* features.
* [**Docs**](Docs.md): Used to create technical documentation website. ABP's [own documentation](https://docs.abp.io) already using this module.
* [**Feature Management**](Feature-Management.md): Used to persist and manage the [features](../Features.md).
* **[Identity](Identity.md)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library.
* **IdentityServer**: Integrates to IdentityServer4.
* **Permission Management**: Used to persist permissions.
* [**IdentityServer**](IdentityServer.md): Integrates to IdentityServer4.
* [**Permission Management**](Permission-Management.md): Used to persist permissions.
* **[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.
* **Users**: Abstract users, so other modules can depend on this module instead of the Identity module.
* [**Tenant Management**](Tenant-Management.md): Manages tenants for a [multi-tenant](../Multi-Tenancy.md) application.
* [**Users**](Users.md): 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.
## Commercial Application Modules
[ABP Commercial](https://commercial.abp.io/) license provides additional pre-built application modules on top of the ABP framework. See the [module list](https://commercial.abp.io/modules) provided by the ABP Commercial.
[ABP Commercial](https://commercial.abp.io/) license provides **additional pre-built application modules** on top of the ABP framework. See the [module list](https://commercial.abp.io/modules) provided by the ABP Commercial.

47
docs/en/Modules/Organization-Units.md

@ -1,47 +0,0 @@
# Organization Unit Management
Organization units (OU) is a part of **Identity Module** and can be used to **hierarchically group users and entities**.
### OrganizationUnit Entity
An OU is represented by the **OrganizationUnit** entity. The fundamental properties of this entity are:
- **TenantId**: Tenant's Id of this OU. Can be null for host OUs.
- **ParentId**: Parent OU's Id. Can be null if this is a root OU.
- **Code**: A hierarchical string code that is unique for a tenant.
- **DisplayName**: Shown name of the OU.
The OrganizationUnit entity's primary key (Id) is a **Guid** type and it derives from the [**FullAuditedAggregateRoot**](../Entities.md) class.
#### Organization Tree
Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. There are some rules for this tree;
- There can be more than one root (where the `ParentId` is `null`).
- There is a limit for the first-level children count of an OU (because of the fixed OU Code unit length explained below).
#### OU Code
OU code is automatically generated and maintained by the OrganizationUnit Manager. It's a string that looks something like this:
"**00001.00042.00005**"
This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code:
- It must be **unique** for a [tenant](../Multi-Tenancy.md).
- All the children of the same OU have codes that **start with the parent OU's code**.
- It's **fixed length** and based on the level of the OU in the tree, as shown in the sample.
- While the OU code is unique, it can be **changeable** if you move an OU.
- You must reference an OU by Id, not Code.
### OrganizationUnit Manager
The **OrganizationUnitManager** class can be [injected](../Dependency-Injection.md) and used to manage OUs. Common use cases are:
- Create, Update or Delete an OU
- Move an OU in the OU tree.
- Getting information about the OU tree and its items.
#### Multi-Tenancy
The `OrganizationUnitManager` is designed to work for a **single tenant** at a time. It works for the **current tenant** by default.

3
docs/en/Modules/Users.md

@ -0,0 +1,3 @@
# Users Module
TODO

2
docs/en/Startup-Templates/Application.md

@ -97,7 +97,7 @@ A `BookType` enum and a `BookConsts` class (which may have some constant fields
#### .Domain Project
This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../Entities.md), [domain services](../Domain-Services.md), [value types](../Value-Types.md), [repository interfaces](../Repositories.md) and other domain objects.
This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../Entities.md), [domain services](../Domain-Services.md), [value objects](../Value-Objects.md), [repository interfaces](../Repositories.md) and other domain objects.
A `Book` entity, a `BookManager` domain service and an `IBookRepository` interface are good candidates for this project.

3
docs/en/Swagger.md

@ -0,0 +1,3 @@
# Swagger UI Integration
TODO

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

@ -101,7 +101,7 @@ Here, the all configuration options;
DataTables.Net has its own expected data format while getting results of an AJAX call to the server to get the table data. They are especially related how paging and sorting parameters are sent and received. ABP Framework also offers its own conventions for the client-server [AJAX](JavaScript-API/Ajax.md) communication.
The `abp.libs.datatables.createAjax` method (used in the example above) adapts request and response data format and perfectly works with the [Dynamic JavaScript Client Proxy](Dynamic-JavaScript-Client-Proxies.md) system.
The `abp.libs.datatables.createAjax` method (used in the example above) adapts request and response data format and perfectly works with the [Dynamic JavaScript Client Proxy](Dynamic-JavaScript-Proxies.md) system.
This works automatically, so most of the times you don't need to know how it works. See the [DTO document](../../Data-Transfer-Objects.md) if you want to learn more about `IPagedAndSortedResultRequest`, `IPagedResult` and other standard interfaces and base DTO classes those are used in client to server communication.

2
docs/en/UI/AspNetCore/Navigation-Menu.md

@ -109,7 +109,7 @@ There are more options of a menu item (the constructor of the `ApplicationMenuIt
As seen above, a menu contributor contributes to the menu dynamically. So, you can perform any custom logic or get menu items from any source.
One use case is the [authorization](Authorization.md). You typically want to add menu items by checking a permission.
One use case is the [authorization](../../Authorization.md). You typically want to add menu items by checking a permission.
**Example: Check if the current user has a permission**

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

@ -16,7 +16,7 @@ Here, the list of components those are wrapped by the ABP Framework:
* [Badges](Badges.md)
* [Blockquote](Blockquote.md)
* [Borders](Borders.md)
* [Breadcrumb](Breadcrumb.md)
* [Breadcrumb](Breadcrumbs.md)
* [Buttons](Buttons.md)
* [Cards](Cards.md)
* [Carousel](Carousel.md)

8
docs/en/Upgrading.md

@ -27,11 +27,15 @@ When you upgrade to a new version, it is good to check if there is a database sc
If `Add-Migration` generates an empty migration, you can use `Remove-Migration` to delete it before executing the `.DbMigrator`.
## The Blog Posts
## The Blog Posts & Guides
Sometimes we introduce new features/changes that requires to make changes in the startup template. We already implement the changes in the startup template for new applications. However, in some cases you need to manually make some minor changes in your solution.
Whenever you upgrade your solution, it is strongly suggested to check the [ABP BLOG](https://blog.abp.io/?_ga=2.177248992.411298747.1597771169-1910388957.1594128976) to learn the new features and changes coming with the new version. We regularly publish posts and write these kind of changes. If the changes are not trivial, we also provide migration guides.
Whenever you upgrade your solution, it is strongly suggested to check the [ABP BLOG](https://blog.abp.io/) to learn the new features and changes coming with the new version. We regularly publish posts and write these kind of changes.
### Migration Guides
We prepare migration guides if the new version brings breaking changes for existing applications. See the [Migration Guides](Migration-Guides/Index.md) page for all the guides.
## Semantic Versioning & Breaking Changes

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

Binary file not shown.

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

Binary file not shown.

594
docs/en/docs-nav.json

@ -21,6 +21,31 @@
}
]
},
{
"text": "Startup Templates",
"items": [
{
"text": "Overall",
"path": "Startup-Templates/Index.md"
},
{
"text": "Application",
"path": "Startup-Templates/Application.md"
},
{
"text": "Module",
"path": "Startup-Templates/Module.md"
},
{
"text": "Console",
"path": "Startup-Templates/Console.md"
},
{
"text": "WPF",
"path": "Startup-Templates/WPF.md"
}
]
},
{
"text": "Tutorials",
"items": [
@ -68,29 +93,10 @@
"path": "Tutorials/Part-10.md"
}
]
}
]
},
{
"text": "Guides",
"items": [
},
{
"text": "Customizing the Application Modules",
"path": "Customizing-Application-Modules-Guide.md",
"items": [
{
"text": "Extending Entities",
"path": "Customizing-Application-Modules-Extending-Entities.md"
},
{
"text": "Overriding Services",
"path": "Customizing-Application-Modules-Overriding-Services.md"
},
{
"text": "Overriding the User Interface",
"path": "Customizing-Application-Modules-Overriding-User-Interface.md"
}
]
"text": "Community Articles",
"path": "https://community.abp.io/articles"
},
{
"text": "Migrating from the ASP.NET Boilerplate",
@ -98,34 +104,9 @@
}
]
},
{
"text": "CLI",
"path": "CLI.md"
},
{
"text": "Authentication & Security",
"items": [
{
"text": "Social/External Logins",
"path": "Authentication/Social-External-Logins.md"
},
{
"text": "CSRF/XSRF & Anti Forgery",
"path": "CSRF-Anti-Forgery.md"
}
]
},
{
"text": "Fundamentals",
"items": [
{
"text": "Configuration",
"path": "Configuration.md"
},
{
"text": "Options",
"path": "Options.md"
},
{
"text": "Dependency Injection",
"path": "Dependency-Injection.md",
@ -137,16 +118,16 @@
]
},
{
"text": "Virtual File System",
"path": "Virtual-File-System.md"
"text": "Configuration",
"path": "Configuration.md"
},
{
"text": "Localization",
"path": "Localization.md"
"text": "Options",
"path": "Options.md"
},
{
"text": "Exception Handling",
"path": "Exception-Handling.md"
"text": "Authorization",
"path": "Authorization.md"
},
{
"text": "Validation",
@ -159,8 +140,8 @@
]
},
{
"text": "Authorization",
"path": "Authorization.md"
"text": "Localization",
"path": "Localization.md"
},
{
"text": "Caching",
@ -173,83 +154,59 @@
]
},
{
"text": "Logging",
"path": "Logging.md"
},
{
"text": "Audit Logging",
"path": "Audit-Logging.md"
"text": "Exception Handling",
"path": "Exception-Handling.md"
},
{
"text": "Settings",
"path": "Settings.md"
},
{
"text": "Features",
"path": "Features.md"
},
{
"text": "Data Filtering",
"path": "Data-Filtering.md"
"text": "Connection Strings",
"path": "Connection-Strings.md"
},
{
"text": "Object Extensions",
"path": "Object-Extensions.md"
},
{
"text": "Logging",
"path": "Logging.md"
}
]
},
{
"text": "Event Bus",
"text": "Infrastructure",
"items": [
{
"text": "Overall",
"path": "Event-Bus.md"
},
{
"text": "Local Event Bus",
"path": "Local-Event-Bus.md"
"text": "Audit Logging",
"path": "Audit-Logging.md"
},
{
"text": "Distributed Event Bus",
"path": "Distributed-Event-Bus.md",
"text": "Background Jobs",
"path": "Background-Jobs.md",
"items": [
{
"text": "RabbitMQ Integration",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
"text": "Hangfire Integration",
"path": "Background-Jobs-Hangfire.md"
},
{
"text": "Kafka Integration",
"path": "Distributed-Event-Bus-Kafka-Integration.md"
"text": "RabbitMQ Integration",
"path": "Background-Jobs-RabbitMq.md"
},
{
"text": "Rebus Integration",
"path": "Distributed-Event-Bus-Rebus-Integration.md"
"text": "Quartz Integration",
"path": "Background-Jobs-Quartz.md"
}
]
}
]
},
{
"text": "Services",
"items": [
{
"text": "Current User",
"path": "CurrentUser.md"
},
{
"text": "Object to object mapping",
"path": "Object-To-Object-Mapping.md"
},
{
"text": "Email Sending",
"text": "Background Workers",
"path": "Background-Workers.md",
"items": [
{
"text": "Email Sending System",
"path": "Emailing.md"
},
{
"text": "MailKit Integration",
"path": "MailKit.md"
"text": "Quartz Integration",
"path": "Background-Workers-Quartz.md"
}
]
},
@ -296,87 +253,190 @@
]
},
{
"text": "Text Templating",
"path": "Text-Templating.md"
"text": "CSRF/XSRF & Anti Forgery",
"path": "CSRF-Anti-Forgery.md"
},
{
"text": "Current User",
"path": "CurrentUser.md"
},
{
"text": "Data Filtering",
"path": "Data-Filtering.md"
},
{
"text": "Data Seeding",
"path": "Data-Seeding.md"
},
{
"text": "Email Sending",
"items": [
{
"text": "Email Sending System",
"path": "Emailing.md"
},
{
"text": "MailKit Integration",
"path": "MailKit.md"
}
]
},
{
"text": "Event Bus",
"items": [
{
"text": "Overall",
"path": "Event-Bus.md"
},
{
"text": "Local Event Bus",
"path": "Local-Event-Bus.md"
},
{
"text": "Distributed Event Bus",
"path": "Distributed-Event-Bus.md",
"items": [
{
"text": "RabbitMQ Integration",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
},
{
"text": "Kafka Integration",
"path": "Distributed-Event-Bus-Kafka-Integration.md"
},
{
"text": "Rebus Integration",
"path": "Distributed-Event-Bus-Rebus-Integration.md"
}
]
}
]
},
{
"text": "Features",
"path": "Features.md"
},
{
"text": "GUID Generation",
"path": "Guid-Generation.md"
},
{
"text": "Timing",
"path": "Timing.md"
}
]
},
{
"text": "Multi Tenancy",
"path": "Multi-Tenancy.md"
},
{
"text": "Module Development",
"items": [
"text": "Object to Object Mapping",
"path": "Object-To-Object-Mapping.md"
},
{
"text": "Basics",
"path": "Module-Development-Basics.md"
"text": "Text Templating",
"path": "Text-Templating.md"
},
{
"text": "Plug-In Modules"
"text": "Timing",
"path": "Timing.md"
},
{
"text": "Best Practices",
"path": "Best-Practices/Index.md"
"text": "Virtual File System",
"path": "Virtual-File-System.md"
}
]
},
{
"text": "Domain Driven Design",
"text": "Architecture",
"items": [
{
"text": "Overall",
"path": "Domain-Driven-Design.md"
},
{
"text": "Domain Layer",
"text": "Modularity",
"items": [
{
"text": "Entities & Aggregate Roots",
"path": "Entities.md"
},
{
"text": "Value Objects",
"path": "Value-Objects.md"
"text": "Basics",
"path": "Module-Development-Basics.md"
},
{
"text": "Repositories",
"path": "Repositories.md"
"text": "Plug-In Modules"
},
{
"text": "Domain Services",
"path": "Domain-Services.md"
"text": "Customizing the Application Modules",
"path": "Customizing-Application-Modules-Guide.md",
"items": [
{
"text": "Extending Entities",
"path": "Customizing-Application-Modules-Extending-Entities.md"
},
{
"text": "Overriding Services",
"path": "Customizing-Application-Modules-Overriding-Services.md"
},
{
"text": "Overriding the User Interface",
"path": "Customizing-Application-Modules-Overriding-User-Interface.md"
}
]
},
{
"text": "Specifications",
"path": "Specifications.md"
"text": "Best Practices",
"path": "Best-Practices/Index.md"
}
]
},
{
"text": "Application Layer",
"text": "Domain Driven Design",
"items": [
{
"text": "Application Services",
"path": "Application-Services.md"
"text": "Overall",
"path": "Domain-Driven-Design.md"
},
{
"text": "Data Transfer Objects",
"path": "Data-Transfer-Objects.md"
"text": "Domain Layer",
"items": [
{
"text": "Entities & Aggregate Roots",
"path": "Entities.md"
},
{
"text": "Value Objects",
"path": "Value-Objects.md"
},
{
"text": "Repositories",
"path": "Repositories.md"
},
{
"text": "Domain Services",
"path": "Domain-Services.md"
},
{
"text": "Specifications",
"path": "Specifications.md"
}
]
},
{
"text": "Application Layer",
"items": [
{
"text": "Application Services",
"path": "Application-Services.md"
},
{
"text": "Data Transfer Objects",
"path": "Data-Transfer-Objects.md"
},
{
"text": "Unit Of Work",
"path": "Unit-Of-Work.md"
}
]
},
{
"text": "Unit Of Work",
"path": "Unit-Of-Work.md"
"text": "Guide: Implementing DDD",
"path": "Domain-Driven-Design-Implementation-Guide.md"
}
]
},
{
"text": "Multi Tenancy",
"path": "Multi-Tenancy.md"
},
{
"text": "Microservices",
"path": "Microservice-Architecture.md"
}
]
},
@ -625,10 +685,6 @@
{
"text": "Angular",
"items": [
{
"text": "Migration Guide v2.x to v3",
"path": "UI/Angular/Migration-Guide-v3.md"
},
{
"text": "Quick Start",
"path": "UI/Angular/Quick-Start.md"
@ -780,57 +836,44 @@
"path": "Data-Access.md"
},
{
"text": "Connection Strings",
"path": "Connection-Strings.md"
},
{
"text": "Database Providers",
"text": "Entity Framework Core",
"path": "Entity-Framework-Core.md",
"items": [
{
"text": "Entity Framework Core",
"path": "Entity-Framework-Core.md",
"text": "Database Migrations",
"path": "Entity-Framework-Core-Migrations.md"
},
{
"text": "Switch DBMS",
"path": "Entity-Framework-Core-Other-DBMS.md",
"items": [
{
"text": "Database Migrations",
"path": "Entity-Framework-Core-Migrations.md"
},
{
"text": "Switch DBMS",
"path": "Entity-Framework-Core-Other-DBMS.md",
"items": [
{
"text": "To MySQL",
"path": "Entity-Framework-Core-MySQL.md"
},
{
"text": "To PostgreSQL",
"path": "Entity-Framework-Core-PostgreSQL.md"
},
{
"text": "To Oracle",
"path": "Entity-Framework-Core-Oracle.md"
},
{
"text": "To SQLite",
"path": "Entity-Framework-Core-SQLite.md"
}
]
"text": "To MySQL",
"path": "Entity-Framework-Core-MySQL.md"
},
{
"text": "To PostgreSQL",
"path": "Entity-Framework-Core-PostgreSQL.md"
},
{
"text": "To Oracle",
"path": "Entity-Framework-Core-Oracle.md"
},
{
"text": "To SQLite",
"path": "Entity-Framework-Core-SQLite.md"
}
]
},
{
"text": "MongoDB",
"path": "MongoDB.md"
},
{
"text": "Dapper",
"path": "Dapper.md"
}
]
},
{
"text": "Data Seeding",
"path": "Data-Seeding.md"
"text": "MongoDB",
"path": "MongoDB.md"
},
{
"text": "Dapper",
"path": "Dapper.md"
}
]
},
@ -844,115 +887,136 @@
]
},
{
"text": "Background",
"text": "Testing",
"path": "Testing.md"
},
{
"text": "Samples",
"items": [
{
"text": "Background Jobs",
"path": "Background-Jobs.md",
"items": [
{
"text": "Hangfire Integration",
"path": "Background-Jobs-Hangfire.md"
},
{
"text": "RabbitMQ Integration",
"path": "Background-Jobs-RabbitMq.md"
},
{
"text": "Quartz Integration",
"path": "Background-Jobs-Quartz.md"
}
]
"text": "All Samples",
"path": "Samples/Index.md"
},
{
"text": "Background Workers",
"path": "Background-Workers.md",
"items": [
{
"text": "Quartz Integration",
"path": "Background-Workers-Quartz.md"
}
]
"text": "Microservice Demo",
"path": "Samples/Microservice-Demo.md"
}
]
},
{
"text": "Testing",
"path": "Testing.md"
},
{
"text": "Startup Templates",
"text": "Application Modules",
"items": [
{
"text": "Overall",
"path": "Startup-Templates/Index.md"
"path": "Modules/Index.md"
},
{
"text": "Application",
"path": "Startup-Templates/Application.md"
"text": "Account",
"path": "Modules/Account.md"
},
{
"text": "Module",
"path": "Startup-Templates/Module.md"
"text": "Audit Logging",
"path": "Modules/Audit-Logging.md"
},
{
"text": "Console",
"path": "Startup-Templates/Console.md"
"text": "Background Jobs",
"path": "Modules/Background-Jobs.md"
},
{
"text": "WPF",
"path": "Startup-Templates/WPF.md"
"text": "Blogging",
"path": "Modules/Blogging.md"
},
{
"text": "Client Simulation",
"path": "Modules/Client-Simulation.md"
},
{
"text": "CMS Kit",
"path": "Modules/Cms-Kit.md"
},
{
"text": "Docs",
"path": "Modules/Docs.md"
},
{
"text": "Feature Management",
"path": "Modules/Feature-Management.md"
},
{
"text": "Identity",
"path": "Modules/Identity.md"
},
{
"text": "IdentityServer",
"path": "Modules/IdentityServer.md"
},
{
"text": "Permission Management",
"path": "Modules/Permission-Management.md"
},
{
"text": "Setting Management",
"path": "Modules/Setting-Management.md"
},
{
"text": "Tenant Management",
"path": "Modules/Tenant-Management.md"
},
{
"text": "Users",
"path": "Modules/Users.md"
},
{
"text": "Virtual File Explorer",
"path": "Modules/Virtual-File-Explorer.md"
}
]
},
{
"text": "Samples",
"text": "Release Information",
"items": [
{
"text": "All Samples",
"path": "Samples/Index.md"
"text": "Upgrading",
"path": "Upgrading.md"
},
{
"text": "Microservice Demo",
"path": "Samples/Microservice-Demo.md"
"text": "Official Packages",
"path": "https://abp.io/packages"
},
{
"text": "Preview Releases",
"path": "Previews.md"
},
{
"text": "Nightly Builds",
"path": "Nightly-Builds.md"
},
{
"text": "Road Map",
"path": "Road-Map.md"
},
{
"text": "Migration Guides",
"path": "Migration-Guides/Index.md"
}
]
},
{
"text": "Application Modules",
"path": "Modules/Index.md"
},
{
"text": "Microservice Architecture",
"path": "Microservice-Architecture.md"
},
{
"text": "Preview Releases",
"path": "Previews.md"
},
{
"text": "Nightly Builds",
"path": "Nightly-Builds.md"
},
{
"text": "Road Map",
"path": "Road-Map.md"
},
{
"text": "Upgrading",
"path": "Upgrading.md"
"text": "Reference",
"items": [
{
"text": "CLI",
"path": "CLI.md"
},
{
"text": "API Documentation",
"path": "{ApiDocumentationUrl}"
}
]
},
{
"text": "Contribution Guide",
"path": "Contribution/Index.md"
},
{
"text": "API Documentation",
"path": "{ApiDocumentationUrl}"
},
{
"text": "Official Packages",
"path": "https://abp.io/packages"
}
]
}

BIN
docs/en/images/db-options.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/images/ddd-microservice-simple.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/en/images/domain-driven-design-domain-vs-application-logic.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/images/domain-driven-design-multiple-applications.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

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

@ -1,33 +1,3 @@
# 社交/外部登录
[帐户模块](../Modules/Account.md)已配置为开箱即用的处理社交或外部登录. 你可以按照ASP.NET Core文档向你的应用程序添加社交/外部登录提供程序.
## 示例: Facebook 认证
按照[ASP.NET Core Facebook集成文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/facebook-logins)向你应用程序添加Facebook登录.
#### 添加NuGet包
添加[Microsoft.AspNetCore.Authentication.Facebook]包到你的项目. 基于你的架构,可能是 `.Web`,`.IdentityServer`(对于分层启动)或 `.Host` 项目.
#### 配置提供程序
在你模块的 `ConfigureServices` 方法中使用 `.AddFacebook(...)` 扩展方法来配置客户端:
````csharp
context.Services.AddAuthentication()
.AddFacebook(facebook =>
{
facebook.AppId = "...";
facebook.AppSecret = "...";
facebook.Scope.Add("email");
facebook.Scope.Add("public_profile");
});
````
> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密.
## Angular UI
从v3.1开始,Angular UI使用授权码流程(作为最佳实践)通过重定向到MVC UI登录页面来对用户进行身份验证. 因此,即使你使用的是Angular UI,社交/外部登录集成也与上面说明的相同.并且可以开箱即用.
> 文档已经移动其他位置. 参阅[账户模块](../Modules/Account.md)文档.

2
docs/zh-Hans/CLI.md

@ -34,7 +34,7 @@ dotnet tool update -g Volo.Abp.Cli
* **`generate-proxy`**: 生成客户端代理以使用HTTP API端点.
* **`remove-proxy`**: 移除以前生成的客户端代理.
* **`switch-to-preview`**: 切换到ABP框架的最新预览版本。
* **`switch-to-preview`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本.
* **`switch-to-nightly`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本.
* **`switch-to-stable`**: 切换解决方案所有ABP相关包为最新的稳定版本.
* **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程.
* **`login`**: 使用你在[abp.io](https://abp.io/)的用户名和密码在你的计算机上认证.

6
docs/zh-Hans/Data-Access.md

@ -8,4 +8,8 @@ ABP框架被设计为与数据库无关, 它通过[仓储](Repositories.md)和[
* [MongoDB](MongoDB.md)
* [Dapper](Dapper.md)
在以后的版本中可能会添加更多的提供程序.
## 另请参阅
* [连接字符串](Connection-Strings.md)
* [种子数据](Data-Seeding.md)
* [数据过滤](Data-Filtering.md)

36
docs/zh-Hans/Modules/Account.md

@ -0,0 +1,36 @@
# 账户模块
该模块提供必要的UI页面与组件使用户登录和注册到应用程序.
> 文档正在更新
## 社交/外部登录
### 示例: Facebook 认证
按照[ASP.NET Core Facebook集成文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/facebook-logins)向你应用程序添加Facebook登录.
#### 添加NuGet包
添加[Microsoft.AspNetCore.Authentication.Facebook]包到你的项目. 基于你的架构,可能是 `.Web`,`.IdentityServer`(对于分层启动)或 `.Host` 项目.
#### 配置提供程序
在你模块的 `ConfigureServices` 方法中使用 `.AddFacebook(...)` 扩展方法来配置客户端:
````csharp
context.Services.AddAuthentication()
.AddFacebook(facebook =>
{
facebook.AppId = "...";
facebook.AppSecret = "...";
facebook.Scope.Add("email");
facebook.Scope.Add("public_profile");
});
````
> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密.
### Angular UI
从v3.1开始,Angular UI使用授权码流程(作为最佳实践)通过重定向到MVC UI登录页面来对用户进行身份验证. 因此,即使你使用的是Angular UI,社交/外部登录集成也与上面说明的相同.并且可以开箱即用.

1
docs/zh-Hans/Modules/Blogging.md

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

3
docs/zh-Hans/Modules/Client-Simulation.md

@ -0,0 +1,3 @@
# Client Simulation Module
TODO

3
docs/zh-Hans/Modules/Cms-Kit.md

@ -0,0 +1,3 @@
# CMS Kit Module
TODO

1
docs/zh-Hans/Modules/Feature-Management.md

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

2
docs/zh-Hans/Modules/Identity.md

@ -2,7 +2,7 @@
身份模块基于Microsoft Identity库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限.
参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善.
> 参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善.
## Identity安全日志

1
docs/zh-Hans/Modules/IdentityServer.md

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

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

@ -1,6 +1,6 @@
# 应用程序模块
ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages** 组成. 它提供了一个完整的基础设施来构建你自己的应用程序模块,这些模块包含实体,服务,数据库集成,API,UI组件等.
ABP是一个 **模块化的应用程序框架** 由十多个 **NuGet & NPM packages** 组成. 它提供了一个完整的基础设施来构建你自己的应用程序模块,这些模块包含实体,服务,数据库集成,API,UI组件等.
**有两种类型的模块.** 它们没有任何结构上的差异,只是按照功能和目地分类:
@ -9,20 +9,20 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages**
## 开源的应用程序模块
有一些由ABP社区开发和维护的 **开源免费** 的应用程序模块:
有一些由ABP框架开发和维护的 **开源免费** 的应用程序模块:
* **Account**: 提供账户管理UI,并允许用户登录/注册应用程序.
* [**Account**](Account.md): 提供账户管理UI,并允许用户登录/注册应用程序.
* [**Audit Logging**](Audit-Logging.md): 用于将审计日志持久化到数据库.
* **Background Jobs**: 用于在使用默认后台作业管理器时保存后台作业.
* **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块.
* [**Background Jobs**](Background-Jobs.md): 用于在使用默认后台作业管理器时保存后台作业.
* [**Blogging**](Blogging.md): 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块.
* [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块.
* **Feature Management**: 用于保存和管理功能.
* [**Feature Management**](Feature-Management.md): 用于保存和管理功能.
* [**Identity**](Identity.md): 基于Microsoft Identity管理组织单元,角色,用户和他们的权限.
* **Identity Server**: 集成了IdentityServer4.
* **Permission Management**: 用于保存权限.
* **Setting Management**: 用于保存设置.
* **Tenant Management**: 管理[多租户](../Multi-Tenancy.md)应用程序的租户.
* **Users**: 抽象用户, 因此其他模块可以依赖此模块而不是Identity模块.
* [**Identity Server**](IdentityServer.md): 集成了IdentityServer4.
* [**Permission Management**](Permission-Management.md): 用于保存权限.
* [**Setting Management**](Setting-Management.md): 用于保存设置.
* [**Tenant Management**](Tenant-Management.md): 管理[多租户](../Multi-Tenancy.md)应用程序的租户.
* [**Users**](Users.md): 抽象用户, 因此其他模块可以依赖此模块而不是Identity模块.
* [**Virtual File Explorer**](Virtual-File-Explorer.md): 提供简单的UI查看[虚拟文件系统](../Virtual-File-System.md)中的文件.
模块化文档正在编写中. 请参阅[这个仓库](https://github.com/abpframework/abp/tree/master/modules)获取所有模块的源代码.

1
docs/zh-Hans/Modules/Users.md

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

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

@ -21,9 +21,33 @@
}
]
},
{
"text": "启动模板",
"items": [
{
"text": "概述",
"path": "Startup-Templates/Index.md"
},
{
"text": "应用程序",
"path": "Startup-Templates/Application.md"
},
{
"text": "模块",
"path": "Startup-Templates/Module.md"
},
{
"text": "控制台",
"path": "Startup-Templates/Console.md"
},
{
"text": "WPF",
"path": "Startup-Templates/WPF.md"
}
]
},
{
"text": "教程",
"path": "Tutorials/Index.md",
"items": [
{
"text": "应用开发",
@ -41,29 +65,10 @@
"path": "Tutorials/Part-3.md"
}
]
}
]
},
{
"text": "指南",
"items": [
},
{
"text": "自定义应用模块",
"path": "Customizing-Application-Modules-Guide.md",
"items": [
{
"text": "扩展实体",
"path": "Customizing-Application-Modules-Extending-Entities.md"
},
{
"text": "重写服务",
"path": "Customizing-Application-Modules-Overriding-Services.md"
},
{
"text": "重写用户界面",
"path": "Customizing-Application-Modules-Overriding-User-Interface.md"
}
]
"text": "社区文章",
"path": "https://community.abp.io/articles"
},
{
"text": "从ASP.NET Boilerplate迁移",
@ -71,19 +76,6 @@
}
]
},
{
"text": "CLI",
"path": "CLI.md"
},
{
"text": "认证",
"items": [
{
"text": "社交/外部登录",
"path": "Authentication/Social-External-Logins.md"
}
]
},
{
"text": "基础知识",
"items": [
@ -105,10 +97,6 @@
}
]
},
{
"text": "虚拟文件系统",
"path": "Virtual-File-System.md"
},
{
"text": "本地化",
"path": "Localization.md"
@ -139,17 +127,13 @@
"text": "日志",
"path": "Logging.md"
},
{
"text": "审计日志",
"path": "Audit-Logging.md"
},
{
"text": "设置管理",
"path": "Settings.md"
},
{
"text": "数据过滤",
"path": "Data-Filtering.md"
"text": "连接字符串",
"path": "Connection-Strings.md"
},
{
"text": "对象扩展",
@ -158,39 +142,88 @@
]
},
{
"text": "事件总线",
"items": [
"text": "基础设施",
"items":[
{
"text": "概述",
"path": "Event-Bus.md"
},
{
"text": "本地 Event Bus",
"path": "Local-Event-Bus.md"
"text": "后台服务",
"items": [
{
"text": "后台作业",
"path": "Background-Jobs.md",
"items": [
{
"text": "Hangfire 集成",
"path": "Background-Jobs-Hangfire.md"
},
{
"text": "RabbitMQ 集成",
"path": "Background-Jobs-RabbitMq.md"
},
{
"text": "Quartz 集成",
"path": "Background-Jobs-Quartz.md"
}
]
},
{
"text": "后台工作者",
"path": "Background-Workers.md",
"items": [
{
"text": "Quartz 集成",
"path": "Background-Workers-Quartz.md"
}
]
}
]
},
{
"text": "分布式 Event Bus",
"path": "Distributed-Event-Bus.md",
"text": "事件总线",
"items": [
{
"text": "RabbitMQ 集成",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
"text": "概述",
"path": "Event-Bus.md"
},
{
"text": "Kafka 集成",
"path": "Distributed-Event-Bus-Kafka-Integration.md"
"text": "本地 Event Bus",
"path": "Local-Event-Bus.md"
},
{
"text": "Rebus 集成",
"path": "Distributed-Event-Bus-Rebus-Integration.md"
"text": "分布式 Event Bus",
"path": "Distributed-Event-Bus.md",
"items": [
{
"text": "RabbitMQ 集成",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
},
{
"text": "Kafka 集成",
"path": "Distributed-Event-Bus-Kafka-Integration.md"
},
{
"text": "Rebus 集成",
"path": "Distributed-Event-Bus-Rebus-Integration.md"
}
]
}
]
}
]
},
{
"text": "服务",
"items": [
},
{
"text": "种子数据",
"path": "Data-Seeding.md"
},
{
"text": "虚拟文件系统",
"path": "Virtual-File-System.md"
},
{
"text": "审计日志",
"path": "Audit-Logging.md"
},
{
"text": "数据过滤",
"path": "Data-Filtering.md"
},
{
"text": "当前用户",
"path": "CurrentUser.md"
@ -270,22 +303,41 @@
]
},
{
"text": "多租户",
"path": "Multi-Tenancy.md"
},
{
"text": "模块开发",
"items": [
{
"text": "基础",
"path": "Module-Development-Basics.md"
},
"text": "架构",
"items":[
{
"text": "模块插件"
},
{
"text": "最佳实践",
"path": "Best-Practices/Index.md"
"text": "模块化",
"items": [
{
"text": "基础",
"path": "Module-Development-Basics.md"
},
{
"text": "模块插件"
},
{
"text": "自定义应用模块",
"path": "Customizing-Application-Modules-Guide.md",
"items": [
{
"text": "扩展实体",
"path": "Customizing-Application-Modules-Extending-Entities.md"
},
{
"text": "重写服务",
"path": "Customizing-Application-Modules-Overriding-Services.md"
},
{
"text": "重写用户界面",
"path": "Customizing-Application-Modules-Overriding-User-Interface.md"
}
]
},
{
"text": "最佳实践",
"path": "Best-Practices/Index.md"
}
]
}
]
},
@ -335,6 +387,14 @@
"path": "Unit-Of-Work.md"
}
]
},
{
"text": "多租户",
"path": "Multi-Tenancy.md"
},
{
"text": "微服务架构",
"path": "Microservice-Architecture.md"
}
]
},
@ -516,58 +576,45 @@
"path": "Data-Access.md"
},
{
"text": "连接字符串",
"path": "Connection-Strings.md"
},
{
"text": "数据库提供程序",
"text": "Entity Framework Core",
"path": "Entity-Framework-Core.md",
"items": [
{
"text": "Entity Framework Core",
"path": "Entity-Framework-Core.md",
"items": [
"text": "数据库迁移",
"path": "Entity-Framework-Core-Migrations.md"
},
{
"text": "切换DMBS",
"path": "Entity-Framework-Core-Other-DBMS.md",
"items":[
{
"text": "数据库迁移",
"path": "Entity-Framework-Core-Migrations.md"
"text": "到MySql",
"path": "Entity-Framework-Core-MySQL.md"
},
{
"text": "切换DMBS",
"path": "Entity-Framework-Core-Other-DBMS.md",
"items":[
{
"text": "到MySql",
"path": "Entity-Framework-Core-MySQL.md"
},
{
"text": "到PostgreSQL",
"path": "Entity-Framework-Core-PostgreSQL.md"
},
{
"text": " Oracle",
"path": "Entity-Framework-Core-Oracle.md"
},
{
"text": "到SQLite",
"path": "Entity-Framework-Core-SQLite.md"
}
]
"text": "到PostgreSQL",
"path": "Entity-Framework-Core-PostgreSQL.md"
},
{
"text": " Oracle",
"path": "Entity-Framework-Core-Oracle.md"
},
{
"text": "到SQLite",
"path": "Entity-Framework-Core-SQLite.md"
}
]
},
{
"text": "MongoDB",
"path": "MongoDB.md"
},
{
"text": "Dapper",
"path": "Dapper.md"
}
]
},
{
"text": "种子数据",
"path": "Data-Seeding.md"
"text": "MongoDB",
"path": "MongoDB.md"
},
{
"text": "Dapper",
"path": "Dapper.md"
}
]
},
@ -581,111 +628,128 @@
]
},
{
"text": "后台服务",
"text": "示例",
"items": [
{
"text": "后台作业",
"path": "Background-Jobs.md",
"items": [
{
"text": "Hangfire 集成",
"path": "Background-Jobs-Hangfire.md"
},
{
"text": "RabbitMQ 集成",
"path": "Background-Jobs-RabbitMq.md"
},
{
"text": "Quartz 集成",
"path": "Background-Jobs-Quartz.md"
}
]
"text": "所有示例",
"path": "Samples/Index.md"
},
{
"text": "后台工作者",
"path": "Background-Workers.md",
"items": [
{
"text": "Quartz 集成",
"path": "Background-Workers-Quartz.md"
}
]
"text": "微服务示例",
"path": "Samples/Microservice-Demo.md"
}
]
},
},
{
"text": "启动模板",
"items": [
"text": "应用模块",
"items":[
{
"text": "概述",
"path": "Startup-Templates/Index.md"
"path": "Modules/Index.md"
},
{
"text": "应用程序",
"path": "Startup-Templates/Application.md"
"text": "账户",
"path": "Modules/Account.md"
},
{
"text": "模块",
"path": "Startup-Templates/Module.md"
"text": "审计日志",
"path": "Modules/Audit-Logging.md"
},
{
"text": "控制台",
"path": "Startup-Templates/Console.md"
"text": "后台作业",
"path": "Modules/Background-Jobs.md"
},
{
"text": "WPF",
"path": "Startup-Templates/WPF.md"
"text": "博客",
"path": "Modules/Blogging.md"
},
{
"text": "客户端模拟",
"path": "Modules/Client-Simulation.md"
},
{
"text": "CMS Kit",
"path": "Modules/Cms-Kit.md"
},
{
"text": "文档",
"path": "Modules/Docs.md"
},
{
"text": "功能管理",
"path": "Modules/Feature-Management.md"
},
{
"text": "Identity",
"path": "Modules/Identity.md"
},
{
"text": "IdentityServer",
"path": "Modules/IdentityServer.md"
},
{
"text": "权限管理",
"path": "Modules/Permission-Management.md"
},
{
"text": "设置管理",
"path": "Modules/Setting-Management.md"
},
{
"text": "租户管理",
"path": "Modules/Tenant-Management.md"
},
{
"text": "用户",
"path": "Modules/Users.md"
},
{
"text": "虚拟文件浏览器",
"path": "Modules/Virtual-File-Explorer.md"
}
]
},
{
"text": "示例",
"text": "发布信息",
"items": [
{
"text": "所有示例",
"path": "Samples/Index.md"
"text": "升级",
"path": "Upgrading.md"
},
{
"text": "微服务示例",
"path": "Samples/Microservice-Demo.md"
"text": "官方包",
"path": "https://abp.io/packages"
},
{
"text": "预览版本",
"path": "Previews.md"
},
{
"text": "每日构建",
"path": "Nightly-Builds.md"
},
{
"text": "路线图",
"path": "Road-Map.md"
}
]
},
{
"text": "应用模块",
"path": "Modules/Index.md"
},
{
"text": "微服务架构",
"path": "Microservice-Architecture.md"
},
{
"text": "预览版本",
"path": "Previews.md"
},
{
"text": "每日构建",
"path": "Nightly-Builds.md"
},
{
"text": "路线图",
"path": "Road-Map.md"
},
{
"text": "升级",
"path": "Upgrading.md"
"text": "参考",
"items": [
{
"text": "CLI",
"path": "CLI.md"
},
{
"text": "API文档",
"path": "{ApiDocumentationUrl}"
}
]
},
{
"text": "贡献指南",
"path": "Contribution/Index.md"
},
{
"text": "API文档",
"path": "{ApiDocumentationUrl}"
},
{
"text": "官方包",
"path": "https://abp.io/packages"
}
]
}

8
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs

@ -23,15 +23,13 @@ namespace Microsoft.AspNetCore.RequestLocalization
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var middleware = new RequestLocalizationMiddleware(
next,
new OptionsWrapper<RequestLocalizationOptions>(
await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()
),
_loggerFactory
new OptionsWrapper<RequestLocalizationOptions>(await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()), _loggerFactory
);
await middleware.Invoke(context);
}
}
}
}

28
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationOptionsFactory.cs

@ -1,28 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using Volo.Abp.Options;
namespace Microsoft.AspNetCore.RequestLocalization
{
public class AbpRequestLocalizationOptionsFactory : AbpOptionsFactory<RequestLocalizationOptions>
{
private readonly IAbpRequestLocalizationOptionsProvider _abpRequestLocalizationOptionsProvider;
public AbpRequestLocalizationOptionsFactory(
IAbpRequestLocalizationOptionsProvider abpRequestLocalizationOptionsProvider,
IEnumerable<IConfigureOptions<RequestLocalizationOptions>> setups,
IEnumerable<IPostConfigureOptions<RequestLocalizationOptions>> postConfigures)
: base(
setups,
postConfigures)
{
_abpRequestLocalizationOptionsProvider = abpRequestLocalizationOptionsProvider;
}
public override RequestLocalizationOptions Create(string name)
{
return _abpRequestLocalizationOptionsProvider.GetLocalizationOptions();
}
}
}

32
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationOptionsManager.cs

@ -0,0 +1,32 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using Volo.Abp.Options;
namespace Microsoft.AspNetCore.RequestLocalization
{
public class AbpRequestLocalizationOptionsManager : AbpDynamicOptionsManager<RequestLocalizationOptions>
{
private RequestLocalizationOptions _options;
private readonly IAbpRequestLocalizationOptionsProvider _abpRequestLocalizationOptionsProvider;
public AbpRequestLocalizationOptionsManager(
IOptionsFactory<RequestLocalizationOptions> factory,
IAbpRequestLocalizationOptionsProvider abpRequestLocalizationOptionsProvider)
: base(factory)
{
_abpRequestLocalizationOptionsProvider = abpRequestLocalizationOptionsProvider;
}
public override RequestLocalizationOptions Get(string name)
{
return _options ?? base.Get(name);
}
protected override async Task OverrideOptionsAsync(string name, RequestLocalizationOptions options)
{
_options = await _abpRequestLocalizationOptionsProvider.GetLocalizationOptionsAsync();
}
}
}

10
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs

@ -32,16 +32,6 @@ namespace Microsoft.AspNetCore.RequestLocalization
_optionsAction = optionsAction;
}
public RequestLocalizationOptions GetLocalizationOptions()
{
if (_requestLocalizationOptions != null)
{
return _requestLocalizationOptions;
}
return AsyncHelper.RunSync(GetLocalizationOptionsAsync);
}
public async Task<RequestLocalizationOptions> GetLocalizationOptionsAsync()
{
if (_requestLocalizationOptions == null)

4
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/IAbpRequestLocalizationOptionsProvider.cs

@ -8,8 +8,6 @@ namespace Microsoft.AspNetCore.RequestLocalization
{
void InitLocalizationOptions(Action<RequestLocalizationOptions> optionsAction = null);
RequestLocalizationOptions GetLocalizationOptions();
Task<RequestLocalizationOptions> GetLocalizationOptionsAsync();
}
}
}

2
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs

@ -38,7 +38,7 @@ namespace Volo.Abp.AspNetCore
AddAspNetServices(context.Services);
context.Services.AddObjectAccessor<IApplicationBuilder>();
context.Services.Replace(ServiceDescriptor.Transient<IOptionsFactory<RequestLocalizationOptions>, AbpRequestLocalizationOptionsFactory>());
context.Services.AddAbpDynamicOptions<RequestLocalizationOptions, AbpRequestLocalizationOptionsManager>();
}
private static void AddAspNetServices(IServiceCollection services)

27
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs

@ -11,23 +11,14 @@ namespace Volo.Abp.BackgroundJobs.Hangfire
public virtual Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null)
{
if (!delay.HasValue)
{
return Task.FromResult(
BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.Execute(args)
)
);
}
else
{
return Task.FromResult(
BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.Execute(args),
delay.Value
)
);
}
return Task.FromResult(delay.HasValue
? BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.ExecuteAsync(args),
delay.Value
)
: BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.ExecuteAsync(args)
));
}
}
}
}

12
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs

@ -1,8 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs.Hangfire
{
@ -22,7 +20,7 @@ namespace Volo.Abp.BackgroundJobs.Hangfire
Options = options.Value;
}
public void Execute(TArgs args)
public async Task ExecuteAsync(TArgs args)
{
if (!Options.IsJobExecutionEnabled)
{
@ -39,8 +37,8 @@ namespace Volo.Abp.BackgroundJobs.Hangfire
{
var jobType = Options.GetJob(typeof(TArgs)).JobType;
var context = new JobExecutionContext(scope.ServiceProvider, jobType, args);
AsyncHelper.RunSync(() => JobExecuter.ExecuteAsync(context));
await JobExecuter.ExecuteAsync(context);
}
}
}
}
}

8
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs

@ -21,7 +21,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
protected BackgroundJobConfiguration JobConfiguration { get; }
protected JobQueueConfiguration QueueConfiguration { get; }
protected IChannelAccessor ChannelAccessor { get; private set; }
protected EventingBasicConsumer Consumer { get; private set; }
protected AsyncEventingBasicConsumer Consumer { get; private set; }
public ILogger<JobQueue<TArgs>> Logger { get; set; }
@ -135,7 +135,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
if (AbpBackgroundJobOptions.IsJobExecutionEnabled)
{
Consumer = new EventingBasicConsumer(ChannelAccessor.Channel);
Consumer = new AsyncEventingBasicConsumer(ChannelAccessor.Channel);
Consumer.Received += MessageReceived;
//TODO: What BasicConsume returns?
@ -173,7 +173,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
return properties;
}
protected virtual void MessageReceived(object sender, BasicDeliverEventArgs ea)
protected virtual async Task MessageReceived(object sender, BasicDeliverEventArgs ea)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
@ -185,7 +185,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
try
{
AsyncHelper.RunSync(() => JobExecuter.ExecuteAsync(context));
await JobExecuter.ExecuteAsync(context);
ChannelAccessor.Channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
}
catch (BackgroundJobExecutionException)

6
framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj

@ -12,9 +12,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blazorise" Version="0.9.3-preview2" />
<PackageReference Include="Blazorise.DataGrid" Version="0.9.3-preview2" />
<PackageReference Include="Blazorise.Snackbar" Version="0.9.3-preview2" />
<PackageReference Include="Blazorise" Version="0.9.3-preview3" />
<PackageReference Include="Blazorise.DataGrid" Version="0.9.3-preview3" />
<PackageReference Include="Blazorise.Snackbar" Version="0.9.3-preview3" />
</ItemGroup>
</Project>

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs

@ -3,6 +3,7 @@ using Volo.Abp.Cli.Commands;
using Volo.Abp.Domain;
using Volo.Abp.IdentityModel;
using Volo.Abp.Json;
using Volo.Abp.Minify;
using Volo.Abp.Modularity;
namespace Volo.Abp.Cli
@ -10,7 +11,8 @@ namespace Volo.Abp.Cli
[DependsOn(
typeof(AbpDddDomainModule),
typeof(AbpJsonModule),
typeof(AbpIdentityModelModule)
typeof(AbpIdentityModelModule),
typeof(AbpMinifyModule)
)]
public class AbpCliCoreModule : AbpModule
{

21
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleOptions.cs

@ -0,0 +1,21 @@
using JetBrains.Annotations;
namespace Volo.Abp.Cli.Bundling
{
public class BundleOptions
{
[NotNull]
public string Directory { get; set; }
[NotNull]
public string BundleName { get; set; }
[NotNull]
public string FrameworkVersion { get; set; }
[NotNull]
public string ProjectFileName { get; set; }
public bool Minify { get; set; }
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleTypeDefinition.cs

@ -6,6 +6,6 @@ namespace Volo.Abp.Cli.Bundling
{
public int Level { get; set; }
public Type BundleContributerType { get; set; }
public Type BundleContributorType { get; set; }
}
}

116
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlerBase.cs

@ -0,0 +1,116 @@
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Volo.Abp.Bundling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify;
using Volo.Abp.Minify.NUglify;
namespace Volo.Abp.Cli.Bundling
{
public abstract class BundlerBase : IBundler, ITransientDependency
{
private static string[] _minFileSuffixes = { "min", "prod" };
protected IMinifier Minifier { get; }
public ILogger<BundlerBase> Logger { get; set; }
public abstract string FileExtension { get; }
public abstract string GenerateDefinition(string bundleFilePath);
protected BundlerBase(IMinifier minifier)
{
Minifier = minifier;
}
public string Bundle(BundleOptions options, BundleContext context)
{
var bundleFilePath = Path.Combine(PathHelper.GetWwwRootPath(options.Directory), $"{options.BundleName}{FileExtension}");
var bundledContent = BundleFiles(options, context);
File.WriteAllText(bundleFilePath, bundledContent);
return GenerateDefinition(bundleFilePath);
}
private bool IsMinFile(string fileName)
{
foreach (var suffix in _minFileSuffixes)
{
if (fileName.EndsWith($".{suffix}{FileExtension}", StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}
private string BundleFiles(BundleOptions options, BundleContext context)
{
var staticAssetsFilePath = Path.Combine(options.Directory, "bin", "Debug", options.FrameworkVersion, $"{options.ProjectFileName}.StaticWebAssets.xml");
if (!File.Exists(staticAssetsFilePath))
{
throw new BundlingException("Unable to find static web assets file. You need to build the project to generate static web assets file.");
}
var staticAssetsDefinitions = new XmlDocument();
staticAssetsDefinitions.Load(staticAssetsFilePath);
var builder = new StringBuilder();
foreach (var definition in context.BundleDefinitions)
{
string content;
if (definition.Source.StartsWith("_content"))
{
var pathFragments = definition.Source.Split('/').ToList();
var basePath = $"{pathFragments[0]}/{pathFragments[1]}";
var path = staticAssetsDefinitions.SelectSingleNode($"//ContentRoot[@BasePath='{basePath}']").Attributes["Path"].Value;
var absolutePath = definition.Source.Replace(basePath, path);
content = GetFileContent(absolutePath, options.Minify);
}
else if (definition.Source.StartsWith("_framework"))
{
var slashIndex = definition.Source.IndexOf('/');
var fileName = definition.Source.Substring(slashIndex + 1, definition.Source.Length - slashIndex - 1);
var filePath = Path.Combine(PathHelper.GetFrameworkFolderPath(options.Directory, options.FrameworkVersion), fileName);
content = GetFileContent(filePath, false);
}
else
{
var filePath = Path.Combine(PathHelper.GetWwwRootPath(options.Directory), definition.Source);
content = GetFileContent(filePath, options.Minify);
}
content = ProcessBeforeAddingToTheBundle(definition.Source, Path.Combine(options.Directory, "wwwroot"), content);
builder.AppendLine(content);
}
return builder.ToString();
}
private string GetFileContent(string filePath, bool minify)
{
var content = File.ReadAllText(filePath);
if (minify && !IsMinFile(filePath))
{
try
{
content = Minifier.Minify(content);
}
catch (NUglifyException ex)
{
Logger.LogWarning($"Unable to minify the file: {Path.GetFileName(filePath)}. Adding file to the bundle without minification.", ex);
}
}
return content;
}
protected virtual string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory, string fileContent)
{
return fileContent;
}
}
}

11
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingConsts.cs

@ -0,0 +1,11 @@
namespace Volo.Abp.Cli.Bundling
{
internal static class BundlingConsts
{
internal const string StylePlaceholderStart = "<!--ABP:Styles-->";
internal const string StylePlaceholderEnd = "<!--/ABP:Styles-->";
internal const string ScriptPlaceholderStart = "<!--ABP:Scripts-->";
internal const string ScriptPlaceholderEnd = "<!--/ABP:Scripts-->";
internal const string SupportedWebAssemblyProjectType = "Microsoft.NET.Sdk.BlazorWebAssembly";
}
}

106
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs

@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -8,22 +9,25 @@ using System.Threading.Tasks;
using System.Xml;
using Volo.Abp.Bundling;
using Volo.Abp.Cli.Build;
using Volo.Abp.Cli.Bundling.Scripts;
using Volo.Abp.Cli.Bundling.Styles;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Scripts;
using Volo.Abp.Minify.Styles;
using Volo.Abp.Modularity;
namespace Volo.Abp.Cli.Bundling
{
public class BundlingService : IBundlingService, ITransientDependency
{
const string StylePlaceholderStart = "<!--ABP:Styles-->";
const string StylePlaceholderEnd = "<!--/ABP:Styles-->";
const string ScriptPlaceholderStart = "<!--ABP:Scripts-->";
const string ScriptPlaceholderEnd = "<!--/ABP:Scripts-->";
const string SupportedWebAssemblyProjectType = "Microsoft.NET.Sdk.BlazorWebAssembly";
public IDotNetProjectBuilder DotNetProjectBuilder { get; set; }
public IJavascriptMinifier JsMinifier { get; set; }
public ICssMinifier CssMinifier { get; set; }
public ILogger<BundlingService> Logger { get; set; }
public IScriptBundler ScriptBundler { get; set; }
public IStyleBundler StyleBundler { get; set; }
public async Task BundleAsync(string directory, bool forceBuild)
public async Task BundleAsync(string directory, bool forceBuild, bool bundle, bool minify, string bundleName)
{
var projectFiles = Directory.GetFiles(directory, "*.csproj");
if (!projectFiles.Any())
@ -44,17 +48,38 @@ namespace Volo.Abp.Cli.Bundling
}
var frameworkVersion = GetTargetFrameworkVersion(projectFilePath);
var assemblyFilePath = GetAssemblyFilePath(directory, frameworkVersion, Path.GetFileNameWithoutExtension(projectFilePath));
var projectName = Path.GetFileNameWithoutExtension(projectFilePath);
var assemblyFilePath = PathHelper.GetAssemblyFilePath(directory, frameworkVersion, projectName);
var startupModule = GetStartupModule(assemblyFilePath);
var bundleDefinitions = new List<BundleTypeDefinition>();
FindBundleContributersRecursively(startupModule, 0, bundleDefinitions);
FindBundleContributorsRecursively(startupModule, 0, bundleDefinitions);
bundleDefinitions = bundleDefinitions.OrderByDescending(t => t.Level).ToList();
var styleContext = GetStyleContext(bundleDefinitions);
var scriptContext = GetScriptContext(bundleDefinitions);
var styleDefinitions = GenerateStyleDefinitions(styleContext);
var scriptDefinitions = GenerateScriptDefinitions(scriptContext);
string styleDefinitions;
string scriptDefinitions;
if (bundle || minify)
{
var options = new BundleOptions
{
Directory = directory,
FrameworkVersion = frameworkVersion,
ProjectFileName = projectName,
BundleName = bundleName,
Minify = minify
};
styleDefinitions = StyleBundler.Bundle(options, styleContext);
scriptDefinitions = ScriptBundler.Bundle(options, scriptContext);
}
else
{
styleDefinitions = GenerateStyleDefinitions(styleContext);
scriptDefinitions = GenerateScriptDefinitions(scriptContext);
}
await UpdateDependenciesInHtmlFileAsync(directory, styleDefinitions, scriptDefinitions);
}
@ -65,8 +90,8 @@ namespace Volo.Abp.Cli.Bundling
foreach (var bundleDefinition in bundleDefinitions)
{
var contributer = CreateContributerInstance(bundleDefinition.BundleContributerType);
contributer.AddScripts(scriptContext);
var contributor = CreateContributorInstance(bundleDefinition.BundleContributorType);
contributor.AddScripts(scriptContext);
}
scriptContext.Add("_framework/blazor.webassembly.js");
@ -79,8 +104,8 @@ namespace Volo.Abp.Cli.Bundling
foreach (var bundleDefinition in bundleDefinitions)
{
var contributer = CreateContributerInstance(bundleDefinition.BundleContributerType);
contributer.AddStyles(styleContext);
var contributor = CreateContributorInstance(bundleDefinition.BundleContributorType);
contributor.AddStyles(styleContext);
}
return styleContext;
@ -88,7 +113,7 @@ namespace Volo.Abp.Cli.Bundling
private async Task UpdateDependenciesInHtmlFileAsync(string directory, string styleDefinitions, string scriptDefinitions)
{
var htmlFilePath = Path.Combine(directory, "wwwroot", "index.html");
var htmlFilePath = Path.Combine(PathHelper.GetWwwRootPath(directory), "index.html");
if (!File.Exists(htmlFilePath))
{
throw new BundlingException($"index.html file could not be found in the following path:{htmlFilePath}");
@ -102,8 +127,8 @@ namespace Volo.Abp.Cli.Bundling
content = await reader.ReadToEndAsync();
}
content = UpdatePlaceholders(content, StylePlaceholderStart, StylePlaceholderEnd, styleDefinitions);
content = UpdatePlaceholders(content, ScriptPlaceholderStart, ScriptPlaceholderEnd, scriptDefinitions);
content = UpdatePlaceholders(content, BundlingConsts.StylePlaceholderStart, BundlingConsts.StylePlaceholderEnd, styleDefinitions);
content = UpdatePlaceholders(content, BundlingConsts.ScriptPlaceholderStart, BundlingConsts.ScriptPlaceholderEnd, scriptDefinitions);
using (var writer = new StreamWriter(htmlFilePath, false, fileEncoding))
{
@ -124,7 +149,7 @@ namespace Volo.Abp.Cli.Bundling
{
var builder = new StringBuilder();
builder.AppendLine($"{StylePlaceholderStart}");
builder.AppendLine($"{BundlingConsts.StylePlaceholderStart}");
foreach (var style in context.BundleDefinitions)
{
@ -138,7 +163,7 @@ namespace Volo.Abp.Cli.Bundling
builder.AppendLine("/>");
}
builder.Append($" {StylePlaceholderEnd}");
builder.Append($" {BundlingConsts.StylePlaceholderEnd}");
return builder.ToString();
}
@ -146,7 +171,7 @@ namespace Volo.Abp.Cli.Bundling
private string GenerateScriptDefinitions(BundleContext context)
{
var builder = new StringBuilder();
builder.AppendLine($"{ScriptPlaceholderStart}");
builder.AppendLine($"{BundlingConsts.ScriptPlaceholderStart}");
foreach (var script in context.BundleDefinitions)
{
builder.Append($" <script src=\"{script.Source}\"");
@ -156,35 +181,35 @@ namespace Volo.Abp.Cli.Bundling
}
builder.AppendLine("></script>");
}
builder.Append($" {ScriptPlaceholderEnd}");
builder.Append($" {BundlingConsts.ScriptPlaceholderEnd}");
return builder.ToString();
}
private IBundleContributor CreateContributerInstance(Type bundleContributerType)
private IBundleContributor CreateContributorInstance(Type bundleContributorType)
{
return (IBundleContributor)Activator.CreateInstance(bundleContributerType);
return (IBundleContributor)Activator.CreateInstance(bundleContributorType);
}
private void FindBundleContributersRecursively(
private void FindBundleContributorsRecursively(
Type module,
int level,
List<BundleTypeDefinition> bundleDefinitions)
{
var bundleContributers = module.Assembly
var bundleContributors = module.Assembly
.GetTypes()
.Where(t => t.IsAssignableTo<IBundleContributor>())
.ToList();
if (bundleContributers.Count > 1)
if (bundleContributors.Count > 1)
{
throw new BundlingException($"Each project must contain only one class implementing {nameof(IBundleContributor)}");
}
if (bundleContributers.Any())
if (bundleContributors.Any())
{
var bundleContributer = bundleContributers[0];
var definition = bundleDefinitions.SingleOrDefault(t => t.BundleContributerType == bundleContributer);
var bundleContributor = bundleContributors[0];
var definition = bundleDefinitions.SingleOrDefault(t => t.BundleContributorType == bundleContributor);
if (definition != null)
{
if (definition.Level < level)
@ -197,7 +222,7 @@ namespace Volo.Abp.Cli.Bundling
bundleDefinitions.Add(new BundleTypeDefinition
{
Level = level,
BundleContributerType = bundleContributer
BundleContributorType = bundleContributor
});
}
}
@ -210,7 +235,7 @@ namespace Volo.Abp.Cli.Bundling
{
foreach (var dependedModuleType in descriptor.GetDependedTypes())
{
FindBundleContributersRecursively(dependedModuleType, level + 1, bundleDefinitions);
FindBundleContributorsRecursively(dependedModuleType, level + 1, bundleDefinitions);
}
}
}
@ -223,28 +248,17 @@ namespace Volo.Abp.Cli.Bundling
.SingleOrDefault(AbpModule.IsAbpModule);
}
private string GetFrameworkFolderPath(string projectDirectory, string frameworkVersion)
{
return Path.Combine(projectDirectory, "bin", "Debug", frameworkVersion, "wwwroot", "_framework"); ;
}
private string GetTargetFrameworkVersion(string projectFilePath)
{
var document = new XmlDocument();
document.Load(projectFilePath);
var sdk = document.DocumentElement.GetAttribute("Sdk");
if (sdk != SupportedWebAssemblyProjectType)
if (sdk != BundlingConsts.SupportedWebAssemblyProjectType)
{
throw new BundlingException($"Unsupported project type. Project type must be {SupportedWebAssemblyProjectType}.");
throw new BundlingException($"Unsupported project type. Project type must be {BundlingConsts.SupportedWebAssemblyProjectType}.");
}
return document.SelectSingleNode("//TargetFramework").InnerText;
}
private string GetAssemblyFilePath(string directory, string frameworkVersion, string projectFileName)
{
var outputDirectory = GetFrameworkFolderPath(directory, frameworkVersion);
return Path.Combine(outputDirectory, projectFileName + ".dll");
}
}
}

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundler.cs

@ -0,0 +1,9 @@
using Volo.Abp.Bundling;
namespace Volo.Abp.Cli.Bundling
{
public interface IBundler
{
string Bundle(BundleOptions options, BundleContext context);
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs

@ -4,6 +4,6 @@ namespace Volo.Abp.Cli.Bundling
{
public interface IBundlingService
{
Task BundleAsync(string directory, bool forceBuild);
Task BundleAsync(string directory, bool forceBuild, bool bundle, bool minify, string bundleName);
}
}

23
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/PathHelper.cs

@ -0,0 +1,23 @@
using System.IO;
namespace Volo.Abp.Cli.Bundling
{
internal static class PathHelper
{
internal static string GetFrameworkFolderPath(string projectDirectory, string frameworkVersion)
{
return Path.Combine(projectDirectory, "bin", "Debug", frameworkVersion, "wwwroot", "_framework"); ;
}
internal static string GetAssemblyFilePath(string directory, string frameworkVersion, string projectFileName)
{
var outputDirectory = GetFrameworkFolderPath(directory, frameworkVersion);
return Path.Combine(outputDirectory, projectFileName + ".dll");
}
internal static string GetWwwRootPath(string directory)
{
return Path.Combine(directory, "wwwroot");
}
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/IScriptBundler.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.Cli.Bundling.Scripts
{
public interface IScriptBundler : IBundler
{
}
}

34
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/ScriptBundler.cs

@ -0,0 +1,34 @@
using System;
using System.IO;
using System.Text;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Scripts;
namespace Volo.Abp.Cli.Bundling.Scripts
{
public class ScriptBundler : BundlerBase, IScriptBundler, ITransientDependency
{
public override string FileExtension => ".js";
public ScriptBundler(IJavascriptMinifier minifier)
: base(minifier)
{
}
public override string GenerateDefinition(string bundleFilePath)
{
var lastModifiedTicks = File.GetLastWriteTime(bundleFilePath).Ticks;
var builder = new StringBuilder();
builder.AppendLine($"{BundlingConsts.ScriptPlaceholderStart}");
builder.AppendLine($" <script src=\"{Path.GetFileName(bundleFilePath)}?_v={lastModifiedTicks}\"></script>");
builder.Append($" {BundlingConsts.ScriptPlaceholderEnd}");
return builder.ToString();
}
protected override string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory, string fileContent)
{
return fileContent.EnsureEndsWith(';') + Environment.NewLine;
}
}
}

77
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs

@ -0,0 +1,77 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
namespace Volo.Abp.Cli.Bundling.Styles
{
internal static class CssRelativePathAdjuster
{
private static readonly Regex _rxUrl = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public static string Adjust(
string cssFileContents,
string absoluteInputFilePath,
string absoluteOutputPath)
{
var matches = _rxUrl.Matches(cssFileContents);
if (matches.Count <= 0)
{
return cssFileContents;
}
var cssDirectoryPath = Path.GetDirectoryName(absoluteInputFilePath);
foreach (Match match in matches)
{
string quoteDelimiter = match.Groups[1].Value; //url('') vs url("")
string relativePathToCss = match.Groups[2].Value;
// Ignore root relative references
if (relativePathToCss.StartsWith("/", StringComparison.Ordinal))
continue;
//prevent query string from causing error
var pathAndQuery = relativePathToCss.Split(new[] { '?' }, 2, StringSplitOptions.RemoveEmptyEntries);
var pathOnly = pathAndQuery[0];
var queryOnly = pathAndQuery.Length == 2 ? pathAndQuery[1] : string.Empty;
string absolutePath = GetAbsolutePath(cssDirectoryPath, pathOnly);
string serverRelativeUrl = MakeRelative(absoluteOutputPath, absolutePath);
if (!string.IsNullOrEmpty(queryOnly))
serverRelativeUrl += "?" + queryOnly;
string replace = string.Format("url({0}{1}{0})", quoteDelimiter, serverRelativeUrl);
cssFileContents = cssFileContents.Replace(match.Groups[0].Value, replace);
}
return cssFileContents;
}
private static string GetAbsolutePath(string cssFilePath, string pathOnly)
{
return Path.GetFullPath(Path.Combine(cssFilePath, pathOnly));
}
private static readonly string _protocol = "file:///";
private static string MakeRelative(string baseFile, string file)
{
if (string.IsNullOrEmpty(file))
return file;
Uri baseUri = new Uri(_protocol + baseFile, UriKind.RelativeOrAbsolute);
Uri fileUri = new Uri(_protocol + file, UriKind.RelativeOrAbsolute);
if (baseUri.IsAbsoluteUri)
{
return Uri.UnescapeDataString(baseUri.MakeRelativeUri(fileUri).ToString());
}
else
{
return baseUri.ToString();
}
}
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/IStyleBundler.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.Cli.Bundling.Styles
{
public interface IStyleBundler : IBundler
{
}
}

37
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs

@ -0,0 +1,37 @@
using System.IO;
using System.Text;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Styles;
namespace Volo.Abp.Cli.Bundling.Styles
{
public class StyleBundler : BundlerBase, IStyleBundler, ITransientDependency
{
public override string FileExtension => ".css";
public StyleBundler(ICssMinifier minifier)
: base(minifier)
{
}
public override string GenerateDefinition(string bundleFilePath)
{
var lastModifiedTicks = File.GetLastWriteTime(bundleFilePath).Ticks;
var builder = new StringBuilder();
builder.AppendLine($"{BundlingConsts.StylePlaceholderStart}");
builder.AppendLine($" <link href=\"{Path.GetFileName(bundleFilePath)}?_v={lastModifiedTicks}\" rel=\"stylesheet\"/>");
builder.Append($" {BundlingConsts.StylePlaceholderEnd}");
return builder.ToString();
}
protected override string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory, string fileContent)
{
return CssRelativePathAdjuster.Adjust(
fileContent,
referencePath,
bundleDirectory
);
}
}
}

44
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs

@ -28,6 +28,26 @@ namespace Volo.Abp.Cli.Commands
var forceBuild = commandLineArgs.Options.ContainsKey(Options.ForceBuild.Short) ||
commandLineArgs.Options.ContainsKey(Options.ForceBuild.Long);
var bundle = commandLineArgs.Options.ContainsKey(Options.Bundle.Short) ||
commandLineArgs.Options.ContainsKey(Options.Bundle.Long);
var minify = commandLineArgs.Options.ContainsKey(Options.Minify.Short) ||
commandLineArgs.Options.ContainsKey(Options.Minify.Long);
var name = commandLineArgs.Options.GetOrNull(
Options.Name.Short,
Options.Name.Long
);
if ((minify || bundle) && name.IsNullOrEmpty())
{
throw new CliUsageException(
"Please specify a bundle name." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
if (!Directory.Exists(workingDirectory))
{
throw new CliUsageException(
@ -39,7 +59,7 @@ namespace Volo.Abp.Cli.Commands
try
{
await BundlingService.BundleAsync(workingDirectory, forceBuild);
await BundlingService.BundleAsync(workingDirectory, forceBuild, bundle, minify, name);
}
catch (BundlingException ex)
{
@ -65,6 +85,10 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("");
sb.AppendLine("-wd|--working-directory <directory-path> (default: empty)");
sb.AppendLine("-f | --force (default: false)");
sb.AppendLine("-f | --force (default: false)");
sb.AppendLine("-b | --bundle (default: false)");
sb.AppendLine("-m | --minify (default: false)");
sb.AppendLine("-n | --name (default: empty)");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
@ -84,6 +108,24 @@ namespace Volo.Abp.Cli.Commands
public const string Short = "f";
public const string Long = "force";
}
public static class Bundle
{
public const string Short = "b";
public const string Long = "bundle";
}
public static class Minify
{
public const string Short = "m";
public const string Long = "minify";
}
public static class Name
{
public const string Short = "n";
public const string Long = "name";
}
}
}
}

49
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs

@ -2,11 +2,15 @@
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Auth;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.Cli.Commands
{
@ -15,10 +19,16 @@ namespace Volo.Abp.Cli.Commands
public ILogger<LoginCommand> Logger { get; set; }
protected AuthService AuthService { get; }
public ICancellationTokenProvider CancellationTokenProvider { get; }
public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; }
public LoginCommand(AuthService authService)
public LoginCommand(AuthService authService,
ICancellationTokenProvider cancellationTokenProvider,
IRemoteServiceExceptionHandler remoteServiceExceptionHandler)
{
AuthService = authService;
CancellationTokenProvider = cancellationTokenProvider;
RemoteServiceExceptionHandler = remoteServiceExceptionHandler;
Logger = NullLogger<LoginCommand>.Instance;
}
@ -33,6 +43,14 @@ namespace Volo.Abp.Cli.Commands
);
}
var organization = commandLineArgs.Options.GetOrNull(Options.Organization.Short, Options.Organization.Long);
if (string.IsNullOrWhiteSpace(organization) && await CheckMultipleOrganizationsAsync(commandLineArgs.Target))
{
Logger.LogError($"You have multiple organizations, please specify your organization with `--organization` parameter.");
return;
}
var password = commandLineArgs.Options.GetOrNull(Options.Password.Short, Options.Password.Long);
if (password == null)
{
@ -51,12 +69,37 @@ namespace Volo.Abp.Cli.Commands
await AuthService.LoginAsync(
commandLineArgs.Target,
password,
commandLineArgs.Options.GetOrNull(Options.Organization.Short, Options.Organization.Long)
organization
);
Logger.LogInformation($"Successfully logged in as '{commandLineArgs.Target}'");
}
private async Task<bool> CheckMultipleOrganizationsAsync(string username)
{
var url = $"{CliUrls.WwwAbpIo}api/license/check-multiple-organizations?username={username}";
using (var client = new CliHttpClient())
{
var response = await client.GetHttpResponseMessageWithRetryAsync(
url,
CancellationTokenProvider.Token,
Logger);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'");
}
await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response);
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<bool>(responseContent);
return result;
}
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
@ -96,4 +139,4 @@ namespace Volo.Abp.Cli.Commands
}
}
}
}
}

39
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -1,5 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Core;
@ -9,6 +11,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
@ -20,10 +23,12 @@ namespace Volo.Abp.Cli.Commands
public ILogger<NewCommand> Logger { get; set; }
protected TemplateProjectBuilder TemplateProjectBuilder { get; }
public ITemplateInfoProvider TemplateInfoProvider { get; }
public NewCommand(TemplateProjectBuilder templateProjectBuilder)
public NewCommand(TemplateProjectBuilder templateProjectBuilder, ITemplateInfoProvider templateInfoProvider)
{
TemplateProjectBuilder = templateProjectBuilder;
TemplateInfoProvider = templateInfoProvider;
Logger = NullLogger<NewCommand>.Instance;
}
@ -187,6 +192,35 @@ namespace Volo.Abp.Cli.Commands
}
Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'");
if (AppTemplateBase.IsAppTemplate(template ?? TemplateInfoProvider.GetDefault().Name))
{
var isCommercial = template == AppProTemplate.TemplateName;
OpenThanksPage(uiFramework, databaseProvider, isTiered || commandLineArgs.Options.ContainsKey("separate-identity-server"), isCommercial);
}
}
private void OpenThanksPage(UiFramework uiFramework, DatabaseProvider databaseProvider, bool tiered, bool commercial)
{
uiFramework = uiFramework == UiFramework.NotSpecified || uiFramework == UiFramework.None ? UiFramework.Mvc : uiFramework;
var urlPrefix = commercial ? "commercial" : "www";
var tieredYesNo = tiered ? "yes" : "no";
var url = $"https://{urlPrefix}.abp.io/project-created-success?ui={uiFramework:g}&db={databaseProvider:g}&tiered={tieredYesNo}";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
}
private bool GetCreateSolutionFolderPreference(CommandLineArgs commandLineArgs)
@ -195,7 +229,7 @@ namespace Volo.Abp.Cli.Commands
if (longKey == false)
{
return commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Short);
return commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Short);
}
return longKey;
@ -230,6 +264,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("-cs|--connection-string <connection-string> (your database connection string)");
sb.AppendLine("--tiered (if supported by the template)");
sb.AppendLine("--no-ui (if supported by the template)");
sb.AppendLine("--no-random-port (Use template's default ports)");
sb.AppendLine("--separate-identity-server (if supported by the template)");
sb.AppendLine("--local-framework-ref --abp-path <your-local-abp-repo-path> (keeps local references to projects instead of replacing with NuGet package references)");
sb.AppendLine("");

128
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs

@ -31,7 +31,10 @@ namespace Volo.Abp.Cli.Commands
{
var operationType = NamespaceHelper.NormalizeNamespace(commandLineArgs.Target);
var preview = commandLineArgs.Options.ContainsKey(Options.Preview.Long);
var preview = commandLineArgs.Options.ContainsKey(Options.Preview.Short) ||
commandLineArgs.Options.ContainsKey(Options.Preview.Long);
var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long);
switch (operationType)
{
@ -42,11 +45,11 @@ namespace Volo.Abp.Cli.Commands
break;
case "install":
await InstallSuiteAsync(preview);
await InstallSuiteAsync(version, preview);
break;
case "update":
await UpdateSuiteAsync(preview);
await UpdateSuiteAsync(version, preview);
break;
case "remove":
@ -70,7 +73,8 @@ namespace Volo.Abp.Cli.Commands
{
var dotnetToolList = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g");
var suiteLine = dotnetToolList.Split(Environment.NewLine).FirstOrDefault(l => l.ToLower().StartsWith("volo.abp.suite "));
var suiteLine = dotnetToolList.Split(Environment.NewLine)
.FirstOrDefault(l => l.ToLower().StartsWith("volo.abp.suite "));
if (string.IsNullOrEmpty(suiteLine))
{
@ -80,9 +84,23 @@ namespace Volo.Abp.Cli.Commands
return suiteLine.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1];
}
private async Task InstallSuiteAsync(bool preview = false)
private async Task InstallSuiteAsync(string version = null, bool preview = false)
{
Logger.LogInformation("Installing ABP Suite...");
var infoText = "Installing ABP Suite ";
if (version != null)
{
infoText += "v" + version + "... ";
}
else if (preview)
{
infoText += "latest preview version...";
}
else
{
infoText += "latest version...";
}
Logger.LogInformation(infoText);
var nugetIndexUrl = await _nuGetIndexUrlService.GetAsync();
@ -93,9 +111,25 @@ namespace Volo.Abp.Cli.Commands
try
{
var versionOption = await GetVersionOption(preview);
var versionOption = string.Empty;
if (preview)
{
var latestPreviewVersion = await GetLatestPreviewVersion();
if (latestPreviewVersion != null)
{
versionOption = $" --version {latestPreviewVersion}";
Logger.LogInformation("Latest preview version is " + latestPreviewVersion);
}
}
else if (version != null)
{
versionOption = $" --version {version}";
}
var result = CmdHelper.RunCmd($"dotnet tool install {SuitePackageName} {versionOption} --add-source {nugetIndexUrl} -g");
var result = CmdHelper.RunCmd(
$"dotnet tool install {SuitePackageName}{versionOption} --add-source {nugetIndexUrl} -g"
);
if (result == 0)
{
@ -117,25 +151,55 @@ namespace Volo.Abp.Cli.Commands
private void ShowSuiteManualInstallCommand()
{
Logger.LogInformation("You can also run the following command to install ABP Suite.");
Logger.LogInformation("dotnet tool install -g Volo.Abp.Suite");
Logger.LogInformation("dotnet tool install -g Volo.Abp.Suite --add-source https://nuget.abp.io/<your-private-key>/v3/index.json");
}
private async Task UpdateSuiteAsync(bool preview = false)
private async Task UpdateSuiteAsync(string version = null, bool preview = false)
{
Logger.LogInformation("Updating ABP Suite...");
var infoText = "Updating ABP Suite ";
if (version != null)
{
infoText += "to the " + version + "... ";
}
else if (preview)
{
infoText += "to the latest preview version...";
}
else
{
infoText += "...";
}
var nugetIndexUrl = await _nuGetIndexUrlService.GetAsync();
Logger.LogInformation(infoText);
var nugetIndexUrl = await _nuGetIndexUrlService.GetAsync();
if (nugetIndexUrl == null)
{
Logger.LogError("Cannot find your NuGet service URL!");
return;
}
try
{
var versionOption = await GetVersionOption(preview);
var versionOption = string.Empty;
var result = CmdHelper.RunCmd($"dotnet tool update {SuitePackageName} {versionOption} --add-source {nugetIndexUrl} -g");
if (preview)
{
var latestPreviewVersion = await GetLatestPreviewVersion();
if (latestPreviewVersion != null)
{
versionOption = $" --version {latestPreviewVersion}";
Logger.LogInformation("Latest preview version is " + latestPreviewVersion);
}
}
else if (version != null)
{
versionOption = $" --version {version}";
}
var result = CmdHelper.RunCmd(
$"dotnet tool update {SuitePackageName}{versionOption} --add-source {nugetIndexUrl} -g"
);
if (result != 0)
{
@ -149,29 +213,21 @@ namespace Volo.Abp.Cli.Commands
}
}
private async Task<string> GetVersionOption(bool preview)
private async Task<string> GetLatestPreviewVersion()
{
if (preview)
{
var latestVersion = await GetLatestSuiteVersionAsync(true);
if (latestVersion.IsPrerelease)
{
return $"--version {latestVersion.ToString()}";
}
}
var latestPreviewVersion = await _nuGetService
.GetLatestVersionOrNullAsync(
packageId: SuitePackageName,
includeReleaseCandidates: true
);
return "";
}
private async Task<SemanticVersion> GetLatestSuiteVersionAsync(bool preview)
{
return await _nuGetService.GetLatestVersionOrNullAsync(SuitePackageName, includeReleaseCandidates: preview);
return latestPreviewVersion.IsPrerelease ? latestPreviewVersion.ToString() : null;
}
private void ShowSuiteManualUpdateCommand()
{
Logger.LogError("You can also run the following command to update ABP Suite.");
Logger.LogError("dotnet tool update -g Volo.Abp.Suite");
Logger.LogError("dotnet tool update -g Volo.Abp.Suite --add-source https://nuget.abp.io/<your-private-key>/v3/index.json");
}
private static void RemoveSuite()
@ -185,7 +241,8 @@ namespace Volo.Abp.Cli.Commands
{
if (!GlobalToolHelper.IsGlobalToolInstalled("abp-suite"))
{
Logger.LogWarning("ABP Suite is not installed! To install it you can run the command: \"abp suite install\"");
Logger.LogWarning(
"ABP Suite is not installed! To install it you can run the command: \"abp suite install\"");
return;
}
}
@ -236,7 +293,14 @@ namespace Volo.Abp.Cli.Commands
public static class Preview
{
public const string Long = "preview";
public const string Short = "p";
}
public static class Version
{
public const string Long = "version";
public const string Short = "v";
}
}
}
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs

@ -161,6 +161,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds"));
steps.Add(new AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep());
if (context.BuildArgs.MobileApp == MobileApp.ReactNative)
{
steps.Add(new ReactEnvironmentFilePortChangeForSeparatedIdentityServersStep());
}
}
else
{
@ -173,6 +178,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
private static void RandomizeSslPorts(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.ExtraProperties.ContainsKey("no-random-port"))
{
return;
}
steps.Add(new TemplateRandomSslPortStep(
new List<string>
{

44
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/ReactEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs

@ -0,0 +1,44 @@
using System;
using System.Linq;
using Volo.Abp.Cli.ProjectBuilding.Building;
namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
{
public class ReactEnvironmentFilePortChangeForSeparatedIdentityServersStep : ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
var fileEntry = context.Files.FirstOrDefault(x =>
!x.IsDirectory &&
x.Name.EndsWith($"{MobileApp.ReactNative.GetFolderName()}/Environment.js",
StringComparison.InvariantCultureIgnoreCase)
);
if (fileEntry == null)
{
return;
}
fileEntry.NormalizeLineEndings();
var lines = fileEntry.GetLines();
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (line.Contains("issuer") && line.Contains("localhost"))
{
line = line.Replace("44305", "44301");
}
else if (line.Contains("apiUrl") && line.Contains("localhost"))
{
line = line.Replace("44305", "44300");
}
lines[i] = line;
}
fileEntry.SetLines(lines);
}
}
}

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs

@ -54,6 +54,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Module
private void RandomizeSslPorts(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.ExtraProperties.ContainsKey("no-random-port"))
{
return;
}
steps.Add(new TemplateRandomSslPortStep(new List<string>
{
"https://localhost:44300",

4
framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpDynamicOptionsManager.cs

@ -16,9 +16,9 @@ namespace Volo.Abp.Options
public virtual Task SetAsync(string name)
{
return OverrideOptionsAsync(base.Get(name));
return OverrideOptionsAsync(name, base.Get(name));
}
protected abstract Task OverrideOptionsAsync(T options);
protected abstract Task OverrideOptionsAsync(string name, T options);
}
}

50
framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpOptionsFactory.cs

@ -4,21 +4,44 @@ using Microsoft.Extensions.Options;
namespace Volo.Abp.Options
{
//TODO: Derive from OptionsFactory when this is released: https://github.com/aspnet/Options/pull/258 (or completely remove this!)
// https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Options/src/OptionsFactory.cs
public class AbpOptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
private readonly IEnumerable<IValidateOptions<TOptions>> _validations;
public AbpOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
public AbpOptionsFactory(
IEnumerable<IConfigureOptions<TOptions>> setups,
IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
: this(setups, postConfigures, validations: null)
{
}
public AbpOptionsFactory(
IEnumerable<IConfigureOptions<TOptions>> setups,
IEnumerable<IPostConfigureOptions<TOptions>> postConfigures,
IEnumerable<IValidateOptions<TOptions>> validations)
{
_setups = setups;
_postConfigures = postConfigures;
_validations = validations;
}
public virtual TOptions Create(string name)
{
var options = new TOptions();
ConfigureOptions(name, options);
PostConfigureOptions(name, options);
ValidateOptions(name, options);
return options;
}
protected virtual void ConfigureOptions(string name, TOptions options)
{
foreach (var setup in _setups)
{
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
@ -30,13 +53,34 @@ namespace Volo.Abp.Options
setup.Configure(options);
}
}
}
protected virtual void PostConfigureOptions(string name, TOptions options)
{
foreach (var post in _postConfigures)
{
post.PostConfigure(name, options);
}
}
return options;
protected virtual void ValidateOptions(string name, TOptions options)
{
if (_validations != null)
{
var failures = new List<string>();
foreach (var validate in _validations)
{
var result = validate.Validate(name, options);
if (result.Failed)
{
failures.AddRange(result.Failures);
}
}
if (failures.Count > 0)
{
throw new OptionsValidationException(name, typeof(TOptions), failures);
}
}
}
}
}
}

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

Loading…
Cancel
Save