diff --git a/.gitignore b/.gitignore
index 6f7330998e..eb4dbedfae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -292,3 +292,4 @@ samples/MicroserviceDemo/microservices/BloggingService.Host/Logs/logs.txt
samples/MicroserviceDemo/applications/ConsoleClientDemo/Logs/logs.txt
modules/docs/app/Volo.DocsTestApp/Logs/logs.txt
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Logs/logs.txt
+samples/MicroserviceDemo/microservices/TenantManagementService.Host/Logs/logs.txt
diff --git a/README.md b/README.md
index 8d872160ca..14b044f8b1 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ See the documentation.
#### Pre Requirements
-- Visual Studio 2019 16.3.0+
+- Visual Studio 2019 16.4.0+
#### Framework
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json
index f461fe8bbd..d4e51eeadf 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json
@@ -1,7 +1,7 @@
{
"culture": "en",
"texts": {
- "Account": "Account",
+ "Account": "ABP Account - Login & Register | ABP.IO",
"Welcome": "Welcome",
"UseOneOfTheFollowingLinksToContinue": "Use one of the following links to continue",
"FrameworkHomePage": "Framework home page",
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sl.json
new file mode 100644
index 0000000000..586cb24760
--- /dev/null
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sl.json
@@ -0,0 +1,13 @@
+{
+ "culture": "sl",
+ "texts": {
+ "Account": "Račun",
+ "Welcome": "Dobrodošli",
+ "UseOneOfTheFollowingLinksToContinue": "Za nadaljevanje uporabite eno od naslednjih povezav",
+ "FrameworkHomePage": "Domača stran razvojnega okolja",
+ "FrameworkDocumentation": "Dokumentacija razvojnega okolja",
+ "OfficialBlog": "Uradni blog",
+ "CommercialHomePage": "Domača stran različice Commercial",
+ "CommercialSupportWebSite": "Spletna stran za komercialno podporo"
+ }
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json
new file mode 100644
index 0000000000..2e90120185
--- /dev/null
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json
@@ -0,0 +1,90 @@
+{
+ "culture": "sl",
+ "texts": {
+ "Permission:Organizations": "Organizacije",
+ "Permission:Manage": "Upravljaj organizacije",
+ "Permission:NpmPackages": "NPM paketi",
+ "Permission:NugetPackages": "Nuget paketi",
+ "Permission:Maintenance": "Vzdrževanje",
+ "Permission:Maintain": "Vzdrževanje",
+ "Permission:ClearCaches": "Počisti predpomnilnike",
+ "Permission:Modules": "Moduli",
+ "Permission:Packages": "Paketi",
+ "Permission:Edit": "Urejanje",
+ "Permission:Delete": "Brisanje",
+ "Permission:Create": "Ustvarjanje",
+ "Menu:Organizations": "Organizacije",
+ "Menu:Packages": "Paketi",
+ "NpmPackageDeletionWarningMessage": "Ta paket NPM bo izbrisan. Ali to potrjujete?",
+ "NugetPackageDeletionWarningMessage": "Ta Nuget paket bo izbrisan. Ali to potrjujete?",
+ "ModuleDeletionWarningMessage": "Ta modul bo izbrisan. Ali to potrjujete?",
+ "Name": "Naziv",
+ "DisplayName": "Naziv za prikaz",
+ "ShortDescription": "Kratek opis",
+ "NameFilter": "Naziv",
+ "CreationTime": "Čas nastanka",
+ "IsPro": "Je pro",
+ "EfCoreConfigureMethodName": "Konfiguriraj ime metode",
+ "IsProFilter": "Je pro",
+ "ApplicationType": "Tip aplikacije",
+ "Target": "Cilj",
+ "TargetFilter": "Cilj",
+ "ModuleClass": "Razred modula",
+ "NugetPackageTarget.DomainShared": "Domain Shared",
+ "NugetPackageTarget.Domain": "Domain",
+ "NugetPackageTarget.Application": "Application",
+ "NugetPackageTarget.ApplicationContracts": "Application Contracts",
+ "NugetPackageTarget.HttpApi": "Http Api",
+ "NugetPackageTarget.HttpApiClient": "Http Api Client",
+ "NugetPackageTarget.Web": "Web",
+ "NugetPackageTarget.EntityFrameworkCore": "DeleteAllEntityFramework Core",
+ "NugetPackageTarget.MongoDB": "MongoDB",
+ "Edit": "Uredi",
+ "Delete": "Izbriši",
+ "Refresh": "Osveži",
+ "NpmPackages": "NPM paketi",
+ "NugetPackages": "Nuget paketi",
+ "NpmPackageCount": "Število NPM paketov",
+ "NugetPackageCount": "Število Nuget paketov",
+ "Module": "Moduli",
+ "ModuleInfo": "Informacije o modulu",
+ "CreateANpmPackage": "Ustvari NPM paket",
+ "CreateAModule": "Ustvari modul",
+ "CreateANugetPackage": "Ustvari Nuget paket",
+ "AddNew": "Dodaj novega",
+ "PackageAlreadyExist{0}": "Paket \"{0}\" je že dodan.",
+ "ModuleAlreadyExist{0}": "Modul \"{0}\" je že dodan.",
+ "ClearCache": "Počisti predpomnilnik",
+ "SuccessfullyCleared": "Uspešno izbrisano",
+ "Menu:NpmPackages": "NPM paketi",
+ "Menu:Modules": "Moduli",
+ "Menu:Maintenance": "Vzdrževanje",
+ "Menu:NugetPackages": "Nuget paketi",
+ "CreateAnOrganization": "Ustvari organizacijo",
+ "Organizations": "Organizacije",
+ "LongName": "Dolg naziv",
+ "LicenseType": "Tip licence",
+ "LicenseStartTime": "Čas začetka licence",
+ "LicenseEndTime": "Čas konca licence",
+ "AllowedDeveloperCount": "Dovoljeno število razvijalcev",
+ "UserNameOrEmailAddress": "Uporabniško ime ali e-poštni naslov",
+ "AddOwner": "Dodaj lastnika",
+ "UserName": "Uporabniško ime",
+ "Email": "E-poštni naslov",
+ "Developers": "Razvijalci",
+ "AddDeveloper": "Dodaj razvijalca",
+ "Create": "Ustvari",
+ "UserNotFound": "Uporabnika ni mogoče najti",
+ "{0}WillBeRemovedFromMembers": "{0} bo odstranjen iz članov",
+ "Computers": "Računalniki",
+ "UniqueComputerId": "Unikatni id računalnika",
+ "LastSeenDate": "Zadnjič viden",
+ "{0}Computer{1}WillBeRemovedFromRecords": "Računalnik {0} ({1}) bo odstranjen iz zapisov",
+ "OrganizationDeletionWarningMessage": "Organizacija bo izbrisana",
+ "This{0}AlreadyExistInThisOrganization": "{0} že obstaja v tej organizaciji",
+ "AreYouSureYouWantToDeleteAllComputers": "Ali ste prepričani, da želite izbrisati vse računalnike?",
+ "DeleteAll": "Izbriši vse",
+ "DoYouWantToCreateNewUser": "Ali želite ustvariti novega uporabnika?",
+ "MasterModules": "Glavni moduli"
+ }
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sl.json
new file mode 100644
index 0000000000..61a3fa4a8c
--- /dev/null
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sl.json
@@ -0,0 +1,31 @@
+{
+ "culture": "sl",
+ "texts": {
+ "Volo.AbpIo.Domain:010004": "Doseženo je največje število članov!",
+ "Volo.AbpIo.Domain:010005": "Doseženo je največje število lastnikov!",
+ "Volo.AbpIo.Domain:010006": "Ta uporabnik je že lastnik te organizacije!",
+ "Volo.AbpIo.Domain:010007": "Ta uporabnik je že razvijalec v tej organizaciji!",
+ "Volo.AbpIo.Domain:010008": "Dovoljeno število razvijalcev ne sme biti manjše od trenutnega števila razvijalcev!",
+ "Volo.AbpIo.Domain:010009": "Dovoljeno število razvijalcev ne sme biti manjše od 0!",
+ "Volo.AbpIo.Domain:010010": "Največje število mac naslovov je preseženo!",
+ "Volo.AbpIo.Domain:010011": "Osebne licence ne sme imeti več kot 1 razvijalec!",
+ "Volo.AbpIo.Domain:010012": "Licence ni mogoče podaljšati en mesec po poteku licence!",
+ "Volo.AbpIo.Domain:020001": "Tega paketa NPM ni mogoče izbrisati, ker so \"{NugetPackages}\" Nuget paketi odvisni od tega paketa.",
+ "Volo.AbpIo.Domain:020002": "Tega paketa NPM ni mogoče izbrisati, ker \"{Modules}\" moduli uporabljajo ta paket.",
+ "Volo.AbpIo.Domain:020003": "Tega paketa NPM ni mogoče izbrisati, ker \"{Modules}\" moduli uporabljajo ta paket in \"{NugetPackages}\" Nuget paketi so odvisni od tega paketa.",
+ "Volo.AbpIo.Domain:020004": "Nuget paketa ni mogoče izbrisati, ker \"{Modules}\" moduli uporabljajo ta paket.",
+ "WantToLearn?": "Se želite naučiti?",
+ "ReadyToGetStarted?": "Pripravljeni, da bi začeli?",
+ "JoinOurCommunity": "Pridružite se naši skupnosti",
+ "GetStartedUpper": "ZAČNI",
+ "ForkMeOnGitHub": "Naredi vejitev na GitHub",
+ "Features": "Funkcionalnosti",
+ "GetStarted": "Začni",
+ "Documents": "Dokumenti",
+ "Community": "Skupnost",
+ "ContributionGuide": "Vodič za prispevke",
+ "Blog": "Blog",
+ "Commercial": "Commercial",
+ "SeeDocuments": "Poglej dokumente"
+ }
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/sl.json
new file mode 100644
index 0000000000..103359cbc2
--- /dev/null
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/sl.json
@@ -0,0 +1,5 @@
+{
+ "culture": "sl",
+ "texts": {
+ }
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json
new file mode 100644
index 0000000000..fc6022b556
--- /dev/null
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json
@@ -0,0 +1,35 @@
+{
+ "culture": "sl",
+ "texts": {
+ "OrganizationManagement": "Upravljanje organizacije",
+ "OrganizationList": "Seznam organizacij",
+ "Volo.AbpIo.Commercial:010003": "Niste lastnik te organizacije!",
+ "OrganizationNotFoundMessage": "Nobene organizacije ni bilo mogoče najti!",
+ "DeveloperCount": "Dodeljeni / skupno število razvijalcev",
+ "QuestionCount": "Preostala / skupno število vprašanj",
+ "Unlimited": "Neomejeno",
+ "Owners": "Lastniki",
+ "AddMember": "Dodaj člana",
+ "AddOwner": "Dodaj lastnika",
+ "AddDeveloper": "Dodaj razvijalce",
+ "UserName": "Uporabniško ime",
+ "Name": "Ime",
+ "EmailAddress": "E-poštni naslov",
+ "Developers": "Razvijalci",
+ "LicenseType": "Tip licence",
+ "Manage": "Upravljaj",
+ "StartDate": "Datum začetka",
+ "EndDate": "Datum konca",
+ "Modules": "Moduli",
+ "LicenseExtendMessage": "Končni datum veljavnosti licence je podaljšan na {0}",
+ "LicenseUpgradeMessage": "Vaša licenca je nadgrajena na {0}",
+ "LicenseAddDeveloperMessage": "{0} razvijalcev je bilo dodanih k vaši licenci",
+ "Volo.AbpIo.Commercial:010004": "Navedenega uporabnika ni mogoče najti! Uporabnik mora biti registriran.",
+ "MyOrganizations": "Moje organizacije",
+ "ApiKey": "Ključ API",
+ "UserNameNotFound": "Ni uporabnika z uporabniškim imenom {0}",
+ "SuccessfullyAddedToNewsletter": "Hvala, ker ste se naročili na naše novice!",
+ "ManageProfile": "Upravljaj svoj profil",
+ "EmailNotValid": "Vnesite veljaven e-poštni naslov."
+ }
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/sl.json
new file mode 100644
index 0000000000..103359cbc2
--- /dev/null
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/sl.json
@@ -0,0 +1,5 @@
+{
+ "culture": "sl",
+ "texts": {
+ }
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/sl.json
new file mode 100644
index 0000000000..103359cbc2
--- /dev/null
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/sl.json
@@ -0,0 +1,5 @@
+{
+ "culture": "sl",
+ "texts": {
+ }
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
index 2502dce76a..accf9a91b3 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
@@ -1,7 +1,7 @@
{
"culture": "en",
"texts": {
- "GetStarted": "Get Started",
+ "GetStarted": "Get Started - Startup Templates",
"Create": "Create",
"NewProject": "New Project",
"DirectDownload": "Direct Download",
@@ -86,7 +86,7 @@
"BasedOnFamiliarToolsExplanation": "Built on and integrated to popular tools you already know. Low learning curve, easy adaptation, comfortable development.",
"ORMIndependent": "ORM Independent",
"ORMIndependentExplanation": "The core framework is ORM/database independent and can work with any data source. Entity Framework Core and MongoDB providers are already available.",
- "Features": "Features",
+ "Features": "Explore the ABP Framework Features",
"ABPCLI": "ABP CLI",
"Modularity": "Modularity",
"BootstrapTagHelpers": "Bootstrap Tag Helpers",
diff --git a/common.props b/common.props
index 6f12895d7c..92d87d3d76 100644
--- a/common.props
+++ b/common.props
@@ -1,7 +1,7 @@
- latest
- 2.2.0
+ latest
+ 2.3.0$(NoWarn);CS1591https://abp.io/assets/abp_nupkg.pnghttps://abp.io
@@ -9,9 +9,7 @@
githttps://github.com/abpframework/abp/
-
-
\ No newline at end of file
diff --git a/docs/cs/AspNetCore/Widgets.md b/docs/cs/AspNetCore/Widgets.md
index c885da9c62..6498f349df 100644
--- a/docs/cs/AspNetCore/Widgets.md
+++ b/docs/cs/AspNetCore/Widgets.md
@@ -478,19 +478,19 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
## WidgetOptions
-Jako alternativu k atributu `Widget` můžete ke konfiguraci widgetů použít `WidgetOptions`:
+Jako alternativu k atributu `Widget` můžete ke konfiguraci widgetů použít `AbpWidgetOptions`:
```csharp
-Configure(options =>
+Configure(options =>
{
options.Widgets.Add();
});
```
-Toto vepište do metody `ConfigureServices` vašeho [modulu](../Module-Development-Basics.md). Veškerá konfigurace udělaná přes atribut `Widget` je dostupná i za pomoci `WidgetOptions`. Příklad konfigurace, která přidává styl pro widget:
+Toto vepište do metody `ConfigureServices` vašeho [modulu](../Module-Development-Basics.md). Veškerá konfigurace udělaná přes atribut `Widget` je dostupná i za pomoci `AbpWidgetOptions`. Příklad konfigurace, která přidává styl pro widget:
````csharp
-Configure(options =>
+Configure(options =>
{
options.Widgets
.Add()
@@ -498,7 +498,7 @@ Configure(options =>
});
````
-> Tip: `WidgetOptions` lze také použít k získání existujícího widgetu a ke změně jeho konfigurace. To je obzvláště užitečné, pokud chcete změnit konfiguraci widgetu uvnitř modulu používaného vaší aplikací. Použíjte `options.Widgets.Find` k získání existujícího `WidgetDefinition`.
+> Tip: `AbpWidgetOptions` lze také použít k získání existujícího widgetu a ke změně jeho konfigurace. To je obzvláště užitečné, pokud chcete změnit konfiguraci widgetu uvnitř modulu používaného vaší aplikací. Použíjte `options.Widgets.Find` k získání existujícího `WidgetDefinition`.
## Podívejte se také na
diff --git a/docs/cs/CLI.md b/docs/cs/CLI.md
index 7d2ab4c479..db6918384e 100644
--- a/docs/cs/CLI.md
+++ b/docs/cs/CLI.md
@@ -1,4 +1,4 @@
-# ABP CLI
+# ABP CLI
ABP CLI (Command Line Interface) je nástroj v příkazovém řádku k provádění některých běžných úkonů v řešeních založených na ABP.
@@ -109,6 +109,7 @@ abp add-module Volo.Blogging
* `--solution` nebo `-s`: Určuje cestu k řešení (.sln). Pokud není zadáno, CLI se pokusí najít soubor .sln v aktuálním adresáři.
* `--skip-db-migrations`: Pro poskytovatele databáze EF Core automaticky přidá nový kód první migrace (`Add-Migration`) a v případě potřeby aktualizuje databázi (`Update-Database`). Tuto možnost určete k vynechání této operace.
* `-sp` nebo `--startup-project`: Relativní cesta ke složce spouštěcího projektu. Výchozí hodnota je aktuální adresář.
+* `--with-source-code`: Místo balíčků NuGet/NPM přidejte zdrojový kód modulu.
### update
diff --git a/docs/cs/Getting-Started-Angular-Template.md b/docs/cs/Getting-Started-Angular-Template.md
index ea5ef0a4bf..076fb45671 100644
--- a/docs/cs/Getting-Started-Angular-Template.md
+++ b/docs/cs/Getting-Started-Angular-Template.md
@@ -26,10 +26,10 @@ abp new Acme.BookStore -u angular
Vytvořené řešení vyžaduje;
-* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
+* [Visual Studio 2019 (v16.4.0+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
-* [Yarn v1.19+](https://yarnpkg.com/)
+* [Yarn v1.19+](https://classic.yarnpkg.com/)
### Struktura řešení
diff --git a/docs/cs/Getting-Started-AspNetCore-Application.md b/docs/cs/Getting-Started-AspNetCore-Application.md
index 0537b01579..eb15f45389 100644
--- a/docs/cs/Getting-Started-AspNetCore-Application.md
+++ b/docs/cs/Getting-Started-AspNetCore-Application.md
@@ -4,7 +4,7 @@ Tento tutoriál vysvětluje jak začít s ABP z ničeho s minimem závislostí.
## Tvorba nového projektu
-1. Vytvořte novou AspNet Core Web aplikaci ve Visual Studio 2019 (16.3.0+):
+1. Vytvořte novou AspNet Core Web aplikaci ve Visual Studio 2019 (16.4.0+):

diff --git a/docs/cs/Getting-Started-AspNetCore-MVC-Template.md b/docs/cs/Getting-Started-AspNetCore-MVC-Template.md
index 32edd59931..c053e1d76a 100644
--- a/docs/cs/Getting-Started-AspNetCore-MVC-Template.md
+++ b/docs/cs/Getting-Started-AspNetCore-MVC-Template.md
@@ -26,10 +26,10 @@ Příkaz `new` vytvoří **vrstvenou MVC aplikaci** s **Entity Framework Core**
Vytvořené řešení vyžaduje;
-* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
+* [Visual Studio 2019 (v16.4.0+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
-* [Yarn v1.19+](https://yarnpkg.com/)
+* [Yarn v1.19+](https://classic.yarnpkg.com/)
### Struktura řešení
diff --git a/docs/en/API/Auto-API-Controllers.md b/docs/en/API/Auto-API-Controllers.md
new file mode 100644
index 0000000000..77819c48c9
--- /dev/null
+++ b/docs/en/API/Auto-API-Controllers.md
@@ -0,0 +1,140 @@
+# Auto API Controllers
+
+Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc.
+
+ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it.
+
+## Configuration
+
+Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below:
+
+````csharp
+[DependsOn(BookStoreApplicationModule)]
+public class BookStoreWebModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options
+ .ConventionalControllers
+ .Create(typeof(BookStoreApplicationModule).Assembly);
+ });
+ }
+}
+````
+
+This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/).
+
+
+
+### Examples
+
+Some example method names and the corresponding routes calculated by convention:
+
+| Service Method Name | HTTP Method | Route |
+| ----------------------------------------------------- | ----------- | -------------------------- |
+| GetAsync(Guid id) | GET | /api/app/book/{id} |
+| GetListAsync() | GET | /api/app/book |
+| CreateAsync(CreateBookDto input) | POST | /api/app/book |
+| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} |
+| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} |
+| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors |
+| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor |
+
+### HTTP Method
+
+ABP uses a naming convention while determining the HTTP method for a service method (action):
+
+- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'.
+- **Put**: Used if the method name starts with 'Put' or 'Update'.
+- **Delete**: Used if the method name starts with 'Delete' or 'Remove'.
+- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'.
+- **Patch**: Used if the method name starts with 'Patch'.
+- Otherwise, **Post** is used **by default**.
+
+If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service.
+
+### Route
+
+Route is calculated based on some conventions:
+
+* It always starts with '**/api**'.
+* Continues with a **route path**. Default value is '**/app**' and can be configured as like below:
+
+````csharp
+Configure(options =>
+{
+ options.ConventionalControllers
+ .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
+ {
+ opts.RootPath = "volosoft/book-store";
+ });
+});
+````
+
+Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth.
+
+* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'.
+ * If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service.
+* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route.
+* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by;
+ * Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'.
+ * Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request.
+ * Converting the result to **camelCase**.
+ * If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'.
+ * Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method.
+* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId').
+
+## Service Selection
+
+Creating conventional HTTP API controllers are not unique to application services actually.
+
+### IRemoteService Interface
+
+If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers.
+
+### RemoteService Attribute
+
+`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example:
+
+````csharp
+[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)]
+public class PersonAppService : ApplicationService
+{
+
+}
+````
+
+### TypePredicate Option
+
+You can further filter classes to become an API controller by providing the `TypePredicate` option:
+
+````csharp
+services.Configure(options =>
+{
+ options.ConventionalControllers
+ .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
+ {
+ opts.TypePredicate = type => { return true; };
+ });
+});
+````
+
+Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller.
+
+## API Explorer
+
+API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint.
+
+API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example:
+
+````csharp
+[RemoteService(IsMetadataEnabled = false)]
+public class PersonAppService : ApplicationService
+{
+
+}
+````
+
+Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route.
diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md
new file mode 100644
index 0000000000..3c3c2ec26c
--- /dev/null
+++ b/docs/en/API/Dynamic-CSharp-API-Clients.md
@@ -0,0 +1,165 @@
+# Dynamic C# API Clients
+
+ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results.
+
+## Service Interface
+
+Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example:
+
+````csharp
+public interface IBookAppService : IApplicationService
+{
+ Task> GetListAsync();
+}
+````
+
+Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition.
+
+Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint.
+
+## Client Proxy Generation
+
+First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project:
+
+````
+Install-Package Volo.Abp.Http.Client
+````
+
+Then add `AbpHttpClientModule` dependency to your module:
+
+````csharp
+[DependsOn(typeof(AbpHttpClientModule))] //add the dependency
+public class MyClientAppModule : AbpModule
+{
+}
+````
+
+Now, it's ready to create the client proxies. Example:
+
+````csharp
+[DependsOn(
+ typeof(AbpHttpClientModule), //used to create client proxies
+ typeof(BookStoreApplicationModule) //contains the application service interfaces
+ )]
+public class MyClientAppModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ //Create dynamic client proxies
+ context.Services.AddHttpClientProxies(
+ typeof(BookStoreApplicationModule).Assembly
+ );
+ }
+}
+````
+
+`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes.
+
+### Endpoint Configuration
+
+`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below:
+
+````
+{
+ "RemoteServices": {
+ "Default": {
+ "BaseUrl": "http://localhost:53929/"
+ }
+ }
+}
+````
+
+See the "RemoteServiceOptions" section below for more detailed configuration.
+
+## Usage
+
+It's straightforward to use. Just inject the service interface in the client application code:
+
+````csharp
+public class MyService : ITransientDependency
+{
+ private readonly IBookAppService _bookService;
+
+ public MyService(IBookAppService bookService)
+ {
+ _bookService = bookService;
+ }
+
+ public async Task DoIt()
+ {
+ var books = await _bookService.GetListAsync();
+ foreach (var book in books)
+ {
+ Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}");
+ }
+ }
+}
+````
+
+This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client.
+
+### IHttpClientProxy Interface
+
+While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy` interface.
+
+## Configuration
+
+### RemoteServiceOptions
+
+`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example:
+
+````csharp
+public override void ConfigureServices(ServiceConfigurationContext context)
+{
+ context.Services.Configure(options =>
+ {
+ options.RemoteServices.Default =
+ new RemoteServiceConfiguration("http://localhost:53929/");
+ });
+
+ //...
+}
+````
+
+### Multiple Remote Service Endpoints
+
+The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file:
+
+````json
+{
+ "RemoteServices": {
+ "Default": {
+ "BaseUrl": "http://localhost:53929/"
+ },
+ "BookStore": {
+ "BaseUrl": "http://localhost:48392/"
+ }
+ }
+}
+````
+
+`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example:
+
+````csharp
+context.Services.AddHttpClientProxies(
+ typeof(BookStoreApplicationModule).Assembly,
+ remoteServiceName: "BookStore"
+);
+````
+
+`remoteServiceName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint.
+
+### As Default Services
+
+When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature.
+
+````csharp
+context.Services.AddHttpClientProxies(
+ typeof(BookStoreApplicationModule).Assembly,
+ asDefaultServices: false
+);
+````
+
+Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy.
+
+> If you disable `asDefaultServices`, you can only use `IHttpClientProxy` interface to use the client proxies (see the related section above).
\ No newline at end of file
diff --git a/docs/en/API/JavaScript-API/Auth.md b/docs/en/API/JavaScript-API/Auth.md
new file mode 100644
index 0000000000..60c9eb8866
--- /dev/null
+++ b/docs/en/API/JavaScript-API/Auth.md
@@ -0,0 +1,3 @@
+# abp.auth JavaScript API
+
+TODO
\ No newline at end of file
diff --git a/docs/en/API/JavaScript-API/Index.md b/docs/en/API/JavaScript-API/Index.md
new file mode 100644
index 0000000000..ca9afca97f
--- /dev/null
+++ b/docs/en/API/JavaScript-API/Index.md
@@ -0,0 +1,24 @@
+# JavaScript API
+
+ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform some common application requirements in the client side.
+
+## APIs
+
+* abp.ajax
+* [abp.auth](Auth.md)
+* abp.currentUser
+* abp.dom
+* abp.event
+* abp.features
+* abp.localization
+* abp.log
+* abp.ModalManager
+* abp.notify
+* abp.security
+* abp.setting
+* abp.ui
+* abp.utils
+* abp.ResourceLoader
+* abp.WidgetManager
+* Other APIs
+
diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md
index c860f0dd82..3fb52377b3 100644
--- a/docs/en/Application-Services.md
+++ b/docs/en/Application-Services.md
@@ -201,7 +201,7 @@ See the [authorization document](Authorization.md) for more.
## CRUD Application Services
-If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from `CrudAppService`.
+If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from the `CrudAppService`.
### Example
@@ -219,7 +219,9 @@ public interface IBookAppService :
}
````
-* `ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface).
+`ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface).
+
+> Creating interface for an application service is a good practice, but not required by the ABP Framework. You can skip the interface part.
`ICrudAppService` declares the following methods:
@@ -292,6 +294,52 @@ public class BookAppService :
`CrudAppService` implements all methods declared in the `ICrudAppService` interface. You can then add your own custom methods or override and customize base methods.
+> `CrudAppService` has different versions gets different number of generic arguments. Use the one suitable for you.
+
+### AbstractKeyCrudAppService
+
+`CrudAppService` requires to have an Id property as the primary key of your entity. If you are using composite keys then you can not utilize it.
+
+`AbstractKeyCrudAppService` implements the same `ICrudAppService` interface, but this time without making assumption about your primary key.
+
+#### Example
+
+Assume that you have a `District` entity with `CityId` and `Name` as a composite primary key. Using `AbstractKeyCrudAppService` requires to implement `DeleteByIdAsync` and `GetEntityByIdAsync` methods yourself:
+
+````csharp
+public class DistrictAppService
+ : AbstractKeyCrudAppService
+{
+ public DistrictAppService(IRepository repository)
+ : base(repository)
+ {
+ }
+
+ protected override async Task DeleteByIdAsync(DistrictKey id)
+ {
+ await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
+ }
+
+ protected override async Task GetEntityByIdAsync(DistrictKey id)
+ {
+ return await AsyncQueryableExecuter.FirstOrDefaultAsync(
+ Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
+ );
+ }
+}
+````
+
+This implementation requires you to create a class represents your composite key:
+
+````csharp
+public class DistrictKey
+{
+ public Guid CityId { get; set; }
+
+ public string Name { get; set; }
+}
+````
+
## Lifetime
Lifetime of application services are [transient](Dependency-Injection.md) and they are automatically registered to the dependency injection system.
diff --git a/docs/en/AspNetCore/Auto-API-Controllers.md b/docs/en/AspNetCore/Auto-API-Controllers.md
index 77819c48c9..52db2bde06 100644
--- a/docs/en/AspNetCore/Auto-API-Controllers.md
+++ b/docs/en/AspNetCore/Auto-API-Controllers.md
@@ -1,140 +1,3 @@
-# Auto API Controllers
+This document has moved.
-Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc.
-
-ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it.
-
-## Configuration
-
-Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below:
-
-````csharp
-[DependsOn(BookStoreApplicationModule)]
-public class BookStoreWebModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- Configure(options =>
- {
- options
- .ConventionalControllers
- .Create(typeof(BookStoreApplicationModule).Assembly);
- });
- }
-}
-````
-
-This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/).
-
-
-
-### Examples
-
-Some example method names and the corresponding routes calculated by convention:
-
-| Service Method Name | HTTP Method | Route |
-| ----------------------------------------------------- | ----------- | -------------------------- |
-| GetAsync(Guid id) | GET | /api/app/book/{id} |
-| GetListAsync() | GET | /api/app/book |
-| CreateAsync(CreateBookDto input) | POST | /api/app/book |
-| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} |
-| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} |
-| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors |
-| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor |
-
-### HTTP Method
-
-ABP uses a naming convention while determining the HTTP method for a service method (action):
-
-- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'.
-- **Put**: Used if the method name starts with 'Put' or 'Update'.
-- **Delete**: Used if the method name starts with 'Delete' or 'Remove'.
-- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'.
-- **Patch**: Used if the method name starts with 'Patch'.
-- Otherwise, **Post** is used **by default**.
-
-If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service.
-
-### Route
-
-Route is calculated based on some conventions:
-
-* It always starts with '**/api**'.
-* Continues with a **route path**. Default value is '**/app**' and can be configured as like below:
-
-````csharp
-Configure(options =>
-{
- options.ConventionalControllers
- .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
- {
- opts.RootPath = "volosoft/book-store";
- });
-});
-````
-
-Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth.
-
-* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'.
- * If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service.
-* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route.
-* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by;
- * Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'.
- * Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request.
- * Converting the result to **camelCase**.
- * If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'.
- * Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method.
-* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId').
-
-## Service Selection
-
-Creating conventional HTTP API controllers are not unique to application services actually.
-
-### IRemoteService Interface
-
-If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers.
-
-### RemoteService Attribute
-
-`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example:
-
-````csharp
-[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)]
-public class PersonAppService : ApplicationService
-{
-
-}
-````
-
-### TypePredicate Option
-
-You can further filter classes to become an API controller by providing the `TypePredicate` option:
-
-````csharp
-services.Configure(options =>
-{
- options.ConventionalControllers
- .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
- {
- opts.TypePredicate = type => { return true; };
- });
-});
-````
-
-Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller.
-
-## API Explorer
-
-API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint.
-
-API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example:
-
-````csharp
-[RemoteService(IsMetadataEnabled = false)]
-public class PersonAppService : ApplicationService
-{
-
-}
-````
-
-Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route.
+[Click to navigate to Auto API Controllers document](../API/Auto-API-Controllers.md)
\ No newline at end of file
diff --git a/docs/en/AspNetCore/Bundling-Minification.md b/docs/en/AspNetCore/Bundling-Minification.md
index 8868d441e1..a67fbe429d 100644
--- a/docs/en/AspNetCore/Bundling-Minification.md
+++ b/docs/en/AspNetCore/Bundling-Minification.md
@@ -1,352 +1,4 @@
-# ASP.NET Core MVC Bundling & Minification
+This document has moved.
-There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are:
-
-* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/).
-* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins.
-
-ABP offers a simple, dynamic, powerful, modular and built-in way.
-
-## Volo.Abp.AspNetCore.Mvc.UI.Bundling Package
-
-> This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually.
-
-Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project:
-
-````
-install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling
-````
-
-Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module:
-
-````C#
-using Volo.Abp.Modularity;
-using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
-
-namespace MyCompany.MyProject
-{
- [DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
- public class MyWebModule : AbpModule
- {
- //...
- }
-}
-````
-
-## Razor Bundling Tag Helpers
-
-The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-style-bundle` tag helpers. Example:
-
-````html
-
-
-
-
-
-
-````
-
-This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It's very easy to understand how to use it. Let's see how it *works*:
-
-* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests.
-* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...).
-* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md).
-* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment).
-
-### Importing The Bundling Tag Helpers
-
-> This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually.
-
-In order to use bundle tag helpers, you need to add it into your `_ViewImports.cshtml` file or into your page:
-
-````
-@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
-````
-
-### Unnamed Bundles
-
-The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example:
-
-````html
-
-
-
-
- @if (ViewBag.IncludeCustomStyles != false)
- {
-
- }
-
-````
-
-This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not).
-
-Advantages of **unnamed** bundles:
-
-* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions.
-
-Advantages of **named** bundles:
-
-* Other **modules can contribute** to the bundle by its name (see the sections below).
-
-### Single File
-
-If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example:
-
-````xml
-
-````
-
-The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too.
-
-## Bundling Options
-
-If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class.
-
-### Creating A New Bundle
-
-Example usage:
-
-````C#
-[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
-public class MyWebModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- Configure(options =>
- {
- options
- .ScriptBundles
- .Add("MyGlobalBundle", bundle => {
- bundle.AddFiles(
- "/libs/jquery/jquery.js",
- "/libs/bootstrap/js/bootstrap.js",
- "/libs/toastr/toastr.min.js",
- "/scripts/my-global-scripts.js"
- );
- });
- });
- }
-}
-````
-
-> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`).
-
-After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example:
-
-````html
-
-````
-
-This time, no file defined in the tag helper definition because the bundle files are defined by the code.
-
-### Configuring An Existing Bundle
-
-ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module. Example:
-
-````C#
-[DependsOn(typeof(MyWebModule))]
-public class MyWebExtensionModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- Configure(options =>
- {
- options
- .ScriptBundles
- .Configure("MyGlobalBundle", bundle => {
- bundle.AddFiles(
- "/scripts/my-extension-script.js"
- );
- });
- });
- }
-}
-````
-
-> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper.
-
-## Bundle Contributors
-
-Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases.
-
-An example bundle contributor that replaces bootstrap.css with a customized version:
-
-````C#
-public class MyExtensionGlobalStyleContributor : BundleContributor
-{
- public override void ConfigureBundle(BundleConfigurationContext context)
- {
- context.Files.ReplaceOne(
- "/libs/bootstrap/css/bootstrap.css",
- "/styles/extensions/bootstrap-customized.css"
- );
- }
-}
-````
-
-Then you can use this contributor as like below:
-
-````C#
-services.Configure(options =>
-{
- options
- .ScriptBundles
- .Configure("MyGlobalBundle", bundle => {
- bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor));
- });
-});
-````
-
-> You can also add contributors while creating a new bundle.
-
-Contributors can also be used in the bundle tag helpers. Example:
-
-````xml
-
-
-
-
-
-````
-
-`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle.
-
-### Contributor Dependencies
-
-A bundle contributor can have one or more dependencies to other contributors.
-Example:
-
-````C#
-[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency
-public class MyExtensionStyleBundleContributor : BundleContributor
-{
- //...
-}
-````
-
-When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications.
-
-Creating contributors and defining dependencies is a way of organizing bundle creation across different modules.
-
-### Contributor Extensions
-
-In some advanced scenarios, you may want to do some additional configuration whenever a bundle contributor is used. Contributor extensions works seamlessly when the extended contributor is used.
-
-The example below adds some styles for prism.js library:
-
-````csharp
-public class MyPrismjsStyleExtension : BundleContributor
-{
- public override void ConfigureBundle(BundleConfigurationContext context)
- {
- context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css");
- }
-}
-````
-
-Then you can configure `BundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`.
-
-````csharp
-Configure(options =>
-{
- options
- .Extensions()
- .Add();
-});
-````
-
-Whenever `PrismjsStyleBundleContributor` is added into a bundle, `MyPrismjsStyleExtension` will also be automatically added.
-
-### Accessing to the IServiceProvider
-
-While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method.
-
-### Standard Package Contributors
-
-Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package.
-
-There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself.
-
-````C#
-[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency
-public class MyExtensionStyleBundleContributor : BundleContributor
-{
- //...
-}
-````
-
-Using the built-in contributors for standard packages;
-
-* Prevents you typing **the invalid resource paths**.
-* Prevents changing your contributor if the resource **path changes** (the dependant contributor will handle it).
-* Prevents multiple modules adding the **duplicate files**.
-* Manages **dependencies recursively** (adds dependencies of dependencies, if necessary).
-
-#### Volo.Abp.AspNetCore.Mvc.UI.Packages Package
-
-> This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually.
-
-Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package.
-To install it to your project:
-
-````
-install-package Volo.Abp.AspNetCore.Mvc.UI.Packages
-````
-
-Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module;
-
-````C#
-using Volo.Abp.Modularity;
-using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
-
-namespace MyCompany.MyProject
-{
- [DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))]
- public class MyWebModule : AbpModule
- {
- //...
- }
-}
-````
-
-### Bundle Inheritance
-
-In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle.
-Example:
-
-````c#
-services.Configure(options =>
-{
- options
- .StyleBundles
- .Add("MyTheme.MyGlobalBundle", bundle => {
- bundle
- .AddBaseBundles("MyGlobalBundle") //Can add multiple
- .AddFiles(
- "/styles/mytheme-global-styles.css"
- );
- });
-});
-````
-
-## Themes
-
-Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more.
-
-## Best Practices & Suggestions
-
-It's suggested to define multiple bundles for an application, each one is used for different purposes.
-
-* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them.
-* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice.
-* **Module bundles**: For shared resources among the pages of an individual module.
-* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice.
-
-Establish a balance between performance, network bandwidth usage and count of many bundles.
-
-## See Also
-
-* [Client Side Package Management](Client-Side-Package-Management.md)
-* [Theming](Theming.md)
+[Click to navigate to ASP.NET Core MVC Bundling & Minification document](../UI/AspNetCore/Bundling-Minification.md)
\ No newline at end of file
diff --git a/docs/en/AspNetCore/Client-Side-Package-Management.md b/docs/en/AspNetCore/Client-Side-Package-Management.md
index f6dea58ff3..780633de50 100644
--- a/docs/en/AspNetCore/Client-Side-Package-Management.md
+++ b/docs/en/AspNetCore/Client-Side-Package-Management.md
@@ -1,116 +1,4 @@
-## ASP.NET Core MVC Client Side Package Management
+This document has moved.
-ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually.
-
-However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn.
-
-Finally, we suggest the [**Yarn**](https://yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM.
-
-### @ABP NPM Packages
-
-ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state.
-
-One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions.
-
-To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs).
-
-The benefit of a **standard package** is:
-
-* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version.
-* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more.
-
-Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example:
-
-````
-{
- ...
- "dependencies": {
- "@abp/bootstrap": "^1.0.0"
- }
-}
-````
-
-It's suggested to depend on a standard package instead of directly depending on a third-party package.
-
-#### Package Installation
-
-After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies:
-
-````
-yarn
-````
-
-Alternatively, you can use `npm install` but [Yarn](https://yarnpkg.com/) is suggested as mentioned before.
-
-#### Package Contribution
-
-If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted:
-
-* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package).
-* It should be the **latest stable** version of the package.
-* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages.
-* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package.
-* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created.
-
-See current standard packages for examples.
-
-### Mapping The Library Resources
-
-Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project.
-
-Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers.
-
-ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies.
-
-The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options.
-
-#### Resource Mapping Definition File
-
-A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below:
-
-````js
-module.exports = {
- aliases: {
- "@node_modules": "./node_modules",
- "@libs": "./wwwroot/libs"
- },
- clean: [
- "@libs"
- ],
- mappings: {
-
- }
-}
-````
-
-* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication.
-* **clean** section is a list of folders to clean before copying the files.
-* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package.
-
-An example mapping configuration is shown below:
-
-````js
-mappings: {
- "@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/",
- "@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/",
- "@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/"
-}
-````
-
-#### Using The Gulp
-
-Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line:
-
-````
-gulp
-````
-
-When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file.
-
-> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively).
-
-#### See Also
-
-* [Bundling & Minification](Bundling-Minification.md)
-* [Theming](Theming.md)
+[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md)
diff --git a/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md b/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md
index 0b0667dd0d..4ca26e8220 100644
--- a/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md
+++ b/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md
@@ -1,165 +1,3 @@
-# Dynamic C# API Clients
+This document has moved.
-ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results.
-
-## Service Interface
-
-Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example:
-
-````csharp
-public interface IBookAppService : IApplicationService
-{
- Task> GetListAsync();
-}
-````
-
-Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition.
-
-Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint.
-
-## Client Proxy Generation
-
-First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project:
-
-````
-Install-Package Volo.Abp.Http.Client
-````
-
-Then add `AbpHttpClientModule` dependency to your module:
-
-````csharp
-[DependsOn(typeof(AbpHttpClientModule))] //add the dependency
-public class MyClientAppModule : AbpModule
-{
-}
-````
-
-Now, it's ready to create the client proxies. Example:
-
-````csharp
-[DependsOn(
- typeof(AbpHttpClientModule), //used to create client proxies
- typeof(BookStoreApplicationModule) //contains the application service interfaces
- )]
-public class MyClientAppModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- //Create dynamic client proxies
- context.Services.AddHttpClientProxies(
- typeof(BookStoreApplicationModule).Assembly
- );
- }
-}
-````
-
-`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes.
-
-### Endpoint Configuration
-
-`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below:
-
-````
-{
- "RemoteServices": {
- "Default": {
- "BaseUrl": "http://localhost:53929/"
- }
- }
-}
-````
-
-See the "RemoteServiceOptions" section below for more detailed configuration.
-
-## Usage
-
-It's straightforward to use. Just inject the service interface in the client application code:
-
-````csharp
-public class MyService : ITransientDependency
-{
- private readonly IBookAppService _bookService;
-
- public MyService(IBookAppService bookService)
- {
- _bookService = bookService;
- }
-
- public async Task DoIt()
- {
- var books = await _bookService.GetListAsync();
- foreach (var book in books)
- {
- Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}");
- }
- }
-}
-````
-
-This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client.
-
-### IHttpClientProxy Interface
-
-While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy` interface.
-
-## Configuration
-
-### RemoteServiceOptions
-
-`RemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example:
-
-````csharp
-public override void ConfigureServices(ServiceConfigurationContext context)
-{
- context.Services.Configure(options =>
- {
- options.RemoteServices.Default =
- new RemoteServiceConfiguration("http://localhost:53929/");
- });
-
- //...
-}
-````
-
-### Multiple Remote Service Endpoints
-
-The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file:
-
-````json
-{
- "RemoteServices": {
- "Default": {
- "BaseUrl": "http://localhost:53929/"
- },
- "BookStore": {
- "BaseUrl": "http://localhost:48392/"
- }
- }
-}
-````
-
-`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example:
-
-````csharp
-context.Services.AddHttpClientProxies(
- typeof(BookStoreApplicationModule).Assembly,
- remoteServiceName: "BookStore"
-);
-````
-
-`remoteServiceName` parameter matches the service endpoint configured via `RemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint.
-
-### As Default Services
-
-When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature.
-
-````csharp
-context.Services.AddHttpClientProxies(
- typeof(BookStoreApplicationModule).Assembly,
- asDefaultServices: false
-);
-````
-
-Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy.
-
-> If you disable `asDefaultServices`, you can only use `IHttpClientProxy` interface to use the client proxies (see the related section above).
\ No newline at end of file
+[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md)
diff --git a/docs/en/AspNetCore/JavaScript-API/Auth.md b/docs/en/AspNetCore/JavaScript-API/Auth.md
index 60c9eb8866..fb83bcfbff 100644
--- a/docs/en/AspNetCore/JavaScript-API/Auth.md
+++ b/docs/en/AspNetCore/JavaScript-API/Auth.md
@@ -1,3 +1,3 @@
-# abp.auth JavaScript API
+This document has moved.
-TODO
\ No newline at end of file
+[Click to navigate to JavaScript Auth document](../../API/JavaScript-API/Auth.md)
\ No newline at end of file
diff --git a/docs/en/AspNetCore/JavaScript-API/Index.md b/docs/en/AspNetCore/JavaScript-API/Index.md
index ca9afca97f..1ed7f0285c 100644
--- a/docs/en/AspNetCore/JavaScript-API/Index.md
+++ b/docs/en/AspNetCore/JavaScript-API/Index.md
@@ -1,24 +1,3 @@
-# JavaScript API
-
-ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform some common application requirements in the client side.
-
-## APIs
-
-* abp.ajax
-* [abp.auth](Auth.md)
-* abp.currentUser
-* abp.dom
-* abp.event
-* abp.features
-* abp.localization
-* abp.log
-* abp.ModalManager
-* abp.notify
-* abp.security
-* abp.setting
-* abp.ui
-* abp.utils
-* abp.ResourceLoader
-* abp.WidgetManager
-* Other APIs
+This document has moved.
+[Click to navigate to JavaScript API document](../../API/JavaScript-API/Index.md)
\ No newline at end of file
diff --git a/docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md b/docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md
index 6d700f030d..35382845ce 100644
--- a/docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md
+++ b/docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md
@@ -1,3 +1,3 @@
-## Dynamic Forms
+This document has moved.
-This is not documented yet. You can see a [demo](http://bootstrap-taghelpers.abp.io/Components/DynamicForms) for now.
\ No newline at end of file
+[Click to navigate to Dynamic Forms document](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md)
\ No newline at end of file
diff --git a/docs/en/AspNetCore/Tag-Helpers/Index.md b/docs/en/AspNetCore/Tag-Helpers/Index.md
index 2d8b465c23..c15c645feb 100644
--- a/docs/en/AspNetCore/Tag-Helpers/Index.md
+++ b/docs/en/AspNetCore/Tag-Helpers/Index.md
@@ -1,3 +1,3 @@
-## ABP Tag Helpers
+This document has moved.
-"ABP tag helpers" is not documented yet. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now.
\ No newline at end of file
+[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md)
diff --git a/docs/en/AspNetCore/Theming.md b/docs/en/AspNetCore/Theming.md
index 470ef1a458..716cf5fa9e 100644
--- a/docs/en/AspNetCore/Theming.md
+++ b/docs/en/AspNetCore/Theming.md
@@ -1,3 +1,4 @@
-# Theming
-TODO
\ No newline at end of file
+This document has moved.
+
+[Click to navigate to Theming document](../UI/AspNetCore/Theming.md)
\ No newline at end of file
diff --git a/docs/en/AspNetCore/Widgets.md b/docs/en/AspNetCore/Widgets.md
index f217e24816..562678c1d8 100644
--- a/docs/en/AspNetCore/Widgets.md
+++ b/docs/en/AspNetCore/Widgets.md
@@ -1,505 +1,4 @@
-# Widgets
-ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are especially useful when you want to;
+This document has moved.
-* Have **scripts & styles** dependencies for your widget.
-* Create **dashboards** with widgets used inside.
-* Define widgets in reusable **[modules](../Module-Development-Basics.md)**.
-* Co-operate widgets with **[authorization](../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems.
-
-## Basic Widget Definition
-
-### Create a View Component
-
-As the first step, create a new regular ASP.NET Core View Component:
-
-
-
-**MySimpleWidgetViewComponent.cs**:
-
-````csharp
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View();
- }
- }
-}
-````
-
-Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties.
-
-You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document fore all different usages.
-
-**Default.cshtml**:
-
-```xml
-
-
My Simple Widget
-
This is a simple widget!
-
-```
-
-### Define the Widget
-
-Add a `Widget` attribute to the `MySimpleWidgetViewComponent` class to mark this view component as a widget:
-
-````csharp
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- [Widget]
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View();
- }
- }
-}
-````
-
-## Rendering a Widget
-
-Rendering a widget is pretty standard. Use the `Component.InvokeAsync` method in a razor view/page as you do for any view component. Examples:
-
-````xml
-@await Component.InvokeAsync("MySimpleWidget")
-@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent))
-````
-
-First approach uses the widget name while second approach uses the view component type.
-
-### Widgets with Arguments
-
-ASP.NET Core's view component system allows you to accept arguments for view components. The sample view component below accepts `startDate` and `endDate` and uses these arguments to retrieve data from a service.
-
-````csharp
-using System;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
-
-namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget
-{
- [Widget]
- public class CountersWidgetViewComponent : AbpViewComponent
- {
- private readonly IDashboardAppService _dashboardAppService;
-
- public CountersWidgetViewComponent(IDashboardAppService dashboardAppService)
- {
- _dashboardAppService = dashboardAppService;
- }
-
- public async Task InvokeAsync(
- DateTime startDate, DateTime endDate)
- {
- var result = await _dashboardAppService.GetCountersWidgetAsync(
- new CountersWidgetInputDto
- {
- StartDate = startDate,
- EndDate = endDate
- }
- );
-
- return View(result);
- }
- }
-}
-````
-
-Now, you need to pass an anonymous object to pass arguments as shown below:
-
-````xml
-@await Component.InvokeAsync("CountersWidget", new
-{
- startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)),
- endDate = DateTime.Now
-})
-````
-
-## Widget Name
-
-Default name of the view components are calculated based on the name of the view component type. If your view component type is `MySimpleWidgetViewComponent` then the widget name will be `MySimpleWidget` (removes `ViewComponent` postfix). This is how ASP.NET Core calculates a view component's name.
-
-To customize widget's name, just use the standard `ViewComponent` attribute of ASP.NET Core:
-
-```csharp
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- [Widget]
- [ViewComponent(Name = "MyCustomNamedWidget")]
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View("~/Pages/Components/MySimpleWidget/Default.cshtml");
- }
- }
-}
-```
-
-ABP will respect to the custom name by handling the widget.
-
-> If the view component name and the folder name of the view component don't match, you may need to manually write the view path as done in this example.
-
-### Display Name
-
-You can also define a human-readable, localizable display name for the widget. This display name then can be used on the UI when needed. Display name is optional and can be defined using properties of the `Widget` attribute:
-
-````csharp
-using DashboardDemo.Localization;
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- [Widget(
- DisplayName = "MySimpleWidgetDisplayName", //Localization key
- DisplayNameResource = typeof(DashboardDemoResource) //localization resource
- )]
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View();
- }
- }
-}
-````
-
-See [the localization document](../Localization.md) to learn about localization resources and keys.
-
-## Style & Script Dependencies
-
-There are some challenges when your widget has script and style files;
-
-* Any page uses the widget should also include the **its script & styles** files into the page.
-* The page should also care about **depended libraries/files** of the widget.
-
-ABP solves these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget while using it.
-
-### Defining as Simple File Paths
-
-The example widget below adds a style and a script file:
-
-````csharp
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- [Widget(
- StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" },
- ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" }
- )]
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View();
- }
- }
-}
-````
-
-ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../Virtual-File-System.md).
-
-### Defining Bundle Contributors
-
-All resources for used widgets in a page are added as a **bundle** (bundled & minified in production if you don't configure otherwise). In addition to adding a simple file, you can take full power of the bundle contributors.
-
-The sample code below does the same with the code above, but defines and uses bundle contributors:
-
-````csharp
-using System.Collections.Generic;
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
-using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- [Widget(
- StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) },
- ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) }
- )]
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View();
- }
- }
-
- public class MySimpleWidgetStyleBundleContributor : BundleContributor
- {
- public override void ConfigureBundle(BundleConfigurationContext context)
- {
- context.Files
- .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css");
- }
- }
-
- public class MySimpleWidgetScriptBundleContributor : BundleContributor
- {
- public override void ConfigureBundle(BundleConfigurationContext context)
- {
- context.Files
- .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js");
- }
- }
-}
-
-````
-
-Bundle contribution system is very powerful. If your widget uses a JavaScript library to render a chart, then you can declare it as a dependency, so the JavaScript library is automatically added to the page if it wasn't added before. In this way, the page using your widget doesn't care about the dependencies.
-
-See the [bundling & minification](Bundling-Minification.md) documentation for more information about that system.
-
-## RefreshUrl
-
-A widget may design a `RefreshUrl` that is used whenever the widget needs to be refreshed. If it is defined, the widget is re-rendered on the server side on every refresh (see the refresh `method` of the `WidgetManager` below).
-
-````csharp
-[Widget(RefreshUrl = "Widgets/Counters")]
-public class CountersWidgetViewComponent : AbpViewComponent
-{
-
-}
-````
-
-Once you define a `RefreshUrl` for your widget, you need to provide an endpoint to render and return it:
-
-````csharp
-[Route("Widgets")]
-public class CountersWidgetController : AbpController
-{
- [HttpGet]
- [Route("Counters")]
- public IActionResult Counters(DateTime startDate, DateTime endDate)
- {
- return ViewComponent("CountersWidget", new {startDate, endDate});
- }
-}
-````
-
-`Widgets/Counters` route matches to the `RefreshUrl` declared before.
-
-> A widget supposed to be refreshed in two ways: In the first way, when you use a `RefreshUrl`, it re-rendered on the server and replaced by the HTML returned from server. In the second way the widget gets data (generally a JSON object) from server and refreshes itself in the client side (see the refresh method in the Widget JavaScript API section).
-
-## JavaScript API
-
-A widget may need to be rendered and refreshed in the client side. In such cases, you can use ABP's `WidgetManager` and define APIs for your widgets.
-
-### WidgetManager
-
-`WidgetManager` is used to initialize and refresh one or more widgets. Create a new `WidgetManager` as shown below:
-
-````js
-$(function() {
- var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea');
-})
-````
-
-`MyDashboardWidgetsArea` may contain one or more widgets inside.
-
-> Using the `WidgetManager` inside document.ready (like above) is a good practice since its functions use the DOM and need DOM to be ready.
-
-#### WidgetManager.init()
-
-`init` simply initializes the `WidgetManager` and calls `init` methods of the related widgets if they define (see Widget JavaScript API section below)
-
-```js
-myWidgetManager.init();
-```
-
-#### WidgetManager.refresh()
-
-`refresh` method refreshes all widgets related to this `WidgetManager`:
-
-````
-myWidgetManager.refresh();
-````
-
-#### WidgetManager Options
-
-WidgetManager has some additional options.
-
-##### Filter Form
-
-If your widgets require parameters/filters then you will generally have a form to filter the widgets. In such cases, you can create a form that has some form elements and a dashboard area with some widgets inside. Example:
-
-````xml
-
-
-
- ...widgets
-
-````
-
-`data-widget-filter` attribute relates the form with the widgets. Whenever the form is submitted, all the widgets are automatically refreshed with the form fields as the filter.
-
-Instead of the `data-widget-filter` attribute, you can use the `filterForm` parameter of the `WidgetManager` constructor. Example:
-
-````js
-var myWidgetManager = new abp.WidgetManager({
- wrapper: '#MyDashboardWidgetsArea',
- filterForm: '#MyDashboardFilterForm'
-});
-````
-
-##### Filter Callback
-
-You may want to have a better control to provide filters while initializing and refreshing the widgets. In this case, you can use the `filterCallback` option:
-
-````js
-var myWidgetManager = new abp.WidgetManager({
- wrapper: '#MyDashboardWidgetsArea',
- filterCallback: function() {
- return $('#MyDashboardFilterForm').serializeFormToObject();
- }
-});
-````
-
-This example shows the default implementation of the `filterCallback`. You can return any JavaScript object with fields. Example:
-
-````js
-filterCallback: function() {
- return {
- 'startDate': $('#StartDateInput').val(),
- 'endDate': $('#EndDateInput').val()
- };
-}
-````
-
-The returning filters are passed to all widgets on `init` and `refresh`.
-
-### Widget JavaScript API
-
-A widget can define a JavaScript API that is invoked by the `WidgetManager` when needed. The code sample below can be used to start to define an API for a widget.
-
-````js
-(function () {
- abp.widgets.NewUserStatisticWidget = function ($wrapper) {
-
- var getFilters = function () {
- return {
- ...
- };
- }
-
- var refresh = function (filters) {
- ...
- };
-
- var init = function (filters) {
- ...
- };
-
- return {
- getFilters: getFilters,
- init: init,
- refresh: refresh
- };
- };
-})();
-````
-
-`NewUserStatisticWidget` is the name of the widget here. It should match the widget name defined in the server side. All of the functions are optional.
-
-#### getFilters
-
-If the widget has internal custom filters, this function should return the filter object. Example:
-
-````js
-var getFilters = function() {
- return {
- frequency: $wrapper.find('.frequency-filter option:selected').val()
- };
-}
-````
-
-This method is used by the `WidgetManager` while building filters.
-
-#### init
-
-Used to initialize the widget when needed. It has a filter argument that can be used while getting data from server. `init` method is used when `WidgetManager.init()` function is called. It is also called if your widget requires a full re-load on refresh. See the `RefreshUrl` widget option.
-
-#### refresh
-
-Used to refresh the widget when needed. It has a filter argument that can be used while getting data from server. `refresh` method is used whenever `WidgetManager.refresh()` function is called.
-
-## Authorization
-
-Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute:
-
-* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application).
-* `RequiredPolicies` (`List`): A list of policy names to authorize the user. See [the authorization document](../Authorization.md) for more info about policies.
-
-Example:
-
-````csharp
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- [Widget(RequiredPolicies = new[] { "MyPolicyName" })]
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View();
- }
- }
-}
-````
-
-## WidgetOptions
-
-As alternative to the `Widget` attribute, you can use the `WidgetOptions` to configure widgets:
-
-```csharp
-Configure(options =>
-{
- options.Widgets.Add();
-});
-```
-
-Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `WidgetOptions`. Example configuration that adds a style for the widget:
-
-````csharp
-Configure(options =>
-{
- options.Widgets
- .Add()
- .WithStyles("/Pages/Components/MySimpleWidget/Default.css");
-});
-````
-
-> Tip: `WidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`.
-
-## See Also
-
-* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo).
\ No newline at end of file
+[Click to navigate to Widgets document](../UI/AspNetCore/Widgets.md)
diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md
index 6cf6917ba6..1bceda951f 100644
--- a/docs/en/Authorization.md
+++ b/docs/en/Authorization.md
@@ -1,6 +1,6 @@
# Authorization
-Authorization is used to check if a user is allowed to perform some specific operations in the application.
+Authorization is used to check if a user is allowed to perform some specific operations in the application.
ABP extends [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing authorization system to be usable in the **[application services](Application-Services.md)** too.
@@ -12,7 +12,7 @@ ASP.NET Core defines the [**Authorize**](https://docs.microsoft.com/en-us/aspnet
Example:
-````csharp
+```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -43,11 +43,11 @@ namespace Acme.BookStore
}
}
-````
+```
-* `Authorize` attribute forces the user to login into the application in order to use the `AuthorAppService` methods. So, `GetListAsync` method is only available to the authenticated users.
-* `AllowAnonymous` suppresses the authentication. So, `GetAsync` method is available to everyone including unauthorized users.
-* `[Authorize("BookStore_Author_Create")]` defines a policy (see [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies)) that is checked to authorize the current user.
+- `Authorize` attribute forces the user to login into the application in order to use the `AuthorAppService` methods. So, `GetListAsync` method is only available to the authenticated users.
+- `AllowAnonymous` suppresses the authentication. So, `GetAsync` method is available to everyone including unauthorized users.
+- `[Authorize("BookStore_Author_Create")]` defines a policy (see [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies)) that is checked to authorize the current user.
"BookStore_Author_Create" is an arbitrary policy name. If you declare an attribute like that, ASP.NET Core authorization system expects a policy to be defined before.
@@ -61,7 +61,7 @@ A permission is a simple policy that is granted or prohibited for a particular u
To define permissions, create a class inheriting from the `PermissionDefinitionProvider` as shown below:
-````csharp
+```csharp
using Volo.Abp.Authorization.Permissions;
namespace Acme.BookStore.Permissions
@@ -76,7 +76,7 @@ namespace Acme.BookStore.Permissions
}
}
}
-````
+```
> ABP automatically discovers this class. No additional configuration required!
@@ -86,8 +86,8 @@ When you define a permission, it becomes usable in the ASP.NET Core authorizatio

-* The "BookStore" group is shown as a new tab on the left side.
-* "BookStore_Author_Create" on the right side is the permission name. You can grant or prohibit it for the role.
+- The "BookStore" group is shown as a new tab on the left side.
+- "BookStore_Author_Create" on the right side is the permission name. You can grant or prohibit it for the role.
When you save the dialog, it is saved to the database and used in the authorization system.
@@ -97,7 +97,7 @@ When you save the dialog, it is saved to the database and used in the authorizat
"BookStore_Author_Create" is not a good permission name for the UI. Fortunately, `AddPermission` and `AddGroup` methods can take `LocalizableString` as second parameters:
-````csharp
+```csharp
var myGroup = context.AddGroup(
"BookStore",
LocalizableString.Create("BookStore")
@@ -107,14 +107,14 @@ myGroup.AddPermission(
"BookStore_Author_Create",
LocalizableString.Create("Permission:BookStore_Author_Create")
);
-````
+```
Then you can define texts for "BookStore" and "Permission:BookStore_Author_Create" keys in the localization file:
-````json
+```json
"BookStore": "Book Store",
"Permission:BookStore_Author_Create": "Creating a new author"
-````
+```
> For more information, see the [localization document](Localization.md) on the localization system.
@@ -126,21 +126,21 @@ The localized UI will be as seen below:
ABP supports [multi-tenancy](Multi-Tenancy.md) as a first class citizen. You can define multi-tenancy side option while defining a new permission. It gets one of the three values defined below:
-* **Host**: The permission is available only for the host side.
-* **Tenant**: The permission is available only for the tenant side.
-* **Both** (default): The permission is available both for tenant and host sides.
+- **Host**: The permission is available only for the host side.
+- **Tenant**: The permission is available only for the tenant side.
+- **Both** (default): The permission is available both for tenant and host sides.
> If your application is not multi-tenant, you can ignore this option.
To set the multi-tenancy side option, pass to the third parameter of the `AddPermission` method:
-````csharp
+```csharp
myGroup.AddPermission(
"BookStore_Author_Create",
LocalizableString.Create("Permission:BookStore_Author_Create"),
multiTenancySide: MultiTenancySides.Tenant //set multi-tenancy side!
);
-````
+```
#### Child Permissions
@@ -148,12 +148,12 @@ A permission may have child permissions. It is especially useful when you want t
Example definition:
-````csharp
+```csharp
var authorManagement = myGroup.AddPermission("Author_Management");
authorManagement.AddChild("Author_Management_Create_Books");
authorManagement.AddChild("Author_Management_Edit_Books");
authorManagement.AddChild("Author_Management_Delete_Books");
-````
+```
The result on the UI is shown below (you probably want to localize permissions for your application):
@@ -161,7 +161,7 @@ The result on the UI is shown below (you probably want to localize permissions f
For the example code, it is assumed that a role/user with "Author_Management" permission granted may have additional permissions. Then a typical application service that checks permissions can be defined as shown below:
-````csharp
+```csharp
[Authorize("Author_Management")]
public class AuthorAppService : ApplicationService, IAuthorAppService
{
@@ -193,10 +193,10 @@ public class AuthorAppService : ApplicationService, IAuthorAppService
...
}
}
-````
+```
-* `GetListAsync` and `GetAsync` will be available to users if they have `Author_Management` permission is granted.
-* Other methods require additional permissions.
+- `GetListAsync` and `GetAsync` will be available to users if they have `Author_Management` permission is granted.
+- Other methods require additional permissions.
### Overriding a Permission by a Custom Policy
@@ -204,13 +204,17 @@ If you define and register a policy to the ASP.NET Core authorization system wit
See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) document to learn how to define a custom policy.
+### Changing Permission Definitions of a Depended Module
+
+A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions.
+
## IAuthorizationService
ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization.
Example:
-````csharp
+```csharp
public async Task CreateAsync(CreateAuthorDto input)
{
var result = await AuthorizationService
@@ -223,7 +227,7 @@ public async Task CreateAsync(CreateAuthorDto input)
//continue to the normal flow...
}
-````
+```
> `AuthorizationService` is available as a property when you derive from ABP's `ApplicationService` base class. Since it is widely used in application services, `ApplicationService` pre-injects it for you. Otherwise, you can directly [inject](Dependency-Injection.md) it into your class.
@@ -231,14 +235,14 @@ Since this is a typical code block, ABP provides extension methods to simplify i
Example:
-````csharp
+```csharp
public async Task CreateAsync(CreateAuthorDto input)
{
await AuthorizationService.CheckAsync("Author_Management_Create_Books");
//continue to the normal flow...
}
-````
+```
`CheckAsync` extension method throws `AbpAuthorizationException` if the current user/client is not granted for the given permission. There is also `IsGrantedAsync` extension method that returns `true` or `false`.
@@ -250,11 +254,11 @@ public async Task CreateAsync(CreateAuthorDto input)
You may need to check a policy/permission on the client side. For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API. Example:
-````js
+```js
abp.auth.isGranted('MyPermissionName');
-````
+```
-See [abp.auth](AspNetCore/JavaScript-API/Auth.md) API documentation for details.
+See [abp.auth](API/JavaScript-API/Auth.md) API documentation for details.
## Permission Management
@@ -264,7 +268,7 @@ Permission management is normally done by an admin user using the permission man
If you need to manage permissions by code, inject the `IPermissionManager` and use as shown below:
-````csharp
+```csharp
public class MyService : ITransientDependency
{
private readonly IPermissionManager _permissionManager;
@@ -284,7 +288,7 @@ public class MyService : ITransientDependency
await _permissionManager.SetForUserAsync(userId, permissionName, false);
}
}
-````
+```
`SetForUserAsync` sets the value (true/false) for a permission of a user. There are more extension methods like `SetForRoleAsync` and `SetForClientAsync`.
@@ -296,15 +300,15 @@ public class MyService : ITransientDependency
Permission checking system is extensible. Any class derived from `PermissionValueProvider` (or implements `IPermissionValueProvider`) can contribute to the permission check. There are three pre-defined value providers:
-* `UserPermissionValueProvider` checks if the current user is granted for the given permission. It gets user id from the current claims. User claim name is defined with the `AbpClaimTypes.UserId` static property.
-* `RolePermissionValueProvider` checks if any of the roles of the current user is granted for the given permission. It gets role names from the current claims. Role claims name is defined with the `AbpClaimTypes.Role` static property.
-* `ClientPermissionValueProvider` checks if the current client is granted for the given permission. This is especially useful on a machine to machine interaction where there is no current user. It gets the client id from the current claims. Client claim name is defined with the `AbpClaimTypes.ClientId` static property.
+- `UserPermissionValueProvider` checks if the current user is granted for the given permission. It gets user id from the current claims. User claim name is defined with the `AbpClaimTypes.UserId` static property.
+- `RolePermissionValueProvider` checks if any of the roles of the current user is granted for the given permission. It gets role names from the current claims. Role claims name is defined with the `AbpClaimTypes.Role` static property.
+- `ClientPermissionValueProvider` checks if the current client is granted for the given permission. This is especially useful on a machine to machine interaction where there is no current user. It gets the client id from the current claims. Client claim name is defined with the `AbpClaimTypes.ClientId` static property.
You can extend the permission checking system by defining your own permission value provider.
Example:
-````csharp
+```csharp
public class SystemAdminPermissionValueProvider : PermissionValueProvider
{
public SystemAdminPermissionValueProvider(IPermissionStore permissionStore)
@@ -314,7 +318,7 @@ public class SystemAdminPermissionValueProvider : PermissionValueProvider
public override string Name => "SystemAdmin";
- public override async Task
+ public override async Task
CheckAsync(PermissionValueCheckContext context)
{
if (context.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin")
@@ -325,24 +329,24 @@ public class SystemAdminPermissionValueProvider : PermissionValueProvider
return PermissionGrantResult.Undefined;
}
}
-````
+```
This provider allows for all permissions to a user with a `User_Type` claim that has `SystemAdmin` value. It is common to use current claims and `IPermissionStore` in a permission value provider.
A permission value provider should return one of the following values from the `CheckAsync` method:
-* `PermissionGrantResult.Granted` is returned to grant the user for the permission. If any of the providers return `Granted`, the result will be `Granted`, if no other provider returns `Prohibited`.
-* `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return.
-* `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission.
+- `PermissionGrantResult.Granted` is returned to grant the user for the permission. If any of the providers return `Granted`, the result will be `Granted`, if no other provider returns `Prohibited`.
+- `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return.
+- `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission.
Once a provider is defined, it should be added to the `PermissionOptions` as shown below:
-````csharp
+```csharp
Configure(options =>
{
options.ValueProviders.Add();
});
-````
+```
### Permission Store
@@ -354,16 +358,17 @@ Configure(options =>
Use `IServiceCollection.AddAlwaysAllowAuthorization()` extension method to register the `AlwaysAllowAuthorizationService` to the [dependency injection](Dependency-Injection.md) system:
-````csharp
+```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
}
-````
+```
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](AspNetCore/JavaScript-API/Auth.md)
+* [ASP.NET Core MVC / Razor Pages JavaScript Auth API](API/JavaScript-API/Auth.md)
+* [Permission Management in Angular UI](UI/Angular/Permission-Management.md)
diff --git a/docs/en/Background-Jobs-Quartz.md b/docs/en/Background-Jobs-Quartz.md
new file mode 100644
index 0000000000..8039192708
--- /dev/null
+++ b/docs/en/Background-Jobs-Quartz.md
@@ -0,0 +1,73 @@
+# Quartz Background Job Manager
+
+[Quartz](https://www.quartz-scheduler.net/) is an advanced background job manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background job manager](Background-Jobs.md). In this way, you can use the same background job API for Quartz and your code will be independent of Quartz. If you like, you can directly use Quartz's API, too.
+
+> See the [background jobs document](Background-Jobs.md) to learn how to use the background job system. This document only shows how to install and configure the Quartz integration.
+
+## Installation
+
+It is suggested to use the [ABP CLI](CLI.md) to install this package.
+
+### Using the ABP CLI
+
+Open a command line window in the folder of the project (.csproj file) and type the following command:
+
+````bash
+abp add-package Volo.Abp.BackgroundJobs.Quartz
+````
+
+### Manual Installation
+
+If you want to manually install;
+
+1. Add the [Volo.Abp.BackgroundJobs.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.Quartz) NuGet package to your project:
+
+ ````
+ Install-Package Volo.Abp.BackgroundJobs.Quartz
+ ````
+
+2. Add the `AbpBackgroundJobsQuartzModule` to the dependency list of your module:
+
+````csharp
+[DependsOn(
+ //...other dependencies
+ typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
+ )]
+public class YourModule : AbpModule
+{
+}
+````
+
+## Configuration
+
+Quartz is a very configurable library,and the ABP framework provides `AbpQuartzPreOptions` for this. You can use the `PreConfigure` method in your module class to pre-configure this option. ABP will use it when initializing the Quartz module. For example:
+
+````csharp
+[DependsOn(
+ //...other dependencies
+ typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
+ )]
+public class YourModule : AbpModule
+{
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ var configuration = context.Services.GetConfiguration();
+
+ PreConfigure(options =>
+ {
+ options.Properties = new NameValueCollection
+ {
+ ["quartz.jobStore.dataSource"] = "BackgroundJobsDemoApp",
+ ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
+ ["quartz.jobStore.tablePrefix"] = "QRTZ_",
+ ["quartz.serializer.type"] = "json",
+ ["quartz.dataSource.BackgroundJobsDemoApp.connectionString"] = configuration.GetConnectionString("Quartz"),
+ ["quartz.dataSource.BackgroundJobsDemoApp.provider"] = "SqlServer",
+ ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
+ };
+ });
+ }
+}
+````
+
+Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html).
\ No newline at end of file
diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md
index 70f06bbadb..2bfdd87140 100644
--- a/docs/en/Background-Jobs.md
+++ b/docs/en/Background-Jobs.md
@@ -11,7 +11,7 @@ Background jobs are **persistent** that means they will be **re-tried** and **ex
## Abstraction Package
-ABP provides an **abstraction** module and **several implementations** for background jobs. It has a built-in/default implementation as well as Hangfire and RabbitMQ integrations.
+ABP provides an **abstraction** module and **several implementations** for background jobs. It has a built-in/default implementation as well as Hangfire, RabbitMQ and Quartz integrations.
`Volo.Abp.BackgroundJobs.Abstractions` nuget package provides needed services to create background jobs and queue background job items. If your module only depend on this package, it can be independent from the actual implementation/integration.
@@ -171,4 +171,7 @@ Background job system is extensible and you can change the default background jo
See pre-built job manager alternatives:
* [Hangfire Background Job Manager](Background-Jobs-Hangfire.md)
-* [RabbitMQ Background Job Manager](Background-Jobs-RabbitMq.md)
\ No newline at end of file
+* [RabbitMQ Background Job Manager](Background-Jobs-RabbitMq.md)
+
+## See Also
+* [Background Workers](Background-Workers.md)
\ No newline at end of file
diff --git a/docs/en/Background-Workers-Quartz.md b/docs/en/Background-Workers-Quartz.md
new file mode 100644
index 0000000000..0e9c640ee2
--- /dev/null
+++ b/docs/en/Background-Workers-Quartz.md
@@ -0,0 +1,68 @@
+# Quartz Background Worker Manager
+
+[Quartz](https://www.quartz-scheduler.net/) is an advanced background worker manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background worker manager](Background-Worker.md). ABP simply integrates quartz.
+
+## Installation
+
+It is suggested to use the [ABP CLI](CLI.md) to install this package.
+
+### Using the ABP CLI
+
+Open a command line window in the folder of the project (.csproj file) and type the following command:
+
+````bash
+abp add-package Volo.Abp.BackgroundWorkers.Quartz
+````
+
+### Manual Installation
+
+If you want to manually install;
+
+1. Add the [Volo.Abp.BackgroundWorkers.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Quartz) NuGet package to your project:
+
+ ````
+ Install-Package Volo.Abp.BackgroundWorkers.Quartz
+ ````
+
+2. Add the `AbpBackgroundWorkersQuartzModule` to the dependency list of your module:
+
+````csharp
+[DependsOn(
+ //...other dependencies
+ typeof(AbpBackgroundWorkersQuartzModule) //Add the new module dependency
+ )]
+public class YourModule : AbpModule
+{
+}
+````
+
+### Configuration
+
+See [Configuration](Background-Jobs-Quartz#Configuration).
+
+### Create a Background Worker
+
+A background work is a class that derives from the `QuartzBackgroundWorkerBase` base class. for example. A simple worker class is shown below:
+
+```` csharp
+public class MyLogWorker : QuartzBackgroundWorkerBase
+{
+ public MyLogWorker()
+ {
+ JobDetail = JobBuilder.Create().Build();
+ Trigger = TriggerBuilder.Create().StartNow().Build();
+ }
+
+ public override Task Execute(IJobExecutionContext context)
+ {
+ Logger.LogInformation("Executed MyLogWorker..!");
+ return Task.CompletedTask;
+ }
+}
+````
+
+We simply implemented the Execute method to write a log. The background worker is a **singleton by default**. If you want, you can also implement a [dependency interface](Dependency-Injection#DependencyInterfaces) to register it as another life cycle.
+
+### More
+
+Please see Quartz's [documentation](https://www.quartz-scheduler.net/documentation/index.html) for more information.
diff --git a/docs/en/Background-Workers.md b/docs/en/Background-Workers.md
new file mode 100644
index 0000000000..ff05a4162c
--- /dev/null
+++ b/docs/en/Background-Workers.md
@@ -0,0 +1,140 @@
+# Background Workers
+
+## Introduction
+
+Background workers are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples;
+
+* A background worker can run periodically to **delete old logs**.
+* A background worker can run periodically to **determine inactive users** and **send emails** to get users to return to your application.
+
+
+## Create a Background Worker
+
+A background worker should directly or indirectly implement the `IBackgroundWorker` interface.
+
+> A background worker is inherently [singleton](Dependency-Injection.md). So, only a single instance of your worker class is instantiated and run.
+
+### BackgroundWorkerBase
+
+`BackgroundWorkerBase` is an easy way to create a background worker.
+
+````csharp
+public class MyWorker : BackgroundWorkerBase
+{
+ public override Task StartAsync(CancellationToken cancellationToken = default)
+ {
+ //...
+ }
+
+ public override Task StopAsync(CancellationToken cancellationToken = default)
+ {
+ //...
+ }
+}
+````
+
+Start your worker in the `StartAsync` (which is called when the application begins) and stop in the `StopAsync` (which is called when the application shuts down).
+
+> You can directly implement the `IBackgroundWorker`, but `BackgroundWorkerBase` provides some useful properties like `Logger`.
+
+### AsyncPeriodicBackgroundWorkerBase
+
+Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below:
+
+````csharp
+public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
+{
+ public PassiveUserCheckerWorker(
+ AbpTimer timer,
+ IServiceScopeFactory serviceScopeFactory
+ ) : base(
+ timer,
+ serviceScopeFactory)
+ {
+ Timer.Period = 600000; //10 minutes
+ }
+
+ protected override async Task DoWorkAsync(
+ PeriodicBackgroundWorkerContext workerContext)
+ {
+ Logger.LogInformation("Starting: Setting status of inactive users...");
+
+ //Resolve dependencies
+ var userRepository = workerContext
+ .ServiceProvider
+ .GetRequiredService();
+
+ //Do the work
+ await userRepository.UpdateInactiveUserStatusesAsync();
+
+ Logger.LogInformation("Completed: Setting status of inactive users...");
+ }
+}
+````
+
+* `AsyncPeriodicBackgroundWorkerBase` uses the `AbpTimer` (a thread-safe timer) object to determine **the period**. We can set its `Period` property in the constructor.
+* It required to implement the `DoWorkAsync` method to **execute** the periodic work.
+* It is a good practice to **resolve dependencies** from the `PeriodicBackgroundWorkerContext` instead of constructor injection. Because `AsyncPeriodicBackgroundWorkerBase` uses a `IServiceScope` that is **disposed** when your work finishes.
+* `AsyncPeriodicBackgroundWorkerBase` **catches and logs exceptions** thrown by the `DoWorkAsync` method.
+
+
+## Register Background Worker
+
+After creating a background worker class, you should to add it to the `IBackgroundWorkerManager`. The most common place is the `OnApplicationInitialization` method of your module class:
+
+````csharp
+[DependsOn(typeof(AbpBackgroundWorkersModule))]
+public class MyModule : AbpModule
+{
+ public override void OnApplicationInitialization(
+ ApplicationInitializationContext context)
+ {
+ context.AddBackgroundWorker();
+ }
+}
+````
+
+`context.AddBackgroundWorker(...)` is a shortcut extension method for the expression below:
+
+````
+context.ServiceProvider
+ .GetRequiredService()
+ .Add(
+ context
+ .ServiceProvider
+ .GetRequiredService()
+ );
+````
+
+So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`.
+
+While we generally add workers in `OnApplicationInitialization`, there are no restrictions on that. You can inject `IBackgroundWorkerManager` anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down.
+
+## Options
+
+`AbpBackgroundWorkerOptions` class is used to [set options](Options.md) for the background workers. Currently, there is only one option:
+
+* `IsEnabled` (default: true): Used to **enable/disable** the background worker system for your application.
+
+> See the [Options](Options.md) document to learn how to set options.
+
+## Making Your Application Always Run
+
+Background workers only work if your application is running. If you host the background job execution in your web application (this is the default behavior), you should ensure that your web application is configured to always be running. Otherwise, background jobs only work while your application is in use.
+
+## Running On a Cluster
+
+Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example).
+
+If that's a problem for your workers, you have two options;
+
+* Disable the background worker system using the `AbpBackgroundWorkerOptions` described above, for all the application instances, except one of them.
+* Disable the background worker system for all the application instances and create another special application that runs on a single server and execute the workers.
+
+## Quartz Integration
+
+ABP Framework's background worker system is good to implement periodic tasks. However, you may want to use an advanced task scheduler like [Quartz](https://www.quartz-scheduler.net/). See the community contributed [quartz integration](Background-Workers-Quartz.md) for the background workers.
+
+## See Also
+* [Quartz Integration for the background workers](Background-Workers-Quartz.md)
+* [Background Jobs](Background-Jobs.md)
\ No newline at end of file
diff --git a/docs/en/Blog-Posts/2018-09-24-Announcement/Post.md b/docs/en/Blog-Posts/2018-09-24-Announcement/Post.md
index 437ee87387..dc6f5fa3e8 100644
--- a/docs/en/Blog-Posts/2018-09-24-Announcement/Post.md
+++ b/docs/en/Blog-Posts/2018-09-24-Announcement/Post.md
@@ -120,7 +120,7 @@ Dynamic bundling & minification system works on the virtual file system and allo
This code creates a new style bundle on the fly by including bootstrap (and its dependencies if there are) and two more css files. These files are bundled & minified on production environment, but will be added individually on the development environment.
-See [the documentation](https://github.com/abpframework/abp/blob/master/docs/AspNetCore/Bundling-Minification.md) for more.
+See [the documentation](https://github.com/abpframework/abp/blob/master/docs/UI/AspNetCore/Bundling-Minification.md) for more.
#### Distributed Event Bus
In current ABP, there is an IEventBus service to trigger and handle events inside the application. In addition to this local event bus, we are creating a distributed event bus abstraction (and RabbitMQ integration) to implement distributed messaging patterns.
diff --git a/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md b/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
index 4077633015..feeb3c2bb6 100644
--- a/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
+++ b/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
@@ -27,7 +27,7 @@ Angular was the first SPA UI option, but it is not the last. After v1.0 release,
### Widget System
-[Widget system](https://docs.abp.io/en/abp/latest/AspNetCore/Widgets) allows to **define and reuse** widgets for ASP.NET Core MVC applications. Widgets may have their own script and style resources and dependencies to 3rd-party libraries which are managed by the ABP framework.
+[Widget system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Widgets) allows to **define and reuse** widgets for ASP.NET Core MVC applications. Widgets may have their own script and style resources and dependencies to 3rd-party libraries which are managed by the ABP framework.
### Others
diff --git a/docs/en/CLI.md b/docs/en/CLI.md
index a1c53e0407..dcab1b672e 100644
--- a/docs/en/CLI.md
+++ b/docs/en/CLI.md
@@ -48,7 +48,10 @@ abp new Acme.BookStore
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `none`: Without UI. There are some additional options for this template:
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
- * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
+ * `--mobile` or `-m`: Specifies the mobile application framework. Default framework is `react-native`. Available frameworks:
+ * `none`: no mobile application.
+ * `react-native`: React Native.
+ * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). Additional options:
@@ -109,6 +112,7 @@ abp add-module Volo.Blogging
* `--solution` or `-s`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory.
* `--skip-db-migrations`: For EF Core database provider, it automatically adds a new code first migration (`Add-Migration`) and updates the database (`Update-Database`) if necessary. Specify this option to skip this operation.
* `-sp` or `--startup-project`: Relative path to the project folder of the startup project. Default value is the current folder.
+* `--with-source-code`: Add source code of the module instead of NuGet/NPM packages.
### update
@@ -140,7 +144,7 @@ abp switch-to-preview [options]
````
#### Options
-`--solution-path` or `-sp`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory.
+`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### switch-to-stable
@@ -153,7 +157,7 @@ abp switch-to-stable [options]
````
#### Options
-`--solution-path` or `-sp`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory.
+`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### login
diff --git a/docs/en/Customizing-Application-Modules-Extending-Entities.md b/docs/en/Customizing-Application-Modules-Extending-Entities.md
new file mode 100644
index 0000000000..def121910e
--- /dev/null
+++ b/docs/en/Customizing-Application-Modules-Extending-Entities.md
@@ -0,0 +1,149 @@
+# Customizing the Application Modules: Extending Entities
+
+In some cases, you may want to add some additional properties (and database fields) for an entity defined in a depended module. This section will cover some different approaches to make this possible.
+
+## Extra Properties
+
+[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these entities.
+
+Example:
+
+````csharp
+//SET AN EXTRA PROPERTY
+var user = await _identityUserRepository.GetAsync(userId);
+user.SetProperty("Title", "My custom title value!");
+await _identityUserRepository.UpdateAsync(user);
+
+//GET AN EXTRA PROPERTY
+var user = await _identityUserRepository.GetAsync(userId);
+return user.GetProperty("Title");
+````
+
+This approach is very easy to use and available out of the box. No extra code needed. You can store more than one property at the same time by using different property names (like `Title` here).
+
+Extra properties are stored as a single `JSON` formatted string value in the database for the EF Core. For MongoDB, they are stored as separate fields of the document.
+
+See the [entities document](Entities.md) for more about the extra properties system.
+
+> It is possible to perform a **business logic** based on the value of an extra property. You can **override** a service method and get or set the value as shown above. Overriding services will be discussed below.
+
+## Creating a New Entity Maps to the Same Database Table/Collection
+
+While using the extra properties approach is **easy to use** and suitable for some scenarios, it has some drawbacks described in the [entities document](Entities.md).
+
+Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database).
+
+`AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage **EF Core database migrations** in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems.
+
+## Creating a New Entity with Its Own Database Table/Collection
+
+Mapping your entity to an **existing table** of a depended module has a few disadvantages;
+
+* You deal with the **database migration structure** for EF Core. While it is possible, you should extra care about the migration code especially when you want to add **relations** between entities.
+* Your application database and the module database will be the **same physical database**. Normally, a module database can be separated if needed, but using the same table restricts it.
+
+If you want to **loose couple** your entity with the entity defined by the module, you can create your own database table/collection and map your entity to your own table in your own database.
+
+In this case, you need to deal with the **synchronization problems**, especially if you want to **duplicate** some properties/fields of the related entity. There are a few solutions;
+
+* If you are building a **monolithic** application (or managing your entity and the related module entity within the same process), you can use the [local event bus](Local-Event-Bus.md) to listen changes.
+* If you are building a **distributed** system where the module entity is managed (created/updated/deleted) on a different process/service than your entity is managed, then you can subscribe to the [distributed event bus](Distributed-Event-Bus.md) for change events.
+
+Once you handle the event, you can update your own entity in your own database.
+
+### Subscribing to Local Events
+
+[Local Event Bus](Local-Event-Bus.md) system is a way to publish and subscribe to events occurring in the same application.
+
+Assume that you want to get informed when a `IdentityUser` entity changes (created, updated or deleted). You can create a class that implements the `ILocalEventHandler>` interface.
+
+````csharp
+public class MyLocalIdentityUserChangeEventHandler :
+ ILocalEventHandler>,
+ ITransientDependency
+{
+ public async Task HandleEventAsync(EntityChangedEventData eventData)
+ {
+ var userId = eventData.Entity.Id;
+ var userName = eventData.Entity.UserName;
+
+ //...
+ }
+}
+````
+
+* `EntityChangedEventData` covers create, update and delete events for the given entity. If you need, you can subscribe to create, update and delete events individually (in the same class or different classes).
+* This code will be executed **out of the local transaction**, because it listens the `EntityChanged` event. You can subscribe to the `EntityChangingEventData` to perform your event handler in **the same local (in-process) transaction** if the current [unit of work](Unit-Of-Work.md) is transactional.
+
+> Reminder: This approach needs to change the `IdentityUser` entity in the same process contains the handler class. It perfectly works even for a clustered environment (when multiple instances of the same application are running on multiple servers).
+
+### Subscribing to Distributed Events
+
+[Distributed Event Bus](Distributed-Event-Bus.md) system is a way to publish an event in one application and receive the event in the same or different application running on the same or different server.
+
+Assume that you want to get informed when a `IdentityUser` entity created, updated or deleted. You can create a class like below:
+
+````csharp
+public class MyDistributedIdentityUserChangeEventHandler :
+ IDistributedEventHandler>,
+ IDistributedEventHandler>,
+ IDistributedEventHandler>,
+ ITransientDependency
+{
+ public async Task HandleEventAsync(EntityCreatedEto eventData)
+ {
+ if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser")
+ {
+ var userId = Guid.Parse(eventData.Entity.KeysAsString);
+ //...handle the "created" event
+ }
+ }
+
+ public async Task HandleEventAsync(EntityUpdatedEto eventData)
+ {
+ if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser")
+ {
+ var userId = Guid.Parse(eventData.Entity.KeysAsString);
+ //...handle the "updated" event
+ }
+ }
+
+ public async Task HandleEventAsync(EntityDeletedEto eventData)
+ {
+ if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser")
+ {
+ var userId = Guid.Parse(eventData.Entity.KeysAsString);
+ //...handle the "deleted" event
+ }
+ }
+}
+````
+
+* It implements multiple `IDistributedEventHandler` interfaces: **Created**, **Updated** and **Deleted**. Because, the distributed event bus system publishes events individually. There is no "Changed" event like the local event bus.
+* It subscribes to `EntityEto`, which is a generic event class that is **automatically published** for all type of entities by the ABP framework. This is why it checks the **entity type** (checking the entity type as string since we assume that there is no type safe reference to the `IdentityUser` entity).
+
+Pre-built application modules do not define specialized event types yet (like `IdentityUserEto` - "ETO" means "Event Transfer Object"). This feature is on the road map and will be available in a short term ([follow this issue](https://github.com/abpframework/abp/issues/3033)). Once it is implemented, you will be able to subscribe to individual entity types. Example:
+
+````csharp
+public class MyDistributedIdentityUserCreatedEventHandler :
+ IDistributedEventHandler>,
+ ITransientDependency
+{
+ public async Task HandleEventAsync(EntityCreatedEto eventData)
+ {
+ var userId = eventData.Entity.Id;
+ var userName = eventData.Entity.UserName;
+ //...handle the "created" event
+ }
+
+ //...
+}
+````
+
+* This handler is executed only when a new user has been created.
+
+> The only pre-defined specialized event class is the `UserEto`. For example, you can subscribe to the `EntityCreatedEto` to get notified when a user has created. This event also works for the Identity module.
+
+## See Also
+
+* [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md)
\ No newline at end of file
diff --git a/docs/en/Customizing-Application-Modules-Guide.md b/docs/en/Customizing-Application-Modules-Guide.md
new file mode 100644
index 0000000000..7f913f366a
--- /dev/null
+++ b/docs/en/Customizing-Application-Modules-Guide.md
@@ -0,0 +1,62 @@
+# Customizing the Existing Modules
+
+ABP Framework provides was designed to support to build fully [modular applications](Module-Development-Basics.md) and systems. It also provides some [pre-built application modules](Modules/Index.md) those are **ready to use** in any kind of application.
+
+For example, you can **re-use** the [Identity Management Module](Modules/Identity.md) to add user, role and permission management to your application. The [application startup template](Startup-Templates/Application.md) already comes with Identity and some other modules **pre-installed**.
+
+## Re-Using an Application Module
+
+You have two options to re-use an application module.
+
+### As Package References
+
+You can add **NuGet** & **NPM** package references of the related module to your application and configure the module (based on its documentation) to integrate to your application.
+
+As mentioned before, the [application startup template](Startup-Templates/Application.md) already comes with some **fundamental modules pre-installed**. It uses the modules as NuGet & NPM package references.
+
+This approach has the following benefits:
+
+* Your solution will be **clean** and only contains your **own application code**.
+* You can **easily upgrade** a module when a new version is available. `abp update` [CLI](CLI.md) command makes it even easier. In this way, you can continue to get **new features and bug fixes**.
+
+However, there is a drawback:
+
+* You may not able to **customize** the module source code as it is in your own solution.
+
+This document explains **how to customize or extend** a depended module without need to change its source code. While it is limited compared to a full source code change opportunity, there are still some good ways to make some customizations.
+
+If you don't think to make huge changes on the pre-built modules, re-using them as package reference is the recommended way.
+
+### Including the Source Code
+
+If you want to make **huge changes** or add **major features** on a pre-built module, but the available extension points are not enough, you can consider to directly work the source code of the depended module.
+
+In this case, you typically **add the source code** of the module to your solution and **replace package references** by local project references. **[ABP CLI](CLI.md)** automates this process for you.
+
+#### Separating the Module Solution
+
+You may prefer to not include the module source code **directly into your solution**. Every module consists of 10+ project files and adding **multiple modules** may impact on the **size** of your solution **load & development time.** Also, you may have different development teams working on different modules, so you don't want to make the module code available to the application development team.
+
+In any case, you can create a **separate solution** for the desired module and depend on the module as project references out of the solution. We do it like that for the [abp repository](https://github.com/abpframework/abp/).
+
+> One problem we see is Visual Studio doesn't play nice with this kind of approach (it doesn't support well to have references to local projects out of the solution directory). If you get error while building the application (depends on an external module), run `dotnet restore` in the command line after opening the application's solution in the Visual Studio.
+
+#### Publishing the Customized Module as Packages
+
+One alternative scenario could be re-packaging the module source code (as NuGet/NPM packages) and using as package references. You can use a local private NuGet/NPM server for your company.
+
+## Module Customization / Extending Approaches
+
+This section suggests some approaches if you decided to use pre-built application modules as NuGet/NPM package references. The following documents explain how to customize/extend existing modules in different ways:
+
+* [Extending Entities](Customizing-Application-Modules-Extending-Entities.md)
+* [Overriding Services](Customizing-Application-Modules-Overriding-Services.md)
+* [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md)
+
+### See Also
+
+Also, see the following documents:
+
+* See [the localization document](Localization.md) to learn how to extend existing localization resources.
+* See [the settings document](Settings.md) to learn how to change setting definitions of a depended module.
+* See [the authorization document](Authorization.md) to learn how to change permission definitions of a depended module.
\ No newline at end of file
diff --git a/docs/en/Customizing-Application-Modules-Overriding-Services.md b/docs/en/Customizing-Application-Modules-Overriding-Services.md
new file mode 100644
index 0000000000..16fbc9d2a4
--- /dev/null
+++ b/docs/en/Customizing-Application-Modules-Overriding-Services.md
@@ -0,0 +1,166 @@
+# Customizing the Application Modules: Overriding Services
+
+You may need to **change behavior (business logic)** of a depended module for your application. In this case, you can use the power of the [dependency injection system](Dependency-Injection.md) to replace a service, controller or even a page model of the depended module by your own implementation.
+
+**Replacing a service** is possible for any type of class registered to the dependency injection, including services of the ABP Framework.
+
+You have different options can be used based on your requirement those will be explained in the next sections.
+
+> Notice that some service methods may not be virtual, so you may not be able to override. We make all virtual by design. If you find any method that is not overridable, please [create an issue](https://github.com/abpframework/abp/issues/new) or do it yourself and send a **pull request** on GitHub.
+
+## Replacing an Interface
+
+If given service defines an interface, like the `IdentityUserAppService` class implements the `IIdentityAppService`, you can re-implement the same interface and replace the current implementation by your class. Example:
+
+````csharp
+public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency
+{
+ //...
+}
+````
+
+`MyIdentityUserAppService` replaces the `IIdentityUserAppService` by naming convention (since both ends with `IdentityUserAppService`). If your class name doesn't match, you need to manually expose the service interface:
+
+````csharp
+[ExposeServices(typeof(IIdentityUserAppService))]
+public class TestAppService : IIdentityUserAppService, ITransientDependency
+{
+ //...
+}
+````
+
+The dependency injection system allows to register multiple services for the same interface. The last registered one is used when the interface is injected. It is a good practice to explicitly replace the service.
+
+Example:
+
+````csharp
+[Dependency(ReplaceServices = true)]
+[ExposeServices(typeof(IIdentityUserAppService))]
+public class TestAppService : IIdentityUserAppService, ITransientDependency
+{
+ //...
+}
+````
+
+In this way, there will be a single implementation of the `IIdentityUserAppService` interface, while it doesn't change the result for this case. Replacing a service is also possible by code:
+
+````csharp
+context.Services.Replace(
+ ServiceDescriptor.Transient()
+);
+````
+
+You can write this inside the `ConfigureServices` method of your [module](Module-Development-Basics.md).
+
+## Overriding a Service Class
+
+In most cases, you will want to change one or a few methods of the current implementation for a service. Re-implementing the complete interface would not be efficient in this case. As a better approach, inherit from the original class and override the desired method.
+
+### Example: Overriding an Application Service
+
+````csharp
+[Dependency(ReplaceServices = true)]
+public class MyIdentityUserAppService : IdentityUserAppService
+{
+ //...
+ public MyIdentityUserAppService(
+ IdentityUserManager userManager,
+ IIdentityUserRepository userRepository,
+ IGuidGenerator guidGenerator
+ ) : base(
+ userManager,
+ userRepository,
+ guidGenerator)
+ {
+ }
+
+ public override async Task CreateAsync(IdentityUserCreateDto input)
+ {
+ if (input.PhoneNumber.IsNullOrWhiteSpace())
+ {
+ throw new AbpValidationException(
+ "Phone number is required for new users!",
+ new List
+ {
+ new ValidationResult(
+ "Phone number can not be empty!",
+ new []{"PhoneNumber"}
+ )
+ }
+ ); }
+
+ return await base.CreateAsync(input);
+ }
+}
+````
+
+This class **overrides** the `CreateAsync` method of the `IdentityUserAppService` [application service](Application-Services.md) to check the phone number. Then calls the base method to continue to the **underlying business logic**. In this way, you can perform additional business logic **before** and **after** the base logic.
+
+You could completely **re-write** the entire business logic for a user creation without calling the base method.
+
+### Example: Overriding a Domain Service
+
+````csharp
+[Dependency(ReplaceServices = true)]
+[ExposeServices(typeof(IdentityUserManager))]
+public class MyIdentityUserManager : IdentityUserManager
+{
+ public MyIdentityUserManager(
+ IdentityUserStore store,
+ IOptions optionsAccessor,
+ IPasswordHasher passwordHasher,
+ IEnumerable> userValidators,
+ IEnumerable> passwordValidators,
+ ILookupNormalizer keyNormalizer,
+ IdentityErrorDescriber errors,
+ IServiceProvider services,
+ ILogger logger,
+ ICancellationTokenProvider cancellationTokenProvider
+ ) : base(
+ store,
+ optionsAccessor,
+ passwordHasher,
+ userValidators,
+ passwordValidators,
+ keyNormalizer,
+ errors,
+ services,
+ logger,
+ cancellationTokenProvider)
+ {
+ }
+
+ public override async Task CreateAsync(IdentityUser user)
+ {
+ if (user.PhoneNumber.IsNullOrWhiteSpace())
+ {
+ throw new AbpValidationException(
+ "Phone number is required for new users!",
+ new List
+ {
+ new ValidationResult(
+ "Phone number can not be empty!",
+ new []{"PhoneNumber"}
+ )
+ }
+ );
+ }
+
+ return await base.CreateAsync(user);
+ }
+}
+````
+
+This example class inherits from the `IdentityUserManager` [domain service](Domain-Services.md) and overrides the `CreateAsync` method to perform the same phone number check implemented above. The result is same, but this time we've implemented it inside the domain service assuming that this is a **core domain logic** for our system.
+
+> `[ExposeServices(typeof(IdentityUserManager))]` attribute is **required** here since `IdentityUserManager` does not define an interface (like `IIdentityUserManager`) and dependency injection system doesn't expose services for inherited classes (like it does for the implemented interfaces) by convention.
+
+Check the [localization system](Localization.md) to learn how to localize the error messages.
+
+### Overriding Other Classes
+
+Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above.
+
+## How to Find the Services?
+
+[Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services.
\ No newline at end of file
diff --git a/docs/en/Customizing-Application-Modules-Overriding-User-Interface.md b/docs/en/Customizing-Application-Modules-Overriding-User-Interface.md
new file mode 100644
index 0000000000..afb701eacd
--- /dev/null
+++ b/docs/en/Customizing-Application-Modules-Overriding-User-Interface.md
@@ -0,0 +1,9 @@
+# Overriding the User Interface
+
+You may want to override a page, a component, a JavaScript, CSS or an image file of your depended module. Overriding the UI completely depends on the UI framework you're using. Select the UI framework to continue:
+
+* [ASP.NET Core (MVC / Razor Pages)](UI/AspNetCore/Customization-User-Interface.md)
+* [Angular](UI/Angular/Customization-User-Interface.md)
+
+
+
diff --git a/docs/en/Entities.md b/docs/en/Entities.md
index b5d196cccb..f0e6c7f301 100644
--- a/docs/en/Entities.md
+++ b/docs/en/Entities.md
@@ -295,6 +295,99 @@ All these base classes also have non-generic versions to take `AuditedEntity` an
All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser` and`FullAuditedAggregateRootWithUser`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern).
+## Extra Properties
+
+ABP defines the `IHasExtraProperties` interface that can be implemented by an entity to be able to dynamically set and get properties for the entity. `AggregateRoot` base class already implements the `IHasExtraProperties` interface. If you've derived from this class (or one of the related audit class defined above), you can directly use the API.
+
+### GetProperty & SetProperty Extension Methods
+
+These extension methods are the recommended way to get and set data for an entity. Example:
+
+````csharp
+public class ExtraPropertiesDemoService : ITransientDependency
+{
+ private readonly IIdentityUserRepository _identityUserRepository;
+
+ public ExtraPropertiesDemoService(IIdentityUserRepository identityUserRepository)
+ {
+ _identityUserRepository = identityUserRepository;
+ }
+
+ public async Task SetTitle(Guid userId, string title)
+ {
+ var user = await _identityUserRepository.GetAsync(userId);
+
+ //SET A PROPERTY
+ user.SetProperty("Title", title);
+
+ await _identityUserRepository.UpdateAsync(user);
+ }
+
+ public async Task GetTitle(Guid userId)
+ {
+ var user = await _identityUserRepository.GetAsync(userId);
+
+ //GET A PROPERTY
+ return user.GetProperty("Title");
+ }
+}
+````
+
+* Property's **value is object** and can be any type of object (string, int, bool... etc).
+* `GetProperty` returns `null` if given property was not set before.
+* You can store more than one property at the same time by using different **property names** (like `Title` here).
+
+It would be a good practice to **define a constant** for the property name to prevent typo errors. It would be even a better practice to **define extension methods** to take the advantage of the intellisense. Example:
+
+````csharp
+public static class IdentityUserExtensions
+{
+ private const string TitlePropertyName = "Title";
+
+ public static void SetTitle(this IdentityUser user, string title)
+ {
+ user.SetProperty(TitlePropertyName, title);
+ }
+
+ public static string GetTitle(this IdentityUser user)
+ {
+ return user.GetProperty(TitlePropertyName);
+ }
+}
+````
+
+Then you can directly use `user.SetTitle("...")` and `user.GetTitle()` for an `IdentityUser` object.
+
+### HasProperty & RemoveProperty Extension Methods
+
+* `HasProperty` is used to check if the object has a property set before.
+* `RemoveProperty` is used to remove a property from the object. You can use this instead of setting a `null` value.
+
+### How it is Implemented?
+
+`IHasExtraProperties` interface requires to define a `Dictionary` property, named `ExtraProperties`, for the implemented class.
+
+So, you can directly use the `ExtraProperties` property to use the dictionary API, if you like. However, `SetProperty` and `GetProperty` methods are the recommended ways since they also check for `null`s.
+
+#### How is it Stored?
+
+The way to store this dictionary in the database depends on the database provider you're using.
+
+* For [Entity Framework Core](Entity-Framework-Core.md), it is stored in a single `ExtraProperties` field as a `JSON` string. Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core.
+* For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system.
+
+### Discussion for the Extra Properties
+
+Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way. You normally **don't need** to this system for your own entities, because it has the following drawbacks:
+
+* It is **not fully type safe**.
+* It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects.
+* It **doesn't create fields** in the database table for EF Core, so it will not be easy to create indexes or search/order by this field in the database side.
+
+### Extra Properties Behind Entities
+
+`IHasExtraProperties` is not restricted to be used with entities. You can implement this interface for any kind of class and use the `GetProperty`, `SetProperty` and other related methods.
+
## See Also
-* [Best practice guide to design the entities](Best-Practices/Entities.md)
\ No newline at end of file
+* [Best practice guide to design the entities](Best-Practices/Entities.md)
diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md
index 58773cb39f..23b17293f1 100644
--- a/docs/en/Entity-Framework-Core-Migrations.md
+++ b/docs/en/Entity-Framework-Core-Migrations.md
@@ -899,26 +899,7 @@ public class BookStoreDbMigratorModule : AbpModule
We had a reference to the `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` project from the `Acme.BookStore.Web` project, but hadn't added module dependency since we hadn't created it before. But, now we have it and we need to add `typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule)` to the dependency list of the `BookStoreWebModule` class.
-#### BookStoreDbMigrationService
-
-You need one final touch to the `BookStoreDbMigrationService` inside the `Acme.BookStore.Domain` project. It is currently designed to work with a single `IBookStoreDbSchemaMigrator` implementation, but now we have two.
-
-It injects `IBookStoreDbSchemaMigrator`. Replace it with an `IEnumerable` injection ([Dependency Injection System](Dependency-Injection.md) allows to inject multiple implementations of an interface just like that).
-
-Now, you have **a collection of schema migrators** instead of a single one. Find the lines like:
-
-````csharp
-await _dbSchemaMigrators.MigrateAsync();
-````
-
-change them to:
-
-````csharp
-foreach (var migrator in _dbSchemaMigrators)
-{
- await migrator.MigrateAsync();
-}
-````
+#### Run the Database Migrator!
You can run the `.DbMigrator` application to migrate & seed the databases. To test, you can delete both databases and run the `.DbMigrator` application again to see if it creates both of the databases.
diff --git a/docs/en/Getting-Started-Angular-Template.md b/docs/en/Getting-Started-Angular-Template.md
index bdc5658e55..22d672d97a 100644
--- a/docs/en/Getting-Started-Angular-Template.md
+++ b/docs/en/Getting-Started-Angular-Template.md
@@ -26,10 +26,10 @@ abp new Acme.BookStore -u angular
The created solution requires;
-* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
+* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
-* [Yarn v1.19+](https://yarnpkg.com/)
+* [Yarn v1.19+](https://classic.yarnpkg.com/)
### The Solution Structure
@@ -123,4 +123,4 @@ The startup template includes the **identity management** and **tenant managemen
### What's Next?
-* [Application development tutorial](Tutorials/Part-1.md?UI=NG)
+* [Application development tutorial](Tutorials/Part-1)
diff --git a/docs/en/Getting-Started-AspNetCore-Application.md b/docs/en/Getting-Started-AspNetCore-Application.md
index 4abcd039c9..f3fafca7df 100644
--- a/docs/en/Getting-Started-AspNetCore-Application.md
+++ b/docs/en/Getting-Started-AspNetCore-Application.md
@@ -4,7 +4,7 @@ This tutorial explains how to start ABP from scratch with minimal dependencies.
## Create A New Project
-1. Create a new AspNet Core Web Application from Visual Studio 2019 (16.3.0+):
+1. Create a new AspNet Core Web Application from Visual Studio 2019 (16.4.0+):

diff --git a/docs/en/Getting-Started-AspNetCore-MVC-Template.md b/docs/en/Getting-Started-AspNetCore-MVC-Template.md
index fd8ef3a81a..d074e8aaef 100644
--- a/docs/en/Getting-Started-AspNetCore-MVC-Template.md
+++ b/docs/en/Getting-Started-AspNetCore-MVC-Template.md
@@ -26,10 +26,10 @@ abp new Acme.BookStore
The created solution requires;
-* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
+* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
-* [Yarn v1.19+](https://yarnpkg.com/)
+* [Yarn v1.19+](https://classic.yarnpkg.com/)
### The Solution Structure
@@ -101,4 +101,4 @@ The startup template includes the **identity management** and **tenant managemen
### What's Next?
-* [Application development tutorial](Tutorials/Part-1.md?UI=MVC)
+* [Application development tutorial](Tutorials/Part-1.md)
diff --git a/docs/en/IdentityServer-Integration.md b/docs/en/IdentityServer-Integration.md
new file mode 100644
index 0000000000..da82d4bc6a
--- /dev/null
+++ b/docs/en/IdentityServer-Integration.md
@@ -0,0 +1,3 @@
+# IdentityServer Integration
+
+TODO
\ No newline at end of file
diff --git a/docs/en/Localization.md b/docs/en/Localization.md
index a9608482cb..11b838cf7f 100644
--- a/docs/en/Localization.md
+++ b/docs/en/Localization.md
@@ -46,7 +46,7 @@ public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
- Configure(options =>
+ Configure(options =>
{
// "YourRootNameSpace" is the root namespace of your project. It can be empty if your root namespace is empty.
options.FileSets.AddEmbedded("YourRootNameSpace");
@@ -67,7 +67,7 @@ In this example;
* Added a new localization resource with "en" (English) as the default culture.
* Used JSON files to store the localization strings.
-* JSON files are embedded into the assembly using `VirtualFileSystemOptions` (see [virtual file system](Virtual-File-System.md)).
+* JSON files are embedded into the assembly using `AbpVirtualFileSystemOptions` (see [virtual file system](Virtual-File-System.md)).
JSON files are located under "/Localization/Resources/Test" project folder as shown below:
@@ -190,4 +190,8 @@ Localize a string:
````js
var str = testResource('HelloWorld');
-````
\ No newline at end of file
+````
+
+## See Also
+
+* [Localization in Angular UI](UI/Angular/Localization.md)
\ No newline at end of file
diff --git a/docs/en/Microservice-Architecture.md b/docs/en/Microservice-Architecture.md
index ff85873ee7..64a47784bb 100644
--- a/docs/en/Microservice-Architecture.md
+++ b/docs/en/Microservice-Architecture.md
@@ -12,8 +12,8 @@ One of the major goals of the ABP framework is to provide a convenient infrastru
* Offers an [architectural model](Best-Practices/Module-Architecture.md) to develop your modules to be compatible to microservice development and deployment.
* Provides [best practices guide](Best-Practices/Index.md) to develop your module standards-compliance.
* Provides base infrastructure to implement [Domain Driven Design](Domain-Driven-Design.md) in your microservices.
-* Provide services to [automatically create REST-style APIs](AspNetCore/Auto-API-Controllers.md) from your application services.
-* Provide services to [automatically create C# API clients](AspNetCore/Dynamic-CSharp-API-Clients.md) that makes easy to consume your services from another service/application.
+* Provide services to [automatically create REST-style APIs](API/Auto-API-Controllers.md) from your application services.
+* Provide services to [automatically create C# API clients](API/Dynamic-CSharp-API-Clients.md) that makes easy to consume your services from another service/application.
* Provides a [distributed event bus](Event-Bus.md) to communicate your services.
* Provides many other services to make your daily development easier.
diff --git a/docs/en/Module-Development-Basics.md b/docs/en/Module-Development-Basics.md
index 8149ec2cb1..6aac90c294 100644
--- a/docs/en/Module-Development-Basics.md
+++ b/docs/en/Module-Development-Basics.md
@@ -2,7 +2,14 @@
## Introduction
-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 Framework was designed to support to build fully modular applications and systems where every module may have entities, services, database integration, APIs, UI components and so on;
+
+* This document introduces the basics of the module system.
+* [Module development best practice guide](Best-Practices/Index.md) explains some **best practices** to develop **re-usable application modules** based on **DDD** principles and layers. A module designed based on this guide will be **database independent** and can be deployed as a **microservice** if needed.
+* [Pre-built application modules](Modules/Index.md) are **ready to use** in any kind of application.
+* [Module startup template](Startup-Templates/Module.md) is a jump start way to **create a new module**.
+* [ABP CLI](CLI.md) has commands to support modular development.
+* All other framework features are compatible to the modularity system.
## Module Class
@@ -42,7 +49,7 @@ public class BlogModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
//Configure default connection string for the application
- Configure(options =>
+ Configure(options =>
{
options.ConnectionStrings.Default = "......";
});
diff --git a/docs/en/Modules/Account.md b/docs/en/Modules/Account.md
new file mode 100644
index 0000000000..6901b38f59
--- /dev/null
+++ b/docs/en/Modules/Account.md
@@ -0,0 +1,3 @@
+# Account Module
+
+TODO
\ No newline at end of file
diff --git a/docs/en/Modules/Blogging.md b/docs/en/Modules/Blogging.md
index 0d2ab35c21..32d2e023ea 100644
--- a/docs/en/Modules/Blogging.md
+++ b/docs/en/Modules/Blogging.md
@@ -1,3 +1,3 @@
-# IdentityServer Module
+# Blogging Module
TODO
\ No newline at end of file
diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md
index afcc1a9dd7..db752ceb74 100644
--- a/docs/en/Modules/Docs.md
+++ b/docs/en/Modules/Docs.md
@@ -20,23 +20,25 @@ When you use GitHub to store your docs, Docs Module supports versioning. If you
> Docs module follows the [module architecture best practices](../Best-Practices/Module-Architecture.md) guide.
+## Installation
+### 1- Download
-## Installation
+If you do not have an existing ABP project, this step shows you how to create a new project from [abp.io](https://abp.io) to add the Docs Module. If you already have an ABP project, you can skip this step.
-### 1- Download
+It is recommended to use ABP CLI to create new projects. Use the following command:
-If you do not have an existing ABP project, this step shows you how to create a new project from [abp.io](https://abp.io) to add the Docs Module. If you already have an ABP project, you can skip this step.
+`abp new Acme.MyProject`
-Navigate to https://abp.io/Templates. Enter your project name as `Acme.MyProject`, select `ASP.NET Core Mvc Application` and select `Entity Framework Core` for the database provider.
+You can also navigate to https://abp.io/get-started. Enter your project name as `Acme.MyProject`, other use default options.
-Note that this document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider.
+Note that this document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider.

### 2- Running The Empty Application
-After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Domain `, `EntityFrameworkCore` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**.
+After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `EntityFrameworkCore.DbMigations`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**.

@@ -45,16 +47,14 @@ The database connection string is located in `appsettings.json` of your `Acme.My
```json
{
"ConnectionStrings": {
- "Default": "Server=localhost;Database=MyProject;Trusted_Connection=True;MultipleActiveResultSets=true"
+ "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProject;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
```
+Run `Acme.MyProject.DbMigrator` project, it will be responsible for applying database migration and seed data. The database `MyProject` will be created in your database server.
-
-Open `Package Manager Console` in the Visual Studio and choose `src\Acme.MyProject.EntityFrameworkCore` as the default project. Run `Update-Database` command to create your new database. The database `MyProject` will be created in your database server.
-
-Now an empty ABP project has been created! You can now run your project and see the empty website.
+Now an empty ABP project has been created! You can now run your project and see the empty website.
To login your website enter `admin` as the username and `1q2w3E*` as the password.
@@ -62,36 +62,27 @@ To login your website enter `admin` as the username and `1q2w3E*` as the passwor
Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project.
-* [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) needs to be referenced to `Acme.MyProject.Domain` project.
+It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
- * Edit `Acme.MyProject.Domain.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest.
+`abp add-module Volo.Docs`
- ```csharp
-
- ```
-* [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) needs to be referenced to `Acme.MyProject.EntityFrameworkCore` project.
+Or you can also manually install nuget package to each project:
- - Edit `Acme.MyProject.EntityFrameworkCore.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest.
+* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project.
- ```csharp
-
- ```
-* [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) needs to be referenced to `Acme.MyProject.Application` project.
+ `Install-Package Volo.Docs.Domain`
- * Edit `Acme.MyProject.Application.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest.
+* Install [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget package to `Acme.MyProject.EntityFrameworkCore` project.
- ```csharp
-
- ```
-* [Volo.Docs.Web ](https://www.nuget.org/packages/Volo.Docs.Web/)needs to be referenced to `Acme.MyProject.Web` project.
+ `Install-Package Volo.Docs.EntityFrameworkCore`
- - Edit `Acme.MyProject.Web.csproj`file and add the below line to as a reference. Note that you need to change version (v0.9.0) to the latest.
+* Install [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget package to `Acme.MyProject.Application` project.
- ```csharp
-
- ```
+ `Install-Package Volo.Docs.Application`
+* Install [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Web` project.
+ `Install-Package Volo.Docs.Web`
### 3- Adding Module Dependencies
@@ -132,7 +123,6 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
}
```
-
* Open `MyProjectApplicationModule.cs`and add `typeof(DocsApplicationModule)` as shown below;
```csharp
@@ -157,7 +147,6 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
}
```
-
* Open `MyProjectWebModule.cs`and add `typeof(DocsWebModule)` as shown below;
```csharp
@@ -176,43 +165,62 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
}
```
-
-
### 4- Database Integration
#### 4.1- Entity Framework Integration
-If you choose Entity Framework as your database provider, you need to configure the Docs Module in your DbContext. To do this;
+If you choose Entity Framework as your database provider, you need to configure the Docs Module. To do this;
-- Open `MyProjectDbContext.cs` and add `modelBuilder.ConfigureDocs()` to the `OnModelCreating()`
+- Open `MyProjectMigrationsDbContext.cs` and add `builder.ConfigureDocs()` to the `OnModelCreating()`.
```csharp
- [ConnectionStringName("Default")]
- public class MyProjectDbContext : AbpDbContext
- {
- public MyProjectDbContext(DbContextOptions options)
- : base(options)
- {
-
- }
-
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- //...
- modelBuilder.ConfigureDocs();
- }
- }
+ public class MyProjectMigrationsDbContext : AbpDbContext
+ {
+ public MyProjectMigrationsDbContext(DbContextOptions options)
+ : base(options)
+ {
+
+ }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ /* Include modules to your migration db context */
+
+ builder.ConfigurePermissionManagement();
+ builder.ConfigureSettingManagement();
+ builder.ConfigureBackgroundJobs();
+ builder.ConfigureAuditLogging();
+ builder.ConfigureIdentity();
+ builder.ConfigureIdentityServer();
+ builder.ConfigureFeatureManagement();
+ builder.ConfigureTenantManagement();
+ builder.ConfigureDocs(); //Add this line to configure the Docs Module
+
+ /* Configure customizations for entities from the modules included */
+
+ builder.Entity(b =>
+ {
+ b.ConfigureCustomUserProperties();
+ });
+
+ /* Configure your own tables/entities inside the ConfigureQaDoc method */
+
+ builder.ConfigureMyProject();
+ }
+ }
```
-* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore` as default project. Then write the below command to add the migration for Docs Module.
+* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore.DbMigrations` as default project. Then write the below command to add the migration for Docs Module.
```csharp
add-migration Added_Docs_Module
```
- When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore\Migrations`.
+ When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore.DbMigrations\Migrations`.
- Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore` is still default project.
+ Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore.DbMigrations` is still default project.
```csharp
update-database
@@ -220,7 +228,6 @@ If you choose Entity Framework as your database provider, you need to configure
Finally, you can check your database to see the newly created tables. For example you can see `DocsProjects` table must be added to your database.
-
### 5- Linking Docs Module
The default route for Docs module is;
@@ -250,7 +257,7 @@ To add Docs module link to your application menu;
}
```
-The `Menu:Docs` keyword is a localization key. To localize the menu text, open `Localization\MyProject\en.json` in the project `Acme.MyProject.Domain`. And add the below line
+The `Menu:Docs` keyword is a localization key. To localize the menu text, open `Localization\MyProject\en.json` in the project `Acme.MyProject.Domain`. And add the below line
```json
"Menu:Docs": "Documents"
@@ -270,7 +277,7 @@ Final look of **en.json**
}
```
-The new menu item for Docs Module is added to the menu. Run your web application and browse to `http://localhost:YOUR_PORT_NUMBER/documents` URL.
+The new menu item for Docs Module is added to the menu. Run your web application and browse to `http://localhost:YOUR_PORT_NUMBER/documents` URL.
You will see a warning says;
@@ -316,23 +323,25 @@ You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documen
- ExtraProperties:
```json
- {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***"}
+ {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""}
```
Note that `GitHubAccessToken` is masked with `***`. It's a private token that you must get it from GitHub. See https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
-- MainWebsiteUrl: `/`
+- MainWebsiteUrl: `/`
- LatestVersionBranchName: `master`
For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table:
```mssql
-INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***"}', N'/', N'master', N'')
+INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""}', N'/', N'master', N'')
```
Be aware that `GitHubAccessToken` is masked. It's a private token and you must get your own token and replace the `***` string.
+Now you can run the application and navigate to `/Documents`.
+
#### Sample Project Record for "FileSystem"
You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store.
@@ -561,11 +570,34 @@ The upper sample `JSON` file renders the below navigation menu as `HTML`.

+Finally a new Docs Module is added to your project which is feeded with GitHub.
-Finally a new Docs Module is added to your project which is feeded with GitHub.
+## Full-Text Search(Elastic Search)
-## Next
+The Docs module supports full-text search using Elastic Search. It is not enabled by default. You can configure `DocsElasticSearchOptions` to enable it.
+
+```
+Configure(options =>
+{
+ options.Enable = true;
+ options.IndexName = "your_index_name"; //default IndexName is abp_documents
+});
+```
+
+The `Index` is automatically created after the application starts if the `Index` does not exist.
-Docs Module is also available as a standalone application. Check out [VoloDocs](../Apps/VoloDocs).
+`DefaultElasticClientProvider` is responsible for creating `IElasticClient`. By default, it reads Elastic Search's `Url` from `IConfiguration`.
+If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](Dependency-Injection.md) system.
+
+```
+{
+ "ElasticSearch": {
+ "Url": "http://localhost:9200"
+ }
+}
+```
+
+## Next
+Docs Module is also available as a standalone application. Check out [VoloDocs](../Apps/VoloDocs).
\ No newline at end of file
diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md
index de5e62f688..1e1e4e9f81 100644
--- a/docs/en/Multi-Tenancy.md
+++ b/docs/en/Multi-Tenancy.md
@@ -168,7 +168,7 @@ namespace MyCompany.MyProject
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
- Configure(options =>
+ Configure(options =>
{
options.Tenants = new[]
{
@@ -214,7 +214,7 @@ namespace MyCompany.MyProject
{
var configuration = BuildConfiguration();
- Configure(configuration);
+ Configure(configuration);
}
private static IConfigurationRoot BuildConfiguration()
@@ -302,7 +302,7 @@ TODO:...
Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box:
-* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be stay as the first contributor for security**.
+* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**.
* **QueryStringTenantResolver**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default.
* **RouteTenantResolver**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route.
* **HeaderTenantResolver**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default.
@@ -343,8 +343,10 @@ namespace MyCompany.MyProject
{
Configure(options =>
{
- //Subdomain format: {0}.mydomain.com (adding as the highest priority resolver)
- options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));
+ //Subdomain format: {0}.mydomain.com
+ //Adding as the second highest priority resolver after 'CurrentUserTenantResolveContributor' to
+ //ensure the user cannot impersonate a different tenant.
+ options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));
});
//...
@@ -355,7 +357,7 @@ namespace MyCompany.MyProject
{0} is the the placeholder to determine current tenant's unique name.
-Instead of ``options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut:
+Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut:
````C#
options.AddDomainTenantResolver("{0}.mydomain.com");
diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md
index ba32878ea6..567d043ef9 100644
--- a/docs/en/Samples/Microservice-Demo.md
+++ b/docs/en/Samples/Microservice-Demo.md
@@ -24,82 +24,15 @@ This sample aims to demonstrate a simple yet complete microservice solution;
The diagram below shows the system:
-
+
### Source Code
You can get the source code from [the GitHub repository](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo).
-### Status
-
-Initial version of this sample has been completed. Additional improvement are still in development.
-
## Running the Solution
-You can either run from the **source code** or from the pre-configured **docker-compose** file.
-
-### Using the Docker Containers
-
-#### Pre Requirements
-
-Running as docker containers is easier since all dependencies are pre-configured. You only need to install the [latest docker](https://docs.docker.com/compose/install/).
-
-#### Running Containers
-
-- Clone or download the [ABP repository](https://github.com/abpframework/abp).
-
-- Open a command line in the `samples/MicroserviceDemo` folder of the repository.
-
-- Pull images from Docker Hub:
-
- ```
- docker-compose -f docker-compose.yml -f docker-compose.migrations.yml pull
- ```
-
-- If you want to build images locally you may skip the above step and instead use build command:
-
- ```
- docker-compose -f docker-compose.yml -f docker-compose.migrations.yml build
- ```
-
- Building images may take a **long time** depending on your machine.
-
-- Restore SQL Server databases:
-
- ```
- docker-compose -f docker-compose.yml -f docker-compose.migrations.yml run restore-database
- ```
-
-- Start the containers:
-
- ```
- docker-compose up -d
- ```
-
-- Add this line to the end of your `hosts` file:
-
- ```
- 127.0.0.1 auth-server
- ```
-
- hosts file is located inside the `C:\Windows\System32\Drivers\etc\hosts` folder on Windows and `/etc/hosts` for Linux/MacOS.
-
-#### Run the Applications
-
-There are a few applications running in the containers you may want to explore:
-
-* Backend Admin Application (BackendAdminApp.Host): `http://localhost:51512`
- *(Used to manage users & products in the system)*
-* Public Web Site (PublicWebsite.Host): `http://localhost:51513`
- *(Used to list products and run/manage the blog module)*
-* Authentication Server (AuthServer.Host): `http://auth-server:51511/`
- *(Used as a single sign on and authentication server built with IdentityServer4)*
-* Kibana UI: `http://localhost:51510`
- *(Use to show/trace logs written by all services/applications/gateways)*
-
-### Running From the Source Code
-
-#### Pre Requirements
+### Pre Requirements
To be able to run the solution from source code, following tools should be installed and running on your computer:
@@ -110,19 +43,19 @@ To be able to run the solution from source code, following tools should be insta
* [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+
* [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (optional, recommended to show logs)
-#### Open & Build the Visual Studio Solution
+### Open & Build the Visual Studio Solution
* Open the `samples\MicroserviceDemo\MicroserviceDemo.sln` in Visual Studio 2017 (15.9.0+).
* Run `dotnet restore` from the command line inside the `samples\MicroserviceDemo` folder.
* Build the solution in Visual Studio.
-#### Create Databases
+### Create Databases
MongoDB database is created dynamically, however you need to create database schemas for SQL server databases. The solution is configured to use Entity Core Code First migrations, so you can easily create databases.
There are two SQL server databases in this solution.
-##### MsDemo_Identity Database
+#### MsDemo_Identity Database
* Right click to the `AuthServer.Host` project and click to the `Set as startup project`.
* Open the **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console)
@@ -131,7 +64,7 @@ There are two SQL server databases in this solution.

-##### MsDemo_ProductManagement
+#### MsDemo_ProductManagement
- Right click to the `ProductService.Host` project and click to the `Set as startup project`.
- Open the **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console)
@@ -140,12 +73,13 @@ There are two SQL server databases in this solution.

-#### Run Projects
+### Run Projects
Run the projects with the following order (right click to each project, set as startup project an press Ctrl+F5 to run without debug):
* AuthServer.Host
* IdentityService.Host
+* TenantManagementService.Host
* BloggingService.Host
* ProductService.Host
* InternalGateway.Host
@@ -160,7 +94,7 @@ When you run projects, they will add some initial demo data to their databases.
The Visual Studio solution consists of multiple projects each have different roles in the system:
-
+
### Applications
@@ -183,8 +117,9 @@ Gateways are used to provide a single entry point to the applications. It can al
Microservices have no UI, but exposes some REST APIs.
-- **IdentityService.Host**: Host the ABP Identity module which is used to manage users & roles. It has no additional service, but only hosts the Identity module's API.
-- **BloggingService.Host**: Host the ABP Blogging module which is used to manage blog & posts (a typical blog application). It has no additional service, but only hosts the Blogging module's API.
+- **IdentityService.Host**: Hosts the ABP Identity module which is used to manage users & roles. It has no additional service, but only hosts the Identity module's API.
+- **TenantManagementService.Host**: Hosts the ABP Tenant Management module which is used to manage roles. It has no additional service, but only hosts the Tenant Management module's API.
+- **BloggingService.Host**: Hosts the ABP Blogging module which is used to manage blog & posts (a typical blog application). It has no additional service, but only hosts the Blogging module's API.
- **ProductService.Host**: Hosts the Product module (that is inside the solution) which is used to manage products. It also contains the EF Core migrations to create/update the Product Management database schema.
### Modules
@@ -195,7 +130,7 @@ Microservices have no UI, but exposes some REST APIs.
This solution is using multiple databases:
-* **MsDemo_Identity**: An SQL database. Used **SQL Server** by default, but can be any DBMS supported by the EF Core. Shared by AuthServer and IdentityService. Also audit logs, permissions and settings are stored in this database (while they could easily have their own databases, shared the same database to keep it simple).
+* **MsDemo_Identity**: An SQL database. Used **SQL Server** by default, but can be any DBMS supported by the EF Core. Shared by AuthServer, IdentityService and the TenantManagementService. Also audit logs, permissions and settings are stored in this database (while they could have their own databases, shared the same database to keep it simple).
* **MsDemo_ProductManagement**: An SQL database. Again, used **SQL Server** by default, but can be any DBMS supported by the EF Core. Used by the ProductService as a dedicated database.
* **MsDemo_Blogging**: A **MongoDB** database. Used by the BloggingService.
* **Elasticsearch**: Used to write logs over Serilog.
@@ -341,7 +276,7 @@ Backend admin application uses the Identity and Product microservices for all op
##### HTTP Clients
-ABP application modules generally provides C# client libraries to consume services (APIs) easily (they generally uses the [Dynamic C# API Clients](../AspNetCore/Dynamic-CSharp-API-Clients.md) feature of the ABP framework). That means if you need to consume Identity service API, you can reference to its client package and easily use the APIs by provided interfaces.
+ABP application modules generally provides C# client libraries to consume services (APIs) easily (they generally uses the [Dynamic C# API Clients](../API/Dynamic-CSharp-API-Clients.md) feature of the ABP framework). That means if you need to consume Identity service API, you can reference to its client package and easily use the APIs by provided interfaces.
For that purpose, `BackendAdminAppHostModule` class declares dependencies for `AbpIdentityHttpApiClientModule` and `ProductManagementHttpApiClientModule`.
@@ -419,7 +354,7 @@ A screenshot from the Products page:
#### Using Microservices
-Publc web site application uses the Blogging and Product microservices for all operations, over the Public Web Site Gateway (PublicWebSiteGateway.Host).
+Public web site application uses the Blogging and Product microservices for all operations, over the Public Web Site Gateway (PublicWebSiteGateway.Host).
##### Remote End Point
@@ -1103,7 +1038,7 @@ Product Management is a module that consists of several layers and packages/proj
* `ProductManagement.Application` contains the implementation of application services.
* `ProductManagement.EntityFrameworkCore` contains DbConext and other EF Core related classes and configuration.
* `ProductManagement.HttpApi` contains API Controllers.
-* `ProductManagement.HttpApi.Client` contains C# proxies to directly use the HTTP API remotely. Uses [Dynamic C# API Clients](../AspNetCore/Dynamic-CSharp-API-Clients.md) feature of the ABP framework.
+* `ProductManagement.HttpApi.Client` contains C# proxies to directly use the HTTP API remotely. Uses [Dynamic C# API Clients](../API/Dynamic-CSharp-API-Clients.md) feature of the ABP framework.
* `ProductManagement.Web` contains the UI elements (pages, scripts, styles... etc).
By the help of this layering, it is possible to use the same module as a package reference in a monolithic application or use as a service that runs in another server. It is possible to separate UI (Web) and API layers, so they run in different servers.
@@ -1472,3 +1407,7 @@ ABP provides automatic audit logging which saves every request in detail (who is
All of the services and applications are configured to write audit logs. Audit logs are saved to the MsDemo_Identity SQL database. So, you can query all audit logs of all applications from a single point.
An Audit Log record has a `CorrelationId` property that can be used to track a request. When a service calls another service in a single web request, they both save audit logs with the same `CorrelationId`. See the `AbpAuditLogs` table in the database.
+
+### Multi-Tenancy
+
+The solution has been configured to provide a [multi-tenant](../Multi-Tenancy.md) system, where each tenant can have their isolated users, roles, permissions and other data.
\ No newline at end of file
diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md
index d25feb490a..3a5c6c3bb2 100644
--- a/docs/en/Startup-Templates/Application.md
+++ b/docs/en/Startup-Templates/Application.md
@@ -145,7 +145,7 @@ While creating database & applying migrations seems only necessary for relationa
This project is used to define your API Controllers.
-Most of time you don't need to manually define API Controllers since ABP's [Auto API Controllers](../AspNetCore/Auto-API-Controllers.md) feature creates them automagically based on your application layer. However, in case of you need to write API controllers, this is the best place to do it.
+Most of time you don't need to manually define API Controllers since ABP's [Auto API Controllers](../API/Auto-API-Controllers.md) feature creates them automagically based on your application layer. However, in case of you need to write API controllers, this is the best place to do it.
* Depends on the `.Application.Contracts` project to be able to inject the application service interfaces.
@@ -153,7 +153,7 @@ Most of time you don't need to manually define API Controllers since ABP's [Auto
This is a project that defines C# client proxies to use the HTTP APIs of the solution. You can share this library to 3rd-party clients, so they can easily consume your HTTP APIs in their Dotnet applications (For other type of applications, they can still use your APIs, either manually or using a tool in their own platform)
-Most of time you don't need to manually create C# client proxies, thanks to ABP's [Dynamic C# API Clients](../AspNetCore/Dynamic-CSharp-API-Clients.md) feature.
+Most of time you don't need to manually create C# client proxies, thanks to ABP's [Dynamic C# API Clients](../API/Dynamic-CSharp-API-Clients.md) feature.
`.HttpApi.Client.ConsoleTestApp` project is a console application created to demonstrate the usage of the client proxies.
@@ -270,4 +270,4 @@ The files under the `angular/src/environments` folder has the essential configur
## What's Next?
- See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template.
-- See the [ASP.NET Core MVC Tutorial](../Tutorials/Part-1.md?UI=MVC) to learn how to develop applications using this template.
+- See the [ASP.NET Core MVC Tutorial](../Tutorials/Part-1.md) to learn how to develop applications using this template.
diff --git a/docs/en/Startup-Templates/Module.md b/docs/en/Startup-Templates/Module.md
index 2b95e49a56..45e123971b 100644
--- a/docs/en/Startup-Templates/Module.md
+++ b/docs/en/Startup-Templates/Module.md
@@ -133,7 +133,7 @@ For the `.Web.Unified` application, there is a single database, named `YourProje
##### How to Run?
-Set it as the startup project, run `Update-Database` command for the EF Core from Package Manager Console and run your application. Default username is `admin` and password is `1q2w3E*`.
+Set `host/YourProjectName.Web.Unified` as the startup project, run `Update-Database` command for the EF Core from Package Manager Console and run your application. Default username is `admin` and password is `1q2w3E*`.
#### Separated Deployment & Databases Scenario
diff --git a/docs/en/Themes/Basic.md b/docs/en/Themes/Basic.md
new file mode 100644
index 0000000000..a0165c803a
--- /dev/null
+++ b/docs/en/Themes/Basic.md
@@ -0,0 +1,3 @@
+## Basic Theme
+
+TODO
\ No newline at end of file
diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md
index a6ceb26c83..20b64f3189 100644
--- a/docs/en/Tutorials/Part-1.md
+++ b/docs/en/Tutorials/Part-1.md
@@ -493,7 +493,7 @@ namespace Acme.BookStore
### Auto API Controllers
-We normally create **Controllers** to expose application services as **HTTP API** endpoints. This allows browsers or 3rd-party clients to call them via AJAX. ABP can [**automagically**](https://docs.abp.io/en/abp/latest/AspNetCore/Auto-API-Controllers) configures your application services as MVC API Controllers by convention.
+We normally create **Controllers** to expose application services as **HTTP API** endpoints. This allows browsers or 3rd-party clients to call them via AJAX. ABP can [**automagically**](https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers) configures your application services as MVC API Controllers by convention.
#### Swagger UI
@@ -698,8 +698,8 @@ Change the `Pages/Books/Index.cshtml` as following:
````
-* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) is used to add external **scripts** to the page. It has many additional features compared to standard `script` tag. It handles **minification** and **versioning**. See the [bundling & minification document](https://docs.abp.io/en/abp/latest/AspNetCore/Bundling-Minification) for details.
-* `abp-card` and `abp-table` are **tag helpers** for Twitter Bootstrap's [card component](http://getbootstrap.com/docs/4.1/components/card/). There are other useful tag helpers in ABP to easily use most of the [bootstrap](https://getbootstrap.com/) components. You can also use regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by help the of IntelliSense and compile time type checking. Further information, see the [tag helpers](https://docs.abp.io/en/abp/latest/AspNetCore/Tag-Helpers/Index) document.
+* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) is used to add external **scripts** to the page. It has many additional features compared to standard `script` tag. It handles **minification** and **versioning**. See the [bundling & minification document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification) for details.
+* `abp-card` and `abp-table` are **tag helpers** for Twitter Bootstrap's [card component](http://getbootstrap.com/docs/4.1/components/card/). There are other useful tag helpers in ABP to easily use most of the [bootstrap](https://getbootstrap.com/) components. You can also use regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by help the of IntelliSense and compile time type checking. Further information, see the [tag helpers](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Tag-Helpers/Index) document.
* You can **localize** the column names in the localization file as you did for the menu items above.
##### Add a Script File
@@ -727,7 +727,7 @@ $(function () {
* `abp.libs.datatables.createAjax` is a helper function to adapt ABP's dynamic JavaScript API proxies to [Datatable](https://datatables.net/)'s format.
* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirement to use it, but it simplifies the [Datatables](https://datatables.net/) configuration by providing conventional values for missing options.
-* `acme.bookStore.book.getList` is the function to get list of books (as described in [dynamic JavaScript proxies](#Dynamic JavaScript proxies)).
+* `acme.bookStore.book.getList` is the function to get list of books (as described in [dynamic JavaScript proxies](#dynamic-javascript-proxies)).
* See [Datatables documentation](https://datatables.net/manual/) for all configuration options.
It's end of this part. The final UI of this work is shown as below:
diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md
index 8408cf6bfb..08e17e0e3f 100644
--- a/docs/en/Tutorials/Part-2.md
+++ b/docs/en/Tutorials/Part-2.md
@@ -641,7 +641,6 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
= {
+ name: "Home",
+ path: "home",
+ children: [
+ {
+ name: "Dashboard",
+ path: "dashboard"
+ }
+ ]
+};
+
+this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig);
+// returns a state stream which emits after dispatch action is complete
+```
+
+### How to Add a New Route Configuration
+
+The `dispatchAddRoute` adds a new route to the configuration state in the `Store`. For this, the route config should be passed as the parameter of the method.
+
+```js
+// this.config is instance of ConfigStateService
+
+const newRoute: ABP.Route = {
+ name: "My New Page",
+ iconClass: "fa fa-dashboard",
+ path: "page",
+ invisible: false,
+ order: 2,
+ requiredPolicy: "MyProjectName::MyNewPage"
+};
+
+this.config.dispatchAddRoute(newRoute);
+// returns a state stream which emits after dispatch action is complete
+```
+
+The `newRoute` will be placed as at root level, i.e. without any parent routes and its url will be stored as `'/path'`.
+
+If you want **to add a child route, you can do this:**
+
+```js
+// this.config is instance of ConfigStateService
+
+const newRoute: ABP.Route = {
+ parentName: "AbpAccount::Login",
+ name: "My New Page",
+ iconClass: "fa fa-dashboard",
+ path: "page",
+ invisible: false,
+ order: 2,
+ requiredPolicy: "MyProjectName::MyNewPage"
+};
+
+this.config.dispatchAddRoute(newRoute);
+// returns a state stream which emits after dispatch action is complete
+```
+
+The `newRoute` will then be placed as a child of the parent route named `'AbpAccount::Login'` and its url will be set as `'/account/login/page'`.
+
+#### Route Configuration Properties
+
+Please refer to `ABP.Route` type for all the properties you can pass to `dispatchSetEnvironment` in its parameter. It can be found in the [common.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27).
+
+### How to Set the Environment
+
+The `dispatchSetEnvironment` places environment variables passed to it in the `Store` under the configuration state. Here is how it is used:
+
+```js
+// this.config is instance of ConfigStateService
+
+this.config.dispatchSetEnvironment({
+ /* environment properties here */
+});
+// returns a state stream which emits after dispatch action is complete
+```
+
+Note that **you do not have to call this method at application initiation**, because the environment variables are already being stored at start.
+
+#### Environment Properties
+
+Please refer to `Config.Environment` type for all the properties you can pass to `dispatchSetEnvironment` as parameter. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13).
diff --git a/docs/en/UI/Angular/Custom-Setting-Page.md b/docs/en/UI/Angular/Custom-Setting-Page.md
new file mode 100644
index 0000000000..7c51cf2cca
--- /dev/null
+++ b/docs/en/UI/Angular/Custom-Setting-Page.md
@@ -0,0 +1,42 @@
+# Custom Setting Page
+
+There are several settings tabs from different modules. You can add custom settings page to your project in 3 steps.
+
+1. Create a Component
+
+```js
+import { Select } from '@ngxs/store';
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-your-custom-settings',
+ template: `
+ custom-settings works!
+ `,
+})
+export class YourCustomSettingsComponent {
+ // Your component logic
+}
+```
+
+2. Add the `YourCustomSettingsComponent` to `declarations` and the `entryComponents` arrays in the `AppModule`.
+
+3. Open the `app.component.ts` and add the below content to the `ngOnInit`
+
+```js
+import { addSettingTab } from '@abp/ng.theme.shared';
+// ...
+
+ngOnInit() {
+ addSettingTab({
+ component: YourCustomSettingsComponent,
+ name: 'Type here the setting tab title (you can type a localization key, e.g: AbpAccount::Login',
+ order: 4,
+ requiredPolicy: 'type here a policy key'
+ });
+}
+```
+
+Navigate to `/setting-management` route to see the changes:
+
+
diff --git a/docs/en/UI/Angular/Customization-User-Interface.md b/docs/en/UI/Angular/Customization-User-Interface.md
new file mode 100644
index 0000000000..20809a1d87
--- /dev/null
+++ b/docs/en/UI/Angular/Customization-User-Interface.md
@@ -0,0 +1,3 @@
+# Angular User Interface Customization Guide
+
+* [Replacing a component](Component-Replacement.md)
\ No newline at end of file
diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md
new file mode 100644
index 0000000000..01394a3fea
--- /dev/null
+++ b/docs/en/UI/Angular/Localization.md
@@ -0,0 +1,136 @@
+# Localization
+
+Before you read about _the Localization Pipe_ and _the Localization Service_, you should know about localization keys.
+
+The Localization key format consists of 2 sections which are **Resource Name** and **Key**.
+`ResourceName::Key`
+
+> If you do not specify the resource name, it will be `defaultResourceName` which is declared in `environment.ts`
+
+```js
+const environment = {
+ //...
+ localization: {
+ defaultResourceName: 'MyProjectName',
+ },
+};
+```
+
+So these two are the same:
+
+```html
+
+```
+
+To use interpolation, you must give the values for interpolation as pipe parameters, for example:
+
+Localization data is stored in key-value pairs:
+
+```js
+{
+ //...
+ AbpAccount: { // AbpAccount is the resource name
+ Key: "Value",
+ PagerInfo: "Showing {0} to {1} of {2} entries"
+ }
+}
+```
+
+So we can use this key like this:
+
+```html
+
+
+
+```
+
+### Using the Localization Service
+
+First of all you should import the `LocalizationService` from **@abp/ng.core**
+
+```js
+import { LocalizationService } from '@abp/ng.core';
+
+class MyClass {
+ constructor(private localizationService: LocalizationService) {}
+}
+```
+
+After that, you are able to use localization service.
+
+> You can add interpolation parameters as arguments to `instant()` and `get()` methods.
+
+```js
+this.localizationService.instant('AbpIdentity::UserDeletionConfirmation', 'John');
+
+// with fallback value
+this.localizationService.instant(
+ { key: 'AbpIdentity::UserDeletionConfirmation', defaultValue: 'Default Value' },
+ 'John',
+);
+
+// Output
+// User 'John' will be deleted. Do you confirm that?
+```
+
+To get a localized text as [_Observable_](https://rxjs.dev/guide/observable) use `get` method instead of `instant`:
+
+```js
+this.localizationService.get('Resource::Key');
+
+// with fallback value
+this.localizationService.get({ key: 'Resource::Key', defaultValue: 'Default Value' });
+```
+
+### Using the Config State
+
+In order to you `getLocalization` method you should import ConfigState.
+
+```js
+import { ConfigState } from '@abp/ng.core';
+```
+
+Then you can use it as followed:
+
+```js
+this.store.selectSnapshot(ConfigState.getLocalization('ResourceName::Key'));
+```
+
+`getLocalization` method can be used with both `localization key` and [`LocalizationWithDefault`](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L34) interface.
+
+```js
+this.store.selectSnapshot(
+ ConfigState.getLocalization(
+ {
+ key: 'AbpIdentity::UserDeletionConfirmation',
+ defaultValue: 'Default Value',
+ },
+ 'John',
+ ),
+);
+```
+
+Localization resources are stored in the `localization` property of `ConfigState`.
+
+
+## See Also
+
+* [Localization in ASP.NET Core](../../Localization.md)
\ No newline at end of file
diff --git a/docs/en/UI/Angular/Permission-Management.md b/docs/en/UI/Angular/Permission-Management.md
new file mode 100644
index 0000000000..6e5c5d7155
--- /dev/null
+++ b/docs/en/UI/Angular/Permission-Management.md
@@ -0,0 +1,79 @@
+# Permission Management
+
+A permission is a simple policy that is granted or prohibited for a particular user, role or client. You can read more about [authorization in ABP](../../Authorization.md) document.
+
+You can get permission of authenticated user using `getGrantedPolicy` selector of `ConfigState`.
+
+You can get permission as boolean value from store:
+
+```js
+import { Store } from '@ngxs/store';
+import { ConfigState } from '../states';
+
+export class YourComponent {
+ constructor(private store: Store) {}
+
+ ngOnInit(): void {
+ const canCreate = this.store.selectSnapshot(ConfigState.getGrantedPolicy('AbpIdentity.Roles.Create'));
+ }
+
+ // ...
+}
+```
+
+Or you can get it via `ConfigStateService`:
+
+```js
+import { ConfigStateService } from '../services/config-state.service';
+
+export class YourComponent {
+ constructor(private configStateService: ConfigStateService) {}
+
+ ngOnInit(): void {
+ const canCreate = this.configStateService.getGrantedPolicy('AbpIdentity.Roles.Create');
+ }
+
+ // ...
+}
+```
+
+## Permission Directive
+
+You can use the `PermissionDirective` to manage visibility of a DOM Element accordingly to user's permission.
+
+```html
+
+ This content is only visible if the user has 'AbpIdentity.Roles' permission.
+
+```
+
+As shown above you can remove elements from DOM with `abpPermission` structural directive.
+
+The directive can also be used as an attribute directive but we recommend to you to use it as a structural directive.
+
+## Permission Guard
+
+You can use `PermissionGuard` if you want to control authenticated user's permission to access to the route during navigation.
+
+Add `requiredPolicy` to the `routes` property in your routing module.
+
+```js
+const routes: Routes = [
+ {
+ path: 'path',
+ component: YourComponent,
+ canActivate: [PermissionGuard],
+ data: {
+ routes: {
+ requiredPolicy: 'AbpIdentity.Roles.Create',
+ },
+ },
+ },
+];
+```
+
+Granted Policies are stored in the `auth` property of `ConfigState`.
+
+## What's Next?
+
+* [Component Replacement](./Component-Replacement.md)
\ No newline at end of file
diff --git a/docs/en/UI/Angular/images/component-replacement.gif b/docs/en/UI/Angular/images/component-replacement.gif
new file mode 100644
index 0000000000..3a88500e53
Binary files /dev/null and b/docs/en/UI/Angular/images/component-replacement.gif differ
diff --git a/docs/en/UI/Angular/images/custom-settings.png b/docs/en/UI/Angular/images/custom-settings.png
new file mode 100644
index 0000000000..05dd3f695a
Binary files /dev/null and b/docs/en/UI/Angular/images/custom-settings.png differ
diff --git a/docs/en/UI/AspNetCore/Bundling-Minification.md b/docs/en/UI/AspNetCore/Bundling-Minification.md
new file mode 100644
index 0000000000..5af3f177d5
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Bundling-Minification.md
@@ -0,0 +1,352 @@
+
+# ASP.NET Core MVC Bundling & Minification
+
+There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are:
+
+* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/).
+* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins.
+
+ABP offers a simple, dynamic, powerful, modular and built-in way.
+
+## Volo.Abp.AspNetCore.Mvc.UI.Bundling Package
+
+> This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually.
+
+Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project:
+
+````
+install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling
+````
+
+Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module:
+
+````C#
+using Volo.Abp.Modularity;
+using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
+
+namespace MyCompany.MyProject
+{
+ [DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
+ public class MyWebModule : AbpModule
+ {
+ //...
+ }
+}
+````
+
+## Razor Bundling Tag Helpers
+
+The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-style-bundle` tag helpers. Example:
+
+````html
+
+
+
+
+
+
+````
+
+This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It's very easy to understand how to use it. Let's see how it *works*:
+
+* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests.
+* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...).
+* The bundle files may be **physical** files or [**virtual/embedded** files](../../Virtual-File-System.md).
+* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment).
+
+### Importing The Bundling Tag Helpers
+
+> This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually.
+
+In order to use bundle tag helpers, you need to add it into your `_ViewImports.cshtml` file or into your page:
+
+````
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
+````
+
+### Unnamed Bundles
+
+The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example:
+
+````html
+
+
+
+
+ @if (ViewBag.IncludeCustomStyles != false)
+ {
+
+ }
+
+````
+
+This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not).
+
+Advantages of **unnamed** bundles:
+
+* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions.
+
+Advantages of **named** bundles:
+
+* Other **modules can contribute** to the bundle by its name (see the sections below).
+
+### Single File
+
+If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example:
+
+````xml
+
+````
+
+The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too.
+
+## Bundling Options
+
+If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../../Module-Development-Basics.md) class.
+
+### Creating A New Bundle
+
+Example usage:
+
+````C#
+[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
+public class MyWebModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options
+ .ScriptBundles
+ .Add("MyGlobalBundle", bundle => {
+ bundle.AddFiles(
+ "/libs/jquery/jquery.js",
+ "/libs/bootstrap/js/bootstrap.js",
+ "/libs/toastr/toastr.min.js",
+ "/scripts/my-global-scripts.js"
+ );
+ });
+ });
+ }
+}
+````
+
+> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`).
+
+After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example:
+
+````html
+
+````
+
+This time, no file defined in the tag helper definition because the bundle files are defined by the code.
+
+### Configuring An Existing Bundle
+
+ABP supports [modularity](../../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a depended module. Example:
+
+````C#
+[DependsOn(typeof(MyWebModule))]
+public class MyWebExtensionModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options
+ .ScriptBundles
+ .Configure("MyGlobalBundle", bundle => {
+ bundle.AddFiles(
+ "/scripts/my-extension-script.js"
+ );
+ });
+ });
+ }
+}
+````
+
+> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper.
+
+## Bundle Contributors
+
+Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases.
+
+An example bundle contributor that replaces bootstrap.css with a customized version:
+
+````C#
+public class MyExtensionGlobalStyleContributor : BundleContributor
+{
+ public override void ConfigureBundle(BundleConfigurationContext context)
+ {
+ context.Files.ReplaceOne(
+ "/libs/bootstrap/css/bootstrap.css",
+ "/styles/extensions/bootstrap-customized.css"
+ );
+ }
+}
+````
+
+Then you can use this contributor as like below:
+
+````C#
+services.Configure(options =>
+{
+ options
+ .ScriptBundles
+ .Configure("MyGlobalBundle", bundle => {
+ bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor));
+ });
+});
+````
+
+> You can also add contributors while creating a new bundle.
+
+Contributors can also be used in the bundle tag helpers. Example:
+
+````xml
+
+
+
+
+
+````
+
+`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle.
+
+### Contributor Dependencies
+
+A bundle contributor can have one or more dependencies to other contributors.
+Example:
+
+````C#
+[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency
+public class MyExtensionStyleBundleContributor : BundleContributor
+{
+ //...
+}
+````
+
+When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications.
+
+Creating contributors and defining dependencies is a way of organizing bundle creation across different modules.
+
+### Contributor Extensions
+
+In some advanced scenarios, you may want to do some additional configuration whenever a bundle contributor is used. Contributor extensions works seamlessly when the extended contributor is used.
+
+The example below adds some styles for prism.js library:
+
+````csharp
+public class MyPrismjsStyleExtension : BundleContributor
+{
+ public override void ConfigureBundle(BundleConfigurationContext context)
+ {
+ context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css");
+ }
+}
+````
+
+Then you can configure `BundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`.
+
+````csharp
+Configure(options =>
+{
+ options
+ .Extensions()
+ .Add();
+});
+````
+
+Whenever `PrismjsStyleBundleContributor` is added into a bundle, `MyPrismjsStyleExtension` will also be automatically added.
+
+### Accessing to the IServiceProvider
+
+While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method.
+
+### Standard Package Contributors
+
+Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package.
+
+There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself.
+
+````C#
+[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency
+public class MyExtensionStyleBundleContributor : BundleContributor
+{
+ //...
+}
+````
+
+Using the built-in contributors for standard packages;
+
+* Prevents you typing **the invalid resource paths**.
+* Prevents changing your contributor if the resource **path changes** (the dependant contributor will handle it).
+* Prevents multiple modules adding the **duplicate files**.
+* Manages **dependencies recursively** (adds dependencies of dependencies, if necessary).
+
+#### Volo.Abp.AspNetCore.Mvc.UI.Packages Package
+
+> This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually.
+
+Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package.
+To install it to your project:
+
+````
+install-package Volo.Abp.AspNetCore.Mvc.UI.Packages
+````
+
+Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module;
+
+````C#
+using Volo.Abp.Modularity;
+using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
+
+namespace MyCompany.MyProject
+{
+ [DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))]
+ public class MyWebModule : AbpModule
+ {
+ //...
+ }
+}
+````
+
+### Bundle Inheritance
+
+In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle.
+Example:
+
+````c#
+services.Configure(options =>
+{
+ options
+ .StyleBundles
+ .Add("MyTheme.MyGlobalBundle", bundle => {
+ bundle
+ .AddBaseBundles("MyGlobalBundle") //Can add multiple
+ .AddFiles(
+ "/styles/mytheme-global-styles.css"
+ );
+ });
+});
+````
+
+## Themes
+
+Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more.
+
+## Best Practices & Suggestions
+
+It's suggested to define multiple bundles for an application, each one is used for different purposes.
+
+* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them.
+* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice.
+* **Module bundles**: For shared resources among the pages of an individual module.
+* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice.
+
+Establish a balance between performance, network bandwidth usage and count of many bundles.
+
+## See Also
+
+* [Client Side Package Management](Client-Side-Package-Management.md)
+* [Theming](Theming.md)
diff --git a/docs/en/UI/AspNetCore/Client-Side-Package-Management.md b/docs/en/UI/AspNetCore/Client-Side-Package-Management.md
new file mode 100644
index 0000000000..63c5cec225
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Client-Side-Package-Management.md
@@ -0,0 +1,116 @@
+
+## ASP.NET Core MVC Client Side Package Management
+
+ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually.
+
+However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn.
+
+Finally, we suggest the [**Yarn**](https://classic.yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM.
+
+### @ABP NPM Packages
+
+ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state.
+
+One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions.
+
+To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs).
+
+The benefit of a **standard package** is:
+
+* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version.
+* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more.
+
+Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example:
+
+````
+{
+ ...
+ "dependencies": {
+ "@abp/bootstrap": "^1.0.0"
+ }
+}
+````
+
+It's suggested to depend on a standard package instead of directly depending on a third-party package.
+
+#### Package Installation
+
+After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies:
+
+````
+yarn
+````
+
+Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before.
+
+#### Package Contribution
+
+If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted:
+
+* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package).
+* It should be the **latest stable** version of the package.
+* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages.
+* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package.
+* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created.
+
+See current standard packages for examples.
+
+### Mapping The Library Resources
+
+Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project.
+
+Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers.
+
+ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies.
+
+The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options.
+
+#### Resource Mapping Definition File
+
+A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below:
+
+````js
+module.exports = {
+ aliases: {
+ "@node_modules": "./node_modules",
+ "@libs": "./wwwroot/libs"
+ },
+ clean: [
+ "@libs"
+ ],
+ mappings: {
+
+ }
+}
+````
+
+* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication.
+* **clean** section is a list of folders to clean before copying the files.
+* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package.
+
+An example mapping configuration is shown below:
+
+````js
+mappings: {
+ "@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/",
+ "@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/",
+ "@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/"
+}
+````
+
+#### Using The Gulp
+
+Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line:
+
+````
+gulp
+````
+
+When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file.
+
+> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively).
+
+#### See Also
+
+* [Bundling & Minification](Bundling-Minification.md)
+* [Theming](Theming.md)
diff --git a/docs/en/UI/AspNetCore/Customization-User-Interface.md b/docs/en/UI/AspNetCore/Customization-User-Interface.md
new file mode 100644
index 0000000000..c5c974e5eb
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Customization-User-Interface.md
@@ -0,0 +1,470 @@
+# ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide
+
+This document explains how to override the user interface of a depended [application module](../../Modules/Index.md) for ASP.NET Core MVC / Razor Page applications.
+
+## Overriding a Page
+
+This section covers the [Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/) development, which is the recommended approach to create server rendered user interface for ASP.NET Core. Pre-built modules typically uses the Razor Pages approach instead of the classic MVC pattern (next sections will cover the MVC pattern too).
+
+You typically have three kind of override requirement for a page:
+
+* Overriding **only the Page Model** (C#) side to perform additional logic without changing the page UI.
+* Overriding **only the Razor Page** (.chtml file) to change the UI without changing the c# behind the page.
+* **Completely overriding** the page.
+
+### Overriding a Page Model (C#)
+
+````csharp
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Identity;
+using Volo.Abp.Identity.Web.Pages.Identity.Users;
+
+namespace Acme.BookStore.Web.Pages.Identity.Users
+{
+ [Dependency(ReplaceServices = true)]
+ [ExposeServices(typeof(EditModalModel))]
+ public class MyEditModalModel : EditModalModel
+ {
+ public MyEditModalModel(
+ IIdentityUserAppService identityUserAppService,
+ IIdentityRoleAppService identityRoleAppService
+ ) : base(
+ identityUserAppService,
+ identityRoleAppService)
+ {
+ }
+
+ public override async Task OnPostAsync()
+ {
+ //TODO: Additional logic
+ await base.OnPostAsync();
+ //TODO: Additional logic
+ }
+ }
+}
+````
+
+* This class inherits from and replaces the `EditModalModel` for the users and overrides the `OnPostAsync` method to perform additional logic before and after the underlying code.
+* It uses `ExposeServices` and `Dependency` attributes to replace the class.
+
+### Overriding a Razor Page (.CSHTML)
+
+Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through the [Virtual File System](../../Virtual-File-System.md).
+
+Virtual File system allows us to **embed resources into assemblies**. In this way, pre-built modules define the razor pages inside their NuGet packages. When you depend a module, you can override any file added to the virtual file system by that module, including pages/views.
+
+#### Example
+
+This example overrides the **login page** UI defined by the [Account Module](../../Modules/Account.md).
+
+Physical files override the embedded files defined in the same location. The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path:
+
+
+
+You typically want to copy the original `.cshtml` file of the module, then make the necessary changes. You can find the original file [here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml). Do not copy the `Login.cshtml.cs` file which is the code behind file for the razor page and we don't want to override it yet (see the next section).
+
+That's all, you can change the file content however you like.
+
+### Completely Overriding a Razor Page
+
+You may want to completely override a page; the razor and the c# file related to the page.
+
+In such a case;
+
+1. Override the C# page model class just like described above, but don't replace the existing page model class.
+2. Override the Razor Page just described above, but also change the @model directive to point your new page model.
+
+#### Example
+
+This example overrides the **login page** defined by the [Account Module](../../Modules/Account.md).
+
+Create a page model class deriving from the ` LoginModel ` (defined in the ` Volo.Abp.Account.Web.Pages.Account ` namespace):
+
+````csharp
+public class MyLoginModel : LoginModel
+{
+ public MyLoginModel(
+ IAuthenticationSchemeProvider schemeProvider,
+ IOptions accountOptions
+ ) : base(
+ schemeProvider,
+ accountOptions)
+ {
+
+ }
+
+ public override Task OnPostAsync(string action)
+ {
+ //TODO: Add logic
+ return base.OnPostAsync(action);
+ }
+
+ //TODO: Add new methods and properties...
+}
+````
+
+You can override any method or add new properties/methods if needed.
+
+> Notice that we didn't use `[Dependency(ReplaceServices = true)]` or `[ExposeServices(typeof(LoginModel))]` since we don't want to replace the existing class in the dependency injection, we define a new one.
+
+Copy `Login.cshtml` file into your solution as just described above. Change the **@model** directive to point to the `MyLoginModel`:
+
+````xml
+@page
+...
+@model Acme.BookStore.Web.Pages.Account.MyLoginModel
+...
+````
+
+That's all! Make any change in the view and run your application.
+
+#### Replacing Page Model Without Inheritance
+
+You don't have to inherit from the original page model class (like done in the previous example). Instead, you can completely **re-implement** the page yourself. In this case, just derive from `PageModel`, `AbpPageModel` or any suitable base class you need.
+
+## Overriding a View Component
+
+The ABP Framework, pre-built themes and modules define some **re-usable view components**. These view components can be replaced just like a page described above.
+
+### Example
+
+The screenshot below was taken from the **basic theme** comes with the application startup template.
+
+
+
+[The basic theme](../../Themes/Basic.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it.
+
+First, create your logo and place under a folder in your web application. We used `wwwroot/logos/bookstore-logo.png` path. Then copy the Brand component's view ([from here](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml)) from the basic theme files under the `Themes/Basic/Components/Brand` folder. The result should be similar the picture below:
+
+
+
+Then change the `Default.cshtml` as you like. Example content can be like that:
+
+````xml
+
+
+
+````
+
+Now, you can run the application to see the result:
+
+
+
+If you need, you can also replace [the code behind c# class](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) of the component just using the dependency injection system.
+
+### Overriding the Theme
+
+Just as explained above, you can replace any component, layout or c# class of the used theme. See the [theming document](Theming.md) for more information on the theming system.
+
+## Overriding Static Resources
+
+Overriding a static embedded resource (like JavaScript, Css or image files) of a module is pretty easy. Just place a file in the same path in your solution and let the Virtual File System to handle it.
+
+## Manipulating the Bundles
+
+The [Bundling & Minification](Bundling-Minification.md) system provides an **extensible and dynamic** system to create **script** and **style** bundles. It allows you to extend and manipulate the existing bundles.
+
+### Example: Add a Global CSS File
+
+For example, ABP Framework defines a **global style bundle** which is added to every page (actually, added to the layout by the themes). Let's add a **custom style file** to the end of the bundle files, so we can override any global style.
+
+First, create a CSS file and locate it in a folder inside the `wwwroot`:
+
+
+
+Define some custom CSS rules inside the file. Example:
+
+````css
+.card-title {
+ color: orange;
+ font-size: 2em;
+ text-decoration: underline;
+}
+
+.btn-primary {
+ background-color: red;
+}
+````
+
+Then add this file to the standard global style bundle in the `ConfigureServices` method of your [module](../../Module-Development-Basics.md):
+
+````csharp
+Configure(options =>
+{
+ options.StyleBundles.Configure(
+ StandardBundles.Styles.Global, //The bundle name!
+ bundleConfiguration =>
+ {
+ bundleConfiguration.AddFiles("/styles/my-global-styles.css");
+ }
+ );
+});
+````
+
+#### The Global Script Bundle
+
+Just like the `StandardBundles.Styles.Global`, there is a `StandardBundles.Scripts.Global` that you can add files or manipulate the existing ones.
+
+### Example: Manipulate the Bundle Files
+
+The example above adds a new file to the bundle. You can do more if you create a **bundle contributor** class. Example:
+
+````csharp
+public class MyGlobalStyleBundleContributor : BundleContributor
+{
+ public override void ConfigureBundle(BundleConfigurationContext context)
+ {
+ context.Files.Clear();
+ context.Files.Add("/styles/my-global-styles.css");
+ }
+}
+````
+
+Then you can add the contributor to an existing bundle:
+
+````csharp
+Configure(options =>
+{
+ options.StyleBundles.Configure(
+ StandardBundles.Styles.Global,
+ bundleConfiguration =>
+ {
+ bundleConfiguration.AddContributors(typeof(MyGlobalStyleBundleContributor));
+ }
+ );
+});
+````
+
+It is not a good idea to clear all CSS files. In a real world scenario, you can find and replace a specific file with your own file.
+
+### Example: Add a JavaScript File for a Specific Page
+
+The examples above works with the global bundle added to the layout. What if you want to add a CSS/JavaScript file (or replace a file) for a specific page defines inside a depended module?
+
+Assume that you want to run a **JavaScript code** once the user enters to the **Role Management** page of the Identity Module.
+
+First, create a standard JavaScript file under the `wwwroot`, `Pages` or `Views` folder (ABP support to add static resources inside these folders by default). We prefer the `Pages/Identity/Roles` folder to follow the conventions:
+
+
+
+Content of the file is simple:
+
+````js
+$(function() {
+ abp.log.info('My custom role script file has been loaded!');
+});
+````
+
+Then add this file to the bundle of the role management page:
+
+````csharp
+Configure(options =>
+{
+ options.ScriptBundles
+ .Configure(
+ typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName,
+ bundleConfig =>
+ {
+ bundleConfig.AddFiles("/Pages/Identity/Roles/my-role-script.js");
+ });
+});
+````
+
+`typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName` is the safe way to get the bundle name for the role management page.
+
+> Notice that not every page defines such page bundles. They define only if needed.
+
+In addition to adding new CSS/JavaScript file to a page, you also can replace the existing one (by defining a bundle contributor).
+
+## Layout Customization
+
+Layouts are defined by the theme ([see the theming](Theming.md)) by design. They are not included in a downloaded application solution. In this way you can easily **upgrade** the theme and get new features. You can not **directly change** the layout code in your application unless you replace it by your own layout (will be explained in the next sections).
+
+There are some common ways to **customize the layout** described in the next sections.
+
+### Menu Contributors
+
+There are two **standard menus** defined by the ABP Framework:
+
+
+
+* `StandardMenus.Main`: The main menu of the application.
+* `StandardMenus.User`: The user menu (generally at the top right of the screen).
+
+Rendering the menus is a responsibility of the theme, but **menu items** are determined by the modules and your application code. Just implement the `IMenuContributor` interface and **manipulate the menu items** in the `ConfigureMenuAsync` method.
+
+Menu contributors are executed whenever need to render the menu. There is already a menu contributor defined in the **application startup template**, so you can take it as an example and improve if necessary. See the [navigation menu](Navigation-Menu.md) document for more.
+
+### Toolbar Contributors
+
+[Toolbar system](Toolbars.md) is used to define **toolbars** on the user interface. Modules (or your application) can add **items** to a toolbar, then the theme renders the toolbar on the **layout**.
+
+There is only one **standard toolbar** (named "Main" - defined as a constant: `StandardToolbars.Main`). For the basic theme, it is rendered as shown below:
+
+In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here.
+
+#### Example: Add a Notification Icon
+
+In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **view component**. So, first, create a new view component in your project:
+
+
+
+**NotificationViewComponent.cs**
+
+````csharp
+public class NotificationViewComponent : AbpViewComponent
+{
+ public async Task InvokeAsync()
+ {
+ return View("/Pages/Shared/Components/Notification/Default.cshtml");
+ }
+}
+````
+
+**Default.cshtml**
+
+````xml
+
+
+
+````
+
+Now, we can create a class implementing the `IToolbarContributor` interface:
+
+````csharp
+public class MyToolbarContributor : IToolbarContributor
+{
+ public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
+ {
+ if (context.Toolbar.Name == StandardToolbars.Main)
+ {
+ context.Toolbar.Items
+ .Insert(0, new ToolbarItem(typeof(NotificationViewComponent)));
+ }
+
+ return Task.CompletedTask;
+ }
+}
+````
+
+This class adds the `NotificationViewComponent` as the first item in the `Main` toolbar.
+
+Finally, you need to add this contributor to the `AbpToolbarOptions`, in the `ConfigureServices` of your module:
+
+````csharp
+Configure(options =>
+{
+ options.Contributors.Add(new MyToolbarContributor());
+});
+````
+
+That's all, you will see the notification icon on the toolbar when you run the application:
+
+
+
+`NotificationViewComponent` in this sample simply returns a view without any data. In real life, you probably want to **query database** (or call an HTTP API) to get notifications and pass to the view. If you need, you can add a `JavaScript` or `CSS` file to the global bundle (as described before) for your toolbar item.
+
+See the [toolbars document](Toolbars.md) for more about the toolbar system.
+
+### Layout Hooks
+
+[Layout Hooks](Layout-Hooks.md) system allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Then you can then add a **view component** into a hook point.
+
+#### Example: Add Google Analytics Script
+
+Assume that you need to add the Google Analytics script to the layout (that will be available for all the pages). First, **create a view component** in your project:
+
+
+
+**NotificationViewComponent.cs**
+
+````csharp
+public class GoogleAnalyticsViewComponent : AbpViewComponent
+{
+ public IViewComponentResult Invoke()
+ {
+ return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml");
+ }
+}
+````
+
+**Default.cshtml**
+
+````html
+
+````
+
+Change `UA-xxxxxx-1` with your own code.
+
+You can then add this component to any of the hook points in the `ConfigureServices` of your module:
+
+````csharp
+Configure(options =>
+{
+ options.Add(
+ LayoutHooks.Head.Last, //The hook name
+ typeof(GoogleAnalyticsViewComponent) //The component to add
+ );
+});
+````
+
+Now, the GA code will be inserted in the `head` of the page as the last item. You (or the modules you are using) can add multiple items to the same hook. All of them will be added to the layout.
+
+The configuration above adds the `GoogleAnalyticsViewComponent` to all layouts. You may want to only add to a specific layout:
+
+````csharp
+Configure(options =>
+{
+ options.Add(
+ LayoutHooks.Head.Last,
+ typeof(GoogleAnalyticsViewComponent),
+ layout: StandardLayouts.Application //Set the layout to add
+ );
+});
+````
+
+See the layouts section below to learn more about the layout system.
+
+### Layouts
+
+Layout system allows themes to define standard, named layouts and allows any page to select a proper layout for its purpose. There are three pre-defined layouts:
+
+* "**Application**": The main (and the default) layout for an application. It typically contains header, menu (sidebar), footer, toolbar... etc.
+* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default.
+* "**Empty**": Empty and minimal layout.
+
+These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are standard layout names and implemented by all the themes out of the box.
+
+#### Layout Location
+
+You can find the layout files [here](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary.
+
+#### ITheme
+
+ABP Framework uses the `ITheme` service to get the layout location by the layout name. You can replace this service to dynamically select the layout location.
+
+#### IThemeManager
+
+`IThemeManager` is used to obtain the current theme and get the layout path. Any page can determine the layout of its own. Example:
+
+````html
+@using Volo.Abp.AspNetCore.Mvc.UI.Theming
+@inject IThemeManager ThemeManager
+@{
+ Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty);
+}
+````
+
+This page will use the empty layout. You use `ThemeManager.CurrentTheme.GetEmptyLayout();` extension method as a shortcut.
+
+If you want to set the layout for all the pages under a specific folder, then write the code above in a `_ViewStart.cshtml` file under that folder.
diff --git a/docs/en/UI/AspNetCore/Layout-Hooks.md b/docs/en/UI/AspNetCore/Layout-Hooks.md
new file mode 100644
index 0000000000..49b334c6c0
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Layout-Hooks.md
@@ -0,0 +1,3 @@
+# Layout Hooks
+
+TODO
\ No newline at end of file
diff --git a/docs/en/UI/AspNetCore/Navigation-Menu.md b/docs/en/UI/AspNetCore/Navigation-Menu.md
new file mode 100644
index 0000000000..4e0164ec72
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Navigation-Menu.md
@@ -0,0 +1,3 @@
+# Navigation Menu
+
+TODO
\ No newline at end of file
diff --git a/docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md b/docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md
new file mode 100644
index 0000000000..ae62c7f2b7
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md
@@ -0,0 +1,86 @@
+# Buttons
+
+## Introduction
+
+`abp-button` is the main element to create buttons.
+
+Basic usage:
+
+````xml
+Click Me
+````
+
+## Demo
+
+See the [buttons demo page](https://bootstrap-taghelpers.abp.io/Components/Buttons) to see it in action.
+
+## Attributes
+
+### button-type
+
+A value indicates the main style/type of the button. Should be one of the following values:
+
+* `Default` (default value)
+* `Primary`
+* `Secondary`
+* `Success`
+* `Danger`
+* `Warning`
+* `Info`
+* `Light`
+* `Dark`
+* `Outline_Primary`
+* `Outline_Secondary`
+* `Outline_Success`
+* `Outline_Danger`
+* `Outline_Warning`
+* `Outline_Info`
+* `Outline_Light`
+* `Outline_Dark`
+* `Link`
+
+### size
+
+A value indicates the size of the button. Should be one of the following values:
+
+* `Default` (default value)
+* `Small`
+* `Medium`
+* `Large`
+* `Block`
+* `Block_Small`
+* `Block_Medium`
+* `Block_Large`
+
+### busy-text
+
+A text that is shown when the button is busy.
+
+### text
+
+The text of the button. This is a shortcut if you simply want to set a text to the button. Example:
+
+````xml
+
+````
+
+In this case, you can use a self-closing tag to make it shorter.
+
+### icon
+
+Used to set an icon for the button. It works with the [Font Awesome](https://fontawesome.com/) icon classes by default. Example:
+
+````xml
+
+````
+
+##### icon-type
+
+If you don't want to use font-awesome, you have two options:
+
+1. Set `icon-type` to `Other` and write the CSS class of the font icon you're using.
+2. If you don't use a font icon use the opening and closing tags manually and write any code inside the tags.
+
+### disabled
+
+Set `true` to make the button initially disabled.
\ No newline at end of file
diff --git a/docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md b/docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md
new file mode 100644
index 0000000000..6d700f030d
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md
@@ -0,0 +1,3 @@
+## Dynamic Forms
+
+This is not documented yet. You can see a [demo](http://bootstrap-taghelpers.abp.io/Components/DynamicForms) for now.
\ No newline at end of file
diff --git a/docs/en/UI/AspNetCore/Tag-Helpers/Index.md b/docs/en/UI/AspNetCore/Tag-Helpers/Index.md
new file mode 100644
index 0000000000..d7e393933e
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Tag-Helpers/Index.md
@@ -0,0 +1,26 @@
+# ABP Tag Helpers
+
+ABP Framework defines a set of **tag helper components** to simply the user interface development for ASP.NET Core (MVC / Razor Pages) applications.
+
+## Bootstrap Component Wrappers
+
+Most of the tag helpers are [Bootstrap](https://getbootstrap.com/) (v4+) wrappers. Coding bootstrap is not so easy, not so type-safe and contains too much repetitive HTML tags. ABP Tag Helpers makes it **easier** and **type safe**.
+
+We don't aim to wrap bootstrap components 100%. Writing **native bootstrap style code** is still possible (actually, tag helpers generates native bootstrap code in the end), but we suggest to use the tag helpers wherever possible.
+
+ABP Framework also adds some **useful features** to the standard bootstrap components.
+
+Here, the list of components those are wrapped by the ABP Framework:
+
+* [Buttons](Buttons.md)
+* ...
+
+> Until all the tag helpers are documented, you can visit https://bootstrap-taghelpers.abp.io/ to see them with live samples.
+
+## Form Elements
+
+See [demo](https://bootstrap-taghelpers.abp.io/Components/FormElements).
+
+## Dynamic Inputs
+
+See [demo](https://bootstrap-taghelpers.abp.io/Components/DynamicForms).
\ No newline at end of file
diff --git a/docs/en/UI/AspNetCore/Theming.md b/docs/en/UI/AspNetCore/Theming.md
new file mode 100644
index 0000000000..e41545635c
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Theming.md
@@ -0,0 +1,3 @@
+# ASP.NET Core MVC / Razor Pages Theming
+
+TODO
\ No newline at end of file
diff --git a/docs/en/UI/AspNetCore/Toolbars.md b/docs/en/UI/AspNetCore/Toolbars.md
new file mode 100644
index 0000000000..cc3bcd95be
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Toolbars.md
@@ -0,0 +1,3 @@
+# Toolbars
+
+TODO
\ No newline at end of file
diff --git a/docs/en/UI/AspNetCore/Widgets.md b/docs/en/UI/AspNetCore/Widgets.md
new file mode 100644
index 0000000000..062fefb078
--- /dev/null
+++ b/docs/en/UI/AspNetCore/Widgets.md
@@ -0,0 +1,505 @@
+# Widgets
+
+ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are especially useful when you want to;
+
+* Have **scripts & styles** dependencies for your widget.
+* Create **dashboards** with widgets used inside.
+* Define widgets in reusable **[modules](../../Module-Development-Basics.md)**.
+* Co-operate widgets with **[authorization](../../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems.
+
+## Basic Widget Definition
+
+### Create a View Component
+
+As the first step, create a new regular ASP.NET Core View Component:
+
+
+
+**MySimpleWidgetViewComponent.cs**:
+
+````csharp
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+
+namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
+{
+ public class MySimpleWidgetViewComponent : AbpViewComponent
+ {
+ public IViewComponentResult Invoke()
+ {
+ return View();
+ }
+ }
+}
+````
+
+Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties.
+
+You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document fore all different usages.
+
+**Default.cshtml**:
+
+```xml
+
+
My Simple Widget
+
This is a simple widget!
+
+```
+
+### Define the Widget
+
+Add a `Widget` attribute to the `MySimpleWidgetViewComponent` class to mark this view component as a widget:
+
+````csharp
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
+{
+ [Widget]
+ public class MySimpleWidgetViewComponent : AbpViewComponent
+ {
+ public IViewComponentResult Invoke()
+ {
+ return View();
+ }
+ }
+}
+````
+
+## Rendering a Widget
+
+Rendering a widget is pretty standard. Use the `Component.InvokeAsync` method in a razor view/page as you do for any view component. Examples:
+
+````xml
+@await Component.InvokeAsync("MySimpleWidget")
+@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent))
+````
+
+First approach uses the widget name while second approach uses the view component type.
+
+### Widgets with Arguments
+
+ASP.NET Core's view component system allows you to accept arguments for view components. The sample view component below accepts `startDate` and `endDate` and uses these arguments to retrieve data from a service.
+
+````csharp
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget
+{
+ [Widget]
+ public class CountersWidgetViewComponent : AbpViewComponent
+ {
+ private readonly IDashboardAppService _dashboardAppService;
+
+ public CountersWidgetViewComponent(IDashboardAppService dashboardAppService)
+ {
+ _dashboardAppService = dashboardAppService;
+ }
+
+ public async Task InvokeAsync(
+ DateTime startDate, DateTime endDate)
+ {
+ var result = await _dashboardAppService.GetCountersWidgetAsync(
+ new CountersWidgetInputDto
+ {
+ StartDate = startDate,
+ EndDate = endDate
+ }
+ );
+
+ return View(result);
+ }
+ }
+}
+````
+
+Now, you need to pass an anonymous object to pass arguments as shown below:
+
+````xml
+@await Component.InvokeAsync("CountersWidget", new
+{
+ startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)),
+ endDate = DateTime.Now
+})
+````
+
+## Widget Name
+
+Default name of the view components are calculated based on the name of the view component type. If your view component type is `MySimpleWidgetViewComponent` then the widget name will be `MySimpleWidget` (removes `ViewComponent` postfix). This is how ASP.NET Core calculates a view component's name.
+
+To customize widget's name, just use the standard `ViewComponent` attribute of ASP.NET Core:
+
+```csharp
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
+{
+ [Widget]
+ [ViewComponent(Name = "MyCustomNamedWidget")]
+ public class MySimpleWidgetViewComponent : AbpViewComponent
+ {
+ public IViewComponentResult Invoke()
+ {
+ return View("~/Pages/Components/MySimpleWidget/Default.cshtml");
+ }
+ }
+}
+```
+
+ABP will respect to the custom name by handling the widget.
+
+> If the view component name and the folder name of the view component don't match, you may need to manually write the view path as done in this example.
+
+### Display Name
+
+You can also define a human-readable, localizable display name for the widget. This display name then can be used on the UI when needed. Display name is optional and can be defined using properties of the `Widget` attribute:
+
+````csharp
+using DashboardDemo.Localization;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
+{
+ [Widget(
+ DisplayName = "MySimpleWidgetDisplayName", //Localization key
+ DisplayNameResource = typeof(DashboardDemoResource) //localization resource
+ )]
+ public class MySimpleWidgetViewComponent : AbpViewComponent
+ {
+ public IViewComponentResult Invoke()
+ {
+ return View();
+ }
+ }
+}
+````
+
+See [the localization document](../../Localization.md) to learn about localization resources and keys.
+
+## Style & Script Dependencies
+
+There are some challenges when your widget has script and style files;
+
+* Any page uses the widget should also include the **its script & styles** files into the page.
+* The page should also care about **depended libraries/files** of the widget.
+
+ABP solves these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget while using it.
+
+### Defining as Simple File Paths
+
+The example widget below adds a style and a script file:
+
+````csharp
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
+{
+ [Widget(
+ StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" },
+ ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" }
+ )]
+ public class MySimpleWidgetViewComponent : AbpViewComponent
+ {
+ public IViewComponentResult Invoke()
+ {
+ return View();
+ }
+ }
+}
+````
+
+ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../../Virtual-File-System.md).
+
+### Defining Bundle Contributors
+
+All resources for used widgets in a page are added as a **bundle** (bundled & minified in production if you don't configure otherwise). In addition to adding a simple file, you can take full power of the bundle contributors.
+
+The sample code below does the same with the code above, but defines and uses bundle contributors:
+
+````csharp
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
+{
+ [Widget(
+ StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) },
+ ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) }
+ )]
+ public class MySimpleWidgetViewComponent : AbpViewComponent
+ {
+ public IViewComponentResult Invoke()
+ {
+ return View();
+ }
+ }
+
+ public class MySimpleWidgetStyleBundleContributor : BundleContributor
+ {
+ public override void ConfigureBundle(BundleConfigurationContext context)
+ {
+ context.Files
+ .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css");
+ }
+ }
+
+ public class MySimpleWidgetScriptBundleContributor : BundleContributor
+ {
+ public override void ConfigureBundle(BundleConfigurationContext context)
+ {
+ context.Files
+ .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js");
+ }
+ }
+}
+
+````
+
+Bundle contribution system is very powerful. If your widget uses a JavaScript library to render a chart, then you can declare it as a dependency, so the JavaScript library is automatically added to the page if it wasn't added before. In this way, the page using your widget doesn't care about the dependencies.
+
+See the [bundling & minification](Bundling-Minification.md) documentation for more information about that system.
+
+## RefreshUrl
+
+A widget may design a `RefreshUrl` that is used whenever the widget needs to be refreshed. If it is defined, the widget is re-rendered on the server side on every refresh (see the refresh `method` of the `WidgetManager` below).
+
+````csharp
+[Widget(RefreshUrl = "Widgets/Counters")]
+public class CountersWidgetViewComponent : AbpViewComponent
+{
+
+}
+````
+
+Once you define a `RefreshUrl` for your widget, you need to provide an endpoint to render and return it:
+
+````csharp
+[Route("Widgets")]
+public class CountersWidgetController : AbpController
+{
+ [HttpGet]
+ [Route("Counters")]
+ public IActionResult Counters(DateTime startDate, DateTime endDate)
+ {
+ return ViewComponent("CountersWidget", new {startDate, endDate});
+ }
+}
+````
+
+`Widgets/Counters` route matches to the `RefreshUrl` declared before.
+
+> A widget supposed to be refreshed in two ways: In the first way, when you use a `RefreshUrl`, it re-rendered on the server and replaced by the HTML returned from server. In the second way the widget gets data (generally a JSON object) from server and refreshes itself in the client side (see the refresh method in the Widget JavaScript API section).
+
+## JavaScript API
+
+A widget may need to be rendered and refreshed in the client side. In such cases, you can use ABP's `WidgetManager` and define APIs for your widgets.
+
+### WidgetManager
+
+`WidgetManager` is used to initialize and refresh one or more widgets. Create a new `WidgetManager` as shown below:
+
+````js
+$(function() {
+ var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea');
+})
+````
+
+`MyDashboardWidgetsArea` may contain one or more widgets inside.
+
+> Using the `WidgetManager` inside document.ready (like above) is a good practice since its functions use the DOM and need DOM to be ready.
+
+#### WidgetManager.init()
+
+`init` simply initializes the `WidgetManager` and calls `init` methods of the related widgets if they define (see Widget JavaScript API section below)
+
+```js
+myWidgetManager.init();
+```
+
+#### WidgetManager.refresh()
+
+`refresh` method refreshes all widgets related to this `WidgetManager`:
+
+````
+myWidgetManager.refresh();
+````
+
+#### WidgetManager Options
+
+WidgetManager has some additional options.
+
+##### Filter Form
+
+If your widgets require parameters/filters then you will generally have a form to filter the widgets. In such cases, you can create a form that has some form elements and a dashboard area with some widgets inside. Example:
+
+````xml
+
+
+
+ ...widgets
+
+````
+
+`data-widget-filter` attribute relates the form with the widgets. Whenever the form is submitted, all the widgets are automatically refreshed with the form fields as the filter.
+
+Instead of the `data-widget-filter` attribute, you can use the `filterForm` parameter of the `WidgetManager` constructor. Example:
+
+````js
+var myWidgetManager = new abp.WidgetManager({
+ wrapper: '#MyDashboardWidgetsArea',
+ filterForm: '#MyDashboardFilterForm'
+});
+````
+
+##### Filter Callback
+
+You may want to have a better control to provide filters while initializing and refreshing the widgets. In this case, you can use the `filterCallback` option:
+
+````js
+var myWidgetManager = new abp.WidgetManager({
+ wrapper: '#MyDashboardWidgetsArea',
+ filterCallback: function() {
+ return $('#MyDashboardFilterForm').serializeFormToObject();
+ }
+});
+````
+
+This example shows the default implementation of the `filterCallback`. You can return any JavaScript object with fields. Example:
+
+````js
+filterCallback: function() {
+ return {
+ 'startDate': $('#StartDateInput').val(),
+ 'endDate': $('#EndDateInput').val()
+ };
+}
+````
+
+The returning filters are passed to all widgets on `init` and `refresh`.
+
+### Widget JavaScript API
+
+A widget can define a JavaScript API that is invoked by the `WidgetManager` when needed. The code sample below can be used to start to define an API for a widget.
+
+````js
+(function () {
+ abp.widgets.NewUserStatisticWidget = function ($wrapper) {
+
+ var getFilters = function () {
+ return {
+ ...
+ };
+ }
+
+ var refresh = function (filters) {
+ ...
+ };
+
+ var init = function (filters) {
+ ...
+ };
+
+ return {
+ getFilters: getFilters,
+ init: init,
+ refresh: refresh
+ };
+ };
+})();
+````
+
+`NewUserStatisticWidget` is the name of the widget here. It should match the widget name defined in the server side. All of the functions are optional.
+
+#### getFilters
+
+If the widget has internal custom filters, this function should return the filter object. Example:
+
+````js
+var getFilters = function() {
+ return {
+ frequency: $wrapper.find('.frequency-filter option:selected').val()
+ };
+}
+````
+
+This method is used by the `WidgetManager` while building filters.
+
+#### init
+
+Used to initialize the widget when needed. It has a filter argument that can be used while getting data from server. `init` method is used when `WidgetManager.init()` function is called. It is also called if your widget requires a full re-load on refresh. See the `RefreshUrl` widget option.
+
+#### refresh
+
+Used to refresh the widget when needed. It has a filter argument that can be used while getting data from server. `refresh` method is used whenever `WidgetManager.refresh()` function is called.
+
+## Authorization
+
+Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute:
+
+* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application).
+* `RequiredPolicies` (`List`): A list of policy names to authorize the user. See [the authorization document](../../Authorization.md) for more info about policies.
+
+Example:
+
+````csharp
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
+{
+ [Widget(RequiredPolicies = new[] { "MyPolicyName" })]
+ public class MySimpleWidgetViewComponent : AbpViewComponent
+ {
+ public IViewComponentResult Invoke()
+ {
+ return View();
+ }
+ }
+}
+````
+
+## WidgetOptions
+
+As alternative to the `Widget` attribute, you can use the `AbpWidgetOptions` to configure widgets:
+
+```csharp
+Configure(options =>
+{
+ options.Widgets.Add();
+});
+```
+
+Write this into the `ConfigureServices` method of your [module](../../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `AbpWidgetOptions`. Example configuration that adds a style for the widget:
+
+````csharp
+Configure(options =>
+{
+ options.Widgets
+ .Add()
+ .WithStyles("/Pages/Components/MySimpleWidget/Default.css");
+});
+````
+
+> Tip: `AbpWidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`.
+
+## See Also
+
+* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo).
\ No newline at end of file
diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json
index 2e5bf3a663..c273e59346 100644
--- a/docs/en/docs-nav.json
+++ b/docs/en/docs-nav.json
@@ -54,8 +54,31 @@
]
},
{
- "text": "ASP.NET Boilerplate Migration Guide",
- "path": "AspNet-Boilerplate-Migration-Guide.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": "Migrating from the ASP.NET Boilerplate",
+ "path": "AspNet-Boilerplate-Migration-Guide.md"
+ }
+ ]
},
{
"text": "CLI",
@@ -219,7 +242,8 @@
"text": "Domain Services"
},
{
- "text": "Specifications"
+ "text": "Specifications",
+ "path": "Specifications.md"
}
]
},
@@ -241,43 +265,68 @@
]
},
{
- "text": "ASP.NET Core",
+ "text": "API",
"items": [
{
- "text": "API",
+ "text": "Auto API Controllers",
+ "path": "API/Auto-API-Controllers.md"
+ },
+ {
+ "text": "Dynamic C# API Clients",
+ "path": "API/Dynamic-CSharp-API-Clients.md"
+ }
+ ]
+ },
+ {
+ "text": "User Interface",
+ "items": [
+ {
+ "text": "ASP.NET Core",
"items": [
{
- "text": "Auto API Controllers",
- "path": "AspNetCore/Auto-API-Controllers.md"
+ "text": "Client Side Package Management",
+ "path": "UI/AspNetCore/Client-Side-Package-Management.md"
+ },
+ {
+ "text": "Bundling & Minification",
+ "path": "UI/AspNetCore/Bundling-Minification.md"
+ },
+ {
+ "text": "Tag Helpers",
+ "path": "UI/AspNetCore/Tag-Helpers/Index.md"
+ },
+ {
+ "text": "Widgets",
+ "path": "UI/AspNetCore/Widgets.md"
},
{
- "text": "Dynamic C# API Clients",
- "path": "AspNetCore/Dynamic-CSharp-API-Clients.md"
+ "text": "Theming",
+ "path": "UI/AspNetCore/Theming.md"
}
]
},
{
- "text": "User Interface",
+ "text": "Angular",
"items": [
{
- "text": "Client Side Package Management",
- "path": "AspNetCore/Client-Side-Package-Management.md"
+ "text": "Localization",
+ "path": "UI/Angular/Localization.md"
},
{
- "text": "Bundling & Minification",
- "path": "AspNetCore/Bundling-Minification.md"
+ "text": "Permission Management",
+ "path": "UI/Angular/Permission-Management.md"
},
{
- "text": "Tag Helpers",
- "path": "AspNetCore/Tag-Helpers/Index.md"
+ "text": "Config State",
+ "path": "UI/Angular/Config-State.md"
},
{
- "text": "Widgets",
- "path": "AspNetCore/Widgets.md"
+ "text": "Component Replacement",
+ "path": "UI/Angular/Component-Replacement.md"
},
{
- "text": "Theming",
- "path": "AspNetCore/Theming.md"
+ "text": "Custom Setting Page",
+ "path": "UI/Angular/Custom-Setting-Page.md"
}
]
}
@@ -348,6 +397,20 @@
{
"text": "RabbitMQ Integration",
"path": "Background-Jobs-RabbitMq.md"
+ },
+ {
+ "text": "Quartz Integration",
+ "path": "Background-Jobs-Quartz.md"
+ }
+ ]
+ },
+ {
+ "text": "Background Workers",
+ "path": "Background-Workers.md",
+ "items": [
+ {
+ "text": "Quartz Integration",
+ "path": "Background-Workers-Quartz.md"
}
]
}
diff --git a/docs/en/images/bookstore-added-brand-files.png b/docs/en/images/bookstore-added-brand-files.png
new file mode 100644
index 0000000000..15ef283a2a
Binary files /dev/null and b/docs/en/images/bookstore-added-brand-files.png differ
diff --git a/docs/en/images/bookstore-added-logo.png b/docs/en/images/bookstore-added-logo.png
new file mode 100644
index 0000000000..55af77a5f4
Binary files /dev/null and b/docs/en/images/bookstore-added-logo.png differ
diff --git a/docs/en/images/bookstore-added-role-js-file.png b/docs/en/images/bookstore-added-role-js-file.png
new file mode 100644
index 0000000000..00881bf808
Binary files /dev/null and b/docs/en/images/bookstore-added-role-js-file.png differ
diff --git a/docs/en/images/bookstore-brand-area-highlighted.png b/docs/en/images/bookstore-brand-area-highlighted.png
new file mode 100644
index 0000000000..7ab1b8fb7f
Binary files /dev/null and b/docs/en/images/bookstore-brand-area-highlighted.png differ
diff --git a/docs/en/images/bookstore-global-css-file.png b/docs/en/images/bookstore-global-css-file.png
new file mode 100644
index 0000000000..e764fef8ee
Binary files /dev/null and b/docs/en/images/bookstore-global-css-file.png differ
diff --git a/docs/en/images/bookstore-google-analytics-view-component.png b/docs/en/images/bookstore-google-analytics-view-component.png
new file mode 100644
index 0000000000..e548a00935
Binary files /dev/null and b/docs/en/images/bookstore-google-analytics-view-component.png differ
diff --git a/docs/en/images/bookstore-menus-highlighted.png b/docs/en/images/bookstore-menus-highlighted.png
new file mode 100644
index 0000000000..c4f3237b00
Binary files /dev/null and b/docs/en/images/bookstore-menus-highlighted.png differ
diff --git a/docs/en/images/bookstore-notification-icon-on-toolbar.png b/docs/en/images/bookstore-notification-icon-on-toolbar.png
new file mode 100644
index 0000000000..817491e9eb
Binary files /dev/null and b/docs/en/images/bookstore-notification-icon-on-toolbar.png differ
diff --git a/docs/en/images/bookstore-notification-view-component.png b/docs/en/images/bookstore-notification-view-component.png
new file mode 100644
index 0000000000..3fc300efe1
Binary files /dev/null and b/docs/en/images/bookstore-notification-view-component.png differ
diff --git a/docs/en/images/bookstore-toolbar-highlighted.png b/docs/en/images/bookstore-toolbar-highlighted.png
new file mode 100644
index 0000000000..f2b7c3f57e
Binary files /dev/null and b/docs/en/images/bookstore-toolbar-highlighted.png differ
diff --git a/docs/en/images/docs-module_download-new-abp-project.png b/docs/en/images/docs-module_download-new-abp-project.png
index 0da3b7a67a..6424ad7622 100644
Binary files a/docs/en/images/docs-module_download-new-abp-project.png and b/docs/en/images/docs-module_download-new-abp-project.png differ
diff --git a/docs/en/images/docs-module_solution-explorer.png b/docs/en/images/docs-module_solution-explorer.png
index cafc38f0b0..2988ec4134 100644
Binary files a/docs/en/images/docs-module_solution-explorer.png and b/docs/en/images/docs-module_solution-explorer.png differ
diff --git a/docs/en/images/microservice-sample-diagram-3.png b/docs/en/images/microservice-sample-diagram-3.png
new file mode 100644
index 0000000000..b8066ef6be
Binary files /dev/null and b/docs/en/images/microservice-sample-diagram-3.png differ
diff --git a/docs/en/images/microservice-sample-solution-2.png b/docs/en/images/microservice-sample-solution-2.png
new file mode 100644
index 0000000000..3da5d160f8
Binary files /dev/null and b/docs/en/images/microservice-sample-solution-2.png differ
diff --git a/docs/en/images/overriding-login-cshtml.png b/docs/en/images/overriding-login-cshtml.png
new file mode 100644
index 0000000000..d6daa4a141
Binary files /dev/null and b/docs/en/images/overriding-login-cshtml.png differ
diff --git a/docs/pt-BR/Getting-Started-Angular-Template.md b/docs/pt-BR/Getting-Started-Angular-Template.md
index 707e06cef3..f1b3abe790 100644
--- a/docs/pt-BR/Getting-Started-Angular-Template.md
+++ b/docs/pt-BR/Getting-Started-Angular-Template.md
@@ -26,10 +26,10 @@ abp new Acme.BookStore -u angular
A solução criada requer;
-* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
+* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
-* [Yarn v1.19+](https://yarnpkg.com/)
+* [Yarn v1.19+](https://classic.yarnpkg.com/)
### A Estrutura da Solução
@@ -103,7 +103,7 @@ A maioria das APIs de aplicativos requer autenticação e autorização. Se voc
#### Execute o aplicativo angular (lado do cliente)
-Vá para a `angular`pasta, abra um terminal de linha de comando, digite o `yarn`comando (sugerimos ao gerenciador de pacotes do [yarn](https://yarnpkg.com/) enquanto o npm install também funcionará na maioria dos casos):
+Vá para a `angular`pasta, abra um terminal de linha de comando, digite o `yarn`comando (sugerimos ao gerenciador de pacotes do [yarn](https://classic.yarnpkg.com/) enquanto o npm install também funcionará na maioria dos casos):
```bash
yarn
diff --git a/docs/pt-BR/Getting-Started-AspNetCore-MVC-Template.md b/docs/pt-BR/Getting-Started-AspNetCore-MVC-Template.md
index ac82ff0279..808c0bd94d 100644
--- a/docs/pt-BR/Getting-Started-AspNetCore-MVC-Template.md
+++ b/docs/pt-BR/Getting-Started-AspNetCore-MVC-Template.md
@@ -26,10 +26,10 @@ abp new Acme.BookStore
A solução criada requer;
-* [Visual Studio 2019 (v16.3+)](https://visualstudio.microsoft.com/vs/)
+* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
-* [Yarn v1.19+](https://yarnpkg.com/)
+* [Yarn v1.19+](https://classic.yarnpkg.com/)
### A Estrutura da Solução
diff --git a/docs/pt-BR/Localization.md b/docs/pt-BR/Localization.md
index f665b68073..35b400b233 100644
--- a/docs/pt-BR/Localization.md
+++ b/docs/pt-BR/Localization.md
@@ -46,7 +46,7 @@ public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
- Configure(options =>
+ Configure(options =>
{
options.FileSets.AddEmbedded();
});
@@ -66,7 +66,7 @@ Neste exemplo;
- Adicionado um novo recurso de localização com "en" (inglês) como a cultura padrão.
- Arquivos JSON usados para armazenar as sequências de localização.
-- Os arquivos JSON são incorporados ao assembly usando `VirtualFileSystemOptions`(consulte [sistema de arquivos virtual](Virtual-File-System.md) ).
+- Os arquivos JSON são incorporados ao assembly usando `AbpVirtualFileSystemOptions`(consulte [sistema de arquivos virtual](Virtual-File-System.md) ).
Os arquivos JSON estão localizados na pasta do projeto "/ Localização / Recursos / Teste", como mostrado abaixo:
diff --git a/docs/zh-Hans/API/Auto-API-Controllers.md b/docs/zh-Hans/API/Auto-API-Controllers.md
new file mode 100644
index 0000000000..93ac124913
--- /dev/null
+++ b/docs/zh-Hans/API/Auto-API-Controllers.md
@@ -0,0 +1,140 @@
+# 自动API控制器
+
+创建[应用程序服务](Application-Services.md)后, 通常需要创建API控制器以将此服务公开为HTTP(REST)API端点. 典型的API控制器除了将方法调用重定向到应用程序服务并使用[HttpGet],[HttpPost],[Route]等属性配置REST API之外什么都不做.
+
+ABP可以按照惯例 **自动** 将你的应用程序服务配置为API控制器. 大多数时候你不关心它的详细配置,但它可以完全被自定义.
+
+## 配置
+
+基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示:
+
+````csharp
+[DependsOn(BookStoreApplicationModule)]
+public class BookStoreWebModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options
+ .ConventionalControllers
+ .Create(typeof(BookStoreApplicationModule).Assembly);
+ });
+ }
+}
+````
+
+此示例代码配置包含类`BookStoreApplicationModule`的程序集中的所有应用程序服务.下图显示了[Swagger UI](https://swagger.io/tools/swagger-ui/)上的API内容.
+
+
+
+### 例子
+
+一些示例方法名称和按约定生成的相应路由:
+
+| 服务方法名称 | HTTP Method | 路由 |
+| ----------------------------------------------------- | ----------- | -------------------------- |
+| GetAsync(Guid id) | GET | /api/app/book/{id} |
+| GetListAsync() | GET | /api/app/book |
+| CreateAsync(CreateBookDto input) | POST | /api/app/book |
+| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} |
+| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} |
+| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors |
+| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor |
+
+### HTTP Method
+
+ABP在确定服务方法的HTTP Method时使用命名约定:
+
+- **Get**: 如果方法名称以`GetList`,`GetAll`或`Get`开头.
+- **Put**: 如果方法名称以`Put`或`Update`开头.
+- **Delete**: 如果方法名称以`Delete`或`Remove`开头.
+- **Post**: 如果方法名称以`Create`,`Add`,`Insert`或`Post`开头.
+- **Patch**: 如果方法名称以`Patch`开头.
+- 其他情况, **Post** 为 **默认方式**.
+
+如果需要为特定方法自定义HTTP Method, 则可以使用标准ASP.NET Core的属性([HttpPost], [HttpGet], [HttpPut]... 等等.). 这需要添加[Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core)的Nuget包.
+
+### 路由
+
+路由根据一些惯例生成:
+
+* 它始终以 **/api**开头.
+* 接着是**路由路径**. 默认值为"**/app**", 可以进行如下配置:
+
+````csharp
+Configure(options =>
+{
+ options.ConventionalControllers
+ .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
+ {
+ opts.RootPath = "volosoft/book-store";
+ });
+});
+````
+
+然后获得一本书的路由将是'**/api/volosoft/book-store/book/{id}**'. 此示例使用两级根路径,但通常使用单个级别的深度.
+
+* 接着 **标准化控制器/服务名称**. 会删除`AppService`,`ApplicationService`和`Service`的后缀并将其转换为 **camelCase**. 如果你的应用程序服务类名称为`BookAppService`.那么它将变为`/book`.
+ * 如果要自定义命名, 则设置`UrlControllerNameNormalizer`选项. 它是一个委托允许你自定义每个控制器/服务的名称.
+* 如果该方法具有 '**id**'参数, 则会在路由中添加'**/{id}**'.
+* 如有必要,它会添加操作名称. 操作名称从服务上的方法名称获取并标准化;
+ * 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`.
+ * 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复.
+ * 将结果转换为**camelCase**.
+ * 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`.
+ * 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method.
+* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId').
+
+## 服务选择
+
+创建的HTTP API控制器并不是应用服务所独有的功能.
+
+### IRemoteService 接口
+
+如果一个类实现了`IRemoteService`接口, 那么它会被自动选择为API控制器. 由于应用程序服务本身实现了`IRemoteService`接口, 因此它自然就成为API控制器.
+
+### RemoteService Attribute
+
+`RemoteService`可用于将实现`IRemoteService`接口的类标记为远程服务或禁用它. 例如:
+
+````csharp
+[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)]
+public class PersonAppService : ApplicationService
+{
+
+}
+````
+
+### TypePredicate 选项
+
+你可以通过提供`TypePedicate`选项进一步过滤类以成为API控制器:
+
+````csharp
+services.Configure(options =>
+{
+ options.ConventionalControllers
+ .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
+ {
+ opts.TypePredicate = type => { return true; };
+ });
+});
+````
+
+如果你不想将此类型公开为API控制器, 则可以在类型检查时返回`false`.
+
+## API Explorer
+
+API Explorer是可以由客户端获取API结构的服务. Swagger使用它为endpoint创建文档和test UI.
+
+默认情况下, HTTP API控制器会自动启用API Explorer, 可以使用`RemoteService`按类或方法的级别控制它. 例如:
+
+````csharp
+[RemoteService(IsMetadataEnabled = false)]
+public class PersonAppService : ApplicationService
+{
+
+}
+````
+
+禁用`IsMetadataEnabled`从而从API Explorer中隐藏此服务, 并且无法被发现. 但是它仍然可以被知道确切API路径/路由的客户端使用.
\ No newline at end of file
diff --git a/docs/zh-Hans/API/Dynamic-CSharp-API-Clients.md b/docs/zh-Hans/API/Dynamic-CSharp-API-Clients.md
new file mode 100644
index 0000000000..331e66c18e
--- /dev/null
+++ b/docs/zh-Hans/API/Dynamic-CSharp-API-Clients.md
@@ -0,0 +1,165 @@
+# 动态 C# API 客户端
+
+ABP可以自动创建C# API 客户端代理来调用远程HTTP服务(REST APIS).通过这种方式,你不需要通过 `HttpClient` 或者其他低级的HTTP功能调用远程服务并获取数据.
+
+## 服务接口
+
+你的service或controller需要实现一个在服务端和客户端共享的接口.因此,首先需要在一个共享的类库项目中定义一个服务接口.例如:
+
+````csharp
+public interface IBookAppService : IApplicationService
+{
+ Task> GetListAsync();
+}
+````
+
+为了能自动被发现,你的接口需要实现`IRemoteService`接口.由于`IApplicationService`继承自`IRemoteService`接口.所以`IBookAppService`完全满足这个条件.
+
+在你的服务中实现这个类,你可以使用[auto API controller system](Auto-API-Controllers.md)将你的服务暴漏为一个REST API 端点.
+
+## 客户端代理生成
+
+首先,将[Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget包添加到你的客户端项目中:
+
+````
+Install-Package Volo.Abp.Http.Client
+````
+
+然后给你的模块添加`AbpHttpClientModule`依赖:
+
+````csharp
+[DependsOn(typeof(AbpHttpClientModule))] //添加依赖
+public class MyClientAppModule : AbpModule
+{
+}
+````
+
+现在,已经可以创建客户端代理了.例如:
+
+````csharp
+[DependsOn(
+ typeof(AbpHttpClientModule), //用来创建客户端代理
+ typeof(BookStoreApplicationModule) //包含应用服务接口
+ )]
+public class MyClientAppModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ //创建动态客户端代理
+ context.Services.AddHttpClientProxies(
+ typeof(BookStoreApplicationModule).Assembly
+ );
+ }
+}
+````
+
+`AddHttpClientproxies`方法获得一个程序集,找到这个程序集中所有的服务接口,创建并注册代理类.
+
+### Endpoint配置
+
+`appsettings.json`文件中的`RemoteServices`节点被用来设置默认的服务地址.下面是最简单的配置:
+
+````
+{
+ "RemoteServices": {
+ "Default": {
+ "BaseUrl": "http://localhost:53929/"
+ }
+ }
+}
+````
+
+查看下面的"RemoteServiceOptions"章节获取更多详细配置.
+
+## 使用
+
+可以很直接地使用.只需要在你的客户端程序中注入服务接口:
+
+````csharp
+public class MyService : ITransientDependency
+{
+ private readonly IBookAppService _bookService;
+
+ public MyService(IBookAppService bookService)
+ {
+ _bookService = bookService;
+ }
+
+ public async Task DoIt()
+ {
+ var books = await _bookService.GetListAsync();
+ foreach (var book in books)
+ {
+ Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}");
+ }
+ }
+}
+````
+
+本例注入了上面定义的`IBookAppService`服务接口.当客户端调用服务方法的时候动态客户端代理就会创建一个HTTP调用.
+
+### IHttpClientProxy接口
+
+你可以像上面那样注入`IBookAppService`来使用客户端代理,也可以注入`IHttpClientProxy`获取更多明确的用法.这种情况下你可以使用`IHttpClientProxy`接口的`Service`属性.
+
+## 配置
+
+### RemoteServiceOptions
+
+默认情况下`AbpRemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如:
+
+````csharp
+public override void ConfigureServices(ServiceConfigurationContext context)
+{
+ context.Services.Configure(options =>
+ {
+ options.RemoteServices.Default =
+ new RemoteServiceConfiguration("http://localhost:53929/");
+ });
+
+ //...
+}
+````
+
+### 多个远程服务端点
+
+上面的例子已经配置了"Default"远程服务端点.你可能需要为不同的服务创建不同的端点.(就像在微服务方法中一样,每个微服务具有不同的端点).在这种情况下,你可以在你的配置文件中添加其他的端点:
+
+````json
+{
+ "RemoteServices": {
+ "Default": {
+ "BaseUrl": "http://localhost:53929/"
+ },
+ "BookStore": {
+ "BaseUrl": "http://localhost:48392/"
+ }
+ }
+}
+````
+
+`AddHttpClientProxies`方法有一个可选的参数来定义远程服务的名字:
+
+````csharp
+context.Services.AddHttpClientProxies(
+ typeof(BookStoreApplicationModule).Assembly,
+ remoteServiceName: "BookStore"
+);
+````
+
+`remoteServiceName`参数会匹配通过`AbpRemoteServiceOptions`配置的服务端点.如果`BookStore`端点没有定义就会使用默认的`Default`端点.
+
+### 作为默认服务
+
+当你为`IBookAppService`创建了一个服务代理,你可以直接注入`IBookAppService`来使用代理客户端(像上面章节中将的那样).你可以传递`asDefaultService:false`到`AddHttpClientProxies`方法来禁用此功能.
+
+````csharp
+context.Services.AddHttpClientProxies(
+ typeof(BookStoreApplicationModule).Assembly,
+ asDefaultServices: false
+);
+````
+
+如果你的程序中已经有一个服务的实现并且你不想用你的客户端代理重写或替换其他的实现,就需要使用`asDefaultServices:false`
+
+> 如果你禁用了`asDefaultService`,你只能使用`IHttpClientProxy`接口去使用客户端代理.(参见上面的相关章节).
\ No newline at end of file
diff --git a/docs/zh-Hans/API/JavaScript-API/Auth.md b/docs/zh-Hans/API/JavaScript-API/Auth.md
new file mode 100644
index 0000000000..60c9eb8866
--- /dev/null
+++ b/docs/zh-Hans/API/JavaScript-API/Auth.md
@@ -0,0 +1,3 @@
+# abp.auth JavaScript API
+
+TODO
\ No newline at end of file
diff --git a/docs/zh-Hans/API/JavaScript-API/Index.md b/docs/zh-Hans/API/JavaScript-API/Index.md
new file mode 100644
index 0000000000..854ec8214b
--- /dev/null
+++ b/docs/zh-Hans/API/JavaScript-API/Index.md
@@ -0,0 +1,23 @@
+# JavaScript API
+
+ABP为ASP.NET Core MVC / Razor页面应用程序提供了一些执行客户端常见需求的JavaScrpt Api.
+
+## APIs
+
+* abp.ajax
+* [abp.auth](Auth.md)
+* abp.currentUser
+* abp.dom
+* abp.event
+* abp.features
+* abp.localization
+* abp.log
+* abp.ModalManager
+* abp.notify
+* abp.security
+* abp.setting
+* abp.ui
+* abp.utils
+* abp.ResourceLoader
+* abp.WidgetManager
+* Other APIs
\ No newline at end of file
diff --git a/docs/zh-Hans/AspNetCore/Auto-API-Controllers.md b/docs/zh-Hans/AspNetCore/Auto-API-Controllers.md
index 93ac124913..19a26440eb 100644
--- a/docs/zh-Hans/AspNetCore/Auto-API-Controllers.md
+++ b/docs/zh-Hans/AspNetCore/Auto-API-Controllers.md
@@ -1,140 +1,3 @@
-# 自动API控制器
+文档已经移动到其他位置.
-创建[应用程序服务](Application-Services.md)后, 通常需要创建API控制器以将此服务公开为HTTP(REST)API端点. 典型的API控制器除了将方法调用重定向到应用程序服务并使用[HttpGet],[HttpPost],[Route]等属性配置REST API之外什么都不做.
-
-ABP可以按照惯例 **自动** 将你的应用程序服务配置为API控制器. 大多数时候你不关心它的详细配置,但它可以完全被自定义.
-
-## 配置
-
-基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示:
-
-````csharp
-[DependsOn(BookStoreApplicationModule)]
-public class BookStoreWebModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- Configure(options =>
- {
- options
- .ConventionalControllers
- .Create(typeof(BookStoreApplicationModule).Assembly);
- });
- }
-}
-````
-
-此示例代码配置包含类`BookStoreApplicationModule`的程序集中的所有应用程序服务.下图显示了[Swagger UI](https://swagger.io/tools/swagger-ui/)上的API内容.
-
-
-
-### 例子
-
-一些示例方法名称和按约定生成的相应路由:
-
-| 服务方法名称 | HTTP Method | 路由 |
-| ----------------------------------------------------- | ----------- | -------------------------- |
-| GetAsync(Guid id) | GET | /api/app/book/{id} |
-| GetListAsync() | GET | /api/app/book |
-| CreateAsync(CreateBookDto input) | POST | /api/app/book |
-| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} |
-| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} |
-| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors |
-| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor |
-
-### HTTP Method
-
-ABP在确定服务方法的HTTP Method时使用命名约定:
-
-- **Get**: 如果方法名称以`GetList`,`GetAll`或`Get`开头.
-- **Put**: 如果方法名称以`Put`或`Update`开头.
-- **Delete**: 如果方法名称以`Delete`或`Remove`开头.
-- **Post**: 如果方法名称以`Create`,`Add`,`Insert`或`Post`开头.
-- **Patch**: 如果方法名称以`Patch`开头.
-- 其他情况, **Post** 为 **默认方式**.
-
-如果需要为特定方法自定义HTTP Method, 则可以使用标准ASP.NET Core的属性([HttpPost], [HttpGet], [HttpPut]... 等等.). 这需要添加[Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core)的Nuget包.
-
-### 路由
-
-路由根据一些惯例生成:
-
-* 它始终以 **/api**开头.
-* 接着是**路由路径**. 默认值为"**/app**", 可以进行如下配置:
-
-````csharp
-Configure(options =>
-{
- options.ConventionalControllers
- .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
- {
- opts.RootPath = "volosoft/book-store";
- });
-});
-````
-
-然后获得一本书的路由将是'**/api/volosoft/book-store/book/{id}**'. 此示例使用两级根路径,但通常使用单个级别的深度.
-
-* 接着 **标准化控制器/服务名称**. 会删除`AppService`,`ApplicationService`和`Service`的后缀并将其转换为 **camelCase**. 如果你的应用程序服务类名称为`BookAppService`.那么它将变为`/book`.
- * 如果要自定义命名, 则设置`UrlControllerNameNormalizer`选项. 它是一个委托允许你自定义每个控制器/服务的名称.
-* 如果该方法具有 '**id**'参数, 则会在路由中添加'**/{id}**'.
-* 如有必要,它会添加操作名称. 操作名称从服务上的方法名称获取并标准化;
- * 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`.
- * 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复.
- * 将结果转换为**camelCase**.
- * 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`.
- * 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method.
-* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId').
-
-## 服务选择
-
-创建的HTTP API控制器并不是应用服务所独有的功能.
-
-### IRemoteService 接口
-
-如果一个类实现了`IRemoteService`接口, 那么它会被自动选择为API控制器. 由于应用程序服务本身实现了`IRemoteService`接口, 因此它自然就成为API控制器.
-
-### RemoteService Attribute
-
-`RemoteService`可用于将实现`IRemoteService`接口的类标记为远程服务或禁用它. 例如:
-
-````csharp
-[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)]
-public class PersonAppService : ApplicationService
-{
-
-}
-````
-
-### TypePredicate 选项
-
-你可以通过提供`TypePedicate`选项进一步过滤类以成为API控制器:
-
-````csharp
-services.Configure(options =>
-{
- options.ConventionalControllers
- .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
- {
- opts.TypePredicate = type => { return true; };
- });
-});
-````
-
-如果你不想将此类型公开为API控制器, 则可以在类型检查时返回`false`.
-
-## API Explorer
-
-API Explorer是可以由客户端获取API结构的服务. Swagger使用它为endpoint创建文档和test UI.
-
-默认情况下, HTTP API控制器会自动启用API Explorer, 可以使用`RemoteService`按类或方法的级别控制它. 例如:
-
-````csharp
-[RemoteService(IsMetadataEnabled = false)]
-public class PersonAppService : ApplicationService
-{
-
-}
-````
-
-禁用`IsMetadataEnabled`从而从API Explorer中隐藏此服务, 并且无法被发现. 但是它仍然可以被知道确切API路径/路由的客户端使用.
\ No newline at end of file
+[点击链接跳转到自动API控制器文档](../API/Auto-API-Controllers.md)
\ No newline at end of file
diff --git a/docs/zh-Hans/AspNetCore/Bundling-Minification.md b/docs/zh-Hans/AspNetCore/Bundling-Minification.md
index b7c8c0a1b1..c102c89fd2 100644
--- a/docs/zh-Hans/AspNetCore/Bundling-Minification.md
+++ b/docs/zh-Hans/AspNetCore/Bundling-Minification.md
@@ -1,354 +1,3 @@
+文档已经移动到其他位置.
-## ASP.NET Core MVC 捆绑 & 压缩
-
-有许多方法可以捆绑&压缩客户端资源(JavaScript和CSS文件). 最常见的方式是:
-
-* 使用Visual Studio[捆绑&压缩](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier)扩展或者其它的[NuGet相关包](https://www.nuget.org/packages/BuildBundlerMinifier/).
-
-* 使用[Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/)及其插件.
-
-ABP内置了简单,动态,强大,模块化的方式.
-
-### Volo.Abp.AspNetCore.Mvc.UI.Bundling 包
-
-> 默认情况下已在启动模板安装此软件包. 大多数情况下,你不需要手动安装它.
-
-将`Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget包安装到你的项目中:
-
-````
-install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling
-````
-
-然后将`AbpAspNetCoreMvcUiBundlingModule`依赖项添加到你的模块上:
-
-````C#
-using Volo.Abp.Modularity;
-using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
-
-namespace MyCompany.MyProject
-{
- [DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
- public class MyWebModule : AbpModule
- {
- //...
- }
-}
-````
-
-### Razor Bundling Tag Helpers
-
-创建bundle的最简单方法是使用`abp-script-bundle`或`abp-style-bundle` tag helpers. 例如:
-
-````html
-
-
-
-
-
-
-````
-
-`abp-script-bundle`定义了一个带有**唯一名称**的样式包:`MyGlobalBundle`. 使用方法很容易理解. 让我们看看它是如何*工作的*:
-
-* 当首次请求时,ABP从提供的文件中 **(延迟)lazy** 创建. 后续将从 **缓存** 中返回内容. 这意味着如果你有条件地将文件添加到包中,它只执行一次, 并且条件的任何更改都不会影响下一个请求的包.
-* 在`development`环境中ABP会将包文件**单独**添加到页面中, 其他环境(`staging`,`production`...)会自动捆绑和压缩.
-* 捆绑文件可以是**物理**文件或[**虚拟/嵌入**](../Virtual-File-System.md)的文件.
-* ABP自动将 **版本查询字符串(version query string)** 添加到捆绑文件的URL中,以防止浏览器缓存. 如:?_v=67872834243042(从文件的上次更改日期生成). 即使捆绑文件单独添加到页面(在`development`环境中), 版本控制仍然有效.
-
-#### 导入 Bundling Tag Helpers
-
-> 默认情况下已在启动模板导入. 大多数情况下,你不需要手动安装它.
-
-要使用`bundle tag helpers`, 你需要将其添加到`_ViewImports.cshtml`文件或页面中:
-
-````
-@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
-````
-
-#### 未命名的 Bundles
-
-对于razor bundle tag helpers, `name`是**可选**. 如果没有定义一个名字,它将根据使用的捆绑文件名自动**计算生成**(they are **concatenated** and **hashed**) 例:
-
-````html
-
-
-
-
- @if (ViewBag.IncludeCustomStyles != false)
- {
-
- }
-
-````
-
-这将潜在地创建**两个不同的bundles**(一个包括`my-global-style.css`而另一个则不包括).
-
-**未命名的** bundles优点:
-
-* 可以**有条件地将项目**添加到捆绑包中. 但这可能会导致基于条件的捆绑的存在多种变化.
-
-**命名** bundles优点:
-
-* 其他模块可以通过其名称为捆绑包做出贡献(参见下面的部分).
-
-#### 单个文件
-
-如果你只需要在页面中添加一个文件, 你可以使用`abp-script`或`abp-style`而不需要包含在`abp-script-bundle`或`abp-style-bundle`中. 例:
-
-````xml
-
-````
-
-对于上面的示例,包名称将是 *scripts.my-scripts*("/"替换为"."). 所有捆绑功能也可以按预期应用于单个文件.
-
-### Bundling 选项
-
-如果你需要在 **多个页面中使用相同的包** 或想要使用更多 **强大功能**, 你可以在[模块](../Module-Development-Basics.md)类中进行**配置**.
-
-#### 创建一个新的捆绑包
-
-用法示例:
-
-````C#
-[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
-public class MyWebModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- Configure(options =>
- {
- options
- .ScriptBundles
- .Add("MyGlobalBundle", bundle => {
- bundle.AddFiles(
- "/libs/jquery/jquery.js",
- "/libs/bootstrap/js/bootstrap.js",
- "/libs/toastr/toastr.min.js",
- "/scripts/my-global-scripts.js"
- );
- });
- });
- }
-}
-````
-
-> 你可以在脚本和样式包中使用相同的名称(*MyGlobalBundle*), 因为它们被添加到不同的集合(`ScriptBundles`和`StyleBundles`).
-
-在定义bundle之后, 可以使用上面定义的相同tag helpers将其包括在页面中. 例如:
-
-````html
-
-````
-
-这次tag helper定义中没有定义文件, 因为捆绑文件是由代码定义的.
-
-#### 配置现有的 Bundle
-
-ABP也支持[模块化](../Module-Development-Basics.md)捆绑. 模块可以修改由依赖模块创建的捆绑包.
-例如:
-
-````C#
-[DependsOn(typeof(MyWebModule))]
-public class MyWebExtensionModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- Configure(options =>
- {
- options
- .ScriptBundles
- .Configure("MyGlobalBundle", bundle => {
- bundle.AddFiles(
- "/scripts/my-extension-script.js"
- );
- });
- });
- }
-}
-````
-
-> 无法通过代码配置未命名的bundle tag helpers, 因为它们的名称在开发时是未知的. 建议始终使用bundle tag helper的名称.
-
-### Bundle 贡献者
-
-将文件添加到现有bundle似乎很有用. 如果你需要**替换**bundle中的文件或者你想**有条件地**添加文件怎么办? 定义bundle贡献者可为此类情况提供额外的功能.
-
-一个bundle的贡献者使用自定义版本bootstrap.css替换示例:
-
-````C#
-public class MyExtensionGlobalStyleContributor : BundleContributor
-{
- public override void ConfigureBundle(BundleConfigurationContext context)
- {
- context.Files.ReplaceOne(
- "/libs/bootstrap/css/bootstrap.css",
- "/styles/extensions/bootstrap-customized.css"
- );
- }
-}
-````
-
-然后你可以按照下面的代码使用这个贡献者:
-
-````C#
-services.Configure(options =>
-{
- options
- .ScriptBundles
- .Configure("MyGlobalBundle", bundle => {
- bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor));
- });
-});
-````
-
-贡献者也可以在bundle tag helpers中使用.
-例如:
-
-````xml
-
-
-
-
-
-````
-
-`abp-style`和`abp-script`标签可以使用`type`属性(而不是`src`属性), 如本示例所示. 添加bundle贡献者时, 其依赖关系也会自动添加到bundle中.
-
-#### 贡献者依赖关系
-
-bundle贡献者可以与其他贡献者具有一个或多个依赖关系.
-例如:
-
-````C#
-[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency
-public class MyExtensionStyleBundleContributor : BundleContributor
-{
- //...
-}
-````
-
-添加bundle贡献者时,其依赖关系将 **自动并递归** 添加. **依赖顺序** 通过阻止 **重复** 添加的依赖关系. 即使它们处于分离的bundle中,也会阻止重复. ABP在页面中组织所有bundle并消除重复.
-
-创建贡献者和定义依赖关系是一种跨不同模块组织bundle创建的方法.
-
-
-#### 贡献者扩展
-
-在某些高级应用场景中, 当用到一个bundle贡献者时,你可能想做一些额外的配置. 贡献者扩展可以和被扩展的贡献者无缝衔接.
-
-下面的示例为 prism.js 脚本库添加一些样式:
-
-````csharp
-public class MyPrismjsStyleExtension : BundleContributor
-{
- public override void ConfigureBundle(BundleConfigurationContext context)
- {
- context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css");
- }
-}
-````
-
-然后你可以配置 `BundleContributorOptions` 去扩展已存在的 `PrismjsStyleBundleContributor`.
-
-````csharp
-Configure(options =>
-{
- options
- .Extensions()
- .Add();
-});
-````
-
-任何时候当 `PrismjsStyleBundleContributor` 被添加到bundle中时, `MyPrismjsStyleExtension` 也会被自动添加.
-
-#### 访问 IServiceProvider
-
-虽然很少需要它, 但是`BundleConfigurationContext`有一个`ServiceProvider`属性, 你可以在`ConfigureBundle`方法中解析服务依赖.
-
-#### 标准包装贡献者
-
-将特定的NPM包资源(js,css文件)添加到包中对于该包非常简单. 例如, 你总是为bootstrap NPM包添加`bootstrap.css`文件.
-
-所有[标准NPM包](Client-Side-Package-Management.md)都有内置的贡献者. 例如,如果你的贡献者依赖于bootstrap,你可以声明它,而不是自己添加bootstrap.css.
-
-````C#
-[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency
-public class MyExtensionStyleBundleContributor : BundleContributor
-{
- //...
-}
-````
-
-使用标准包的内置贡献者:
-
-* 防止你输入**无效的资源路径**.
-* 如果资源 **路径发生变化** (依赖贡献者将处理它),则防止更改你的贡献者.
-* 防止多个模块添加**重复文件**.
-* 以递归方式管理依赖项(如果需要,添加依赖项的依赖项).
-
-##### Volo.Abp.AspNetCore.Mvc.UI.Packages 包
-
-> 默认情况下已在启动模板安装此软件包. 大多数情况下,你不需要手动安装它.
-
-标准包贡献者在`Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet包中定义.
-将它安装到你的项目中:
-
-````
-install-package Volo.Abp.AspNetCore.Mvc.UI.Packages
-````
-
-然后将`AbpAspNetCoreMvcUiPackagesModule`模块依赖项添加到你的模块中;
-
-````C#
-using Volo.Abp.Modularity;
-using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
-
-namespace MyCompany.MyProject
-{
- [DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))]
- public class MyWebModule : AbpModule
- {
- //...
- }
-}
-````
-
-#### Bundle 继承
-
-在某些特定情况下, 可能需要从其他bundle创建一个 **新** bundle **继承**, 从bundle继承(递归)会继承该bundle的所有文件/贡献者. 然后派生的bundle可以添加或修改文件/贡献者**而无需修改**原始bundle.
-例如:
-
-````c#
-services.Configure(options =>
-{
- options
- .StyleBundles
- .Add("MyTheme.MyGlobalBundle", bundle => {
- bundle
- .AddBaseBundles("MyGlobalBundle") //Can add multiple
- .AddFiles(
- "/styles/mytheme-global-styles.css"
- );
- });
-});
-````
-
-### 主题
-
-主题使用标准包贡献者将库资源添加到页面布局. 主题还可以定义一些标准/全局包, 因此任何模块都可以为这些标准/全局包做出贡献. 有关更多信息, 请参阅[主题文档](Theming.md).
-
-### 最佳实践 & 建议
-
-建议为应用程序定义多个包, 每个包用于不同的目的.
-
-* **全局包**: 应用程序中的每个页面都包含全局样式/脚本包. 主题已经定义了全局样式和脚本包. 你的模块可以为他们做出贡献.
-* **布局包**: 这是针对单个布局的特定包. 仅包含在所有页面之间共享的资源使用布局. 使用bundling tag helpers创建捆绑包是一种很好的做法.
-* **模块包**: 用于单个模块页面之间的共享资源.
-* **页面包**: 为每个页面创建的特定包. 使用bundling tag helpers创建捆绑包作为最佳实践.
-
-在性能,网络带宽使用和捆绑包的数量之间建立平衡.
-
-### 参见
-
-* [客户端包管理](Client-Side-Package-Management.md)
-* [主题](Theming.md)
+[点击链接跳转到ASP.NET Core MVC 捆绑 & 压缩文档](../UI/AspNetCore/Bundling-Minification.md)
\ No newline at end of file
diff --git a/docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md b/docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md
index 4d45022da0..f61d4bd623 100644
--- a/docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md
+++ b/docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md
@@ -1,115 +1,3 @@
+文档已经移动到其他位置.
-## ASP.NET Core MVC 客户端包管理
-
-ABP框架可以与任何类型的客户端包管理系统一起使用. 甚至你可以决定不使用包管理系统并手动管理依赖项.
-
-但是, ABP框架最适用于**NPM/Yarn**. 默认情况下,内置模块配置为与NPM/Yarn一起使用.
-
-最后, 我们建议[**Yarn**](https://yarnpkg.com/)而不是NPM,因为它更快,更稳定并且与NPM兼容.
-
-### @ABP NPM Packages
-
-ABP是一个模块化平台. 每个开发人员都可以创建模块, 模块应该在**兼容**和**稳定**状态下协同工作.
-
-一个挑战是依赖NPM包的**版本**. 如果两个不同的模块使用相同的JavaScript库但其不同(并且可能不兼容)的版本会怎样.
-
-为了解决版本问题, 我们创建了一套**标准包**, 这取决于一些常见的第三方库. 一些示例包是[@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@ abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap)和[@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). 你可以从[Github存储库](https://github.com/volosoft/abp/tree/master/npm/packs)中查看**列表**.
-
-**标准包**的好处是:
-
-* 它取决于包装的**标准版本**. 取决于此包是**安全**,因为所有模块都依赖于相同的版本.
-* 它包含将库资源(js,css,img...文件)从**node_modules**文件夹复制到**wwwroot/libs**文件夹的gulp任务. 有关更多信息, 请参阅 *映射库资源* 部分.
-
-依赖标准包装很容易. 只需像往常一样将它添加到**package.json**文件中. 例如:
-
-````
- {
- ...
- "dependencies": {
- "@abp/bootstrap": "^1.0.0"
- }
- }
-````
-
-建议依赖于标准软件包, 而不是直接依赖于第三方软件包.
-
-#### 安装包
-
-依赖于NPM包后, 你应该做的就是从命令行运行**yarn**命令来安装所有包及其依赖项:
-
-````
-yarn
-````
-
-虽然你可以使用`npm install`,但如前所述,建议使用[Yarn](https://yarnpkg.com/).
-
-#### 贡献包
-
-如果你需要不在标准软件包中的第三方NPM软件包,你可以在Github[repository](https://github.com/volosoft/abp)上创建Pull请求. 接受遵循这些规则的拉取请求:
-
-* 对于NPM上的`package-name`, 包名称应该命名为`@abp/package-name`(例如:`bootstrap`包的`@abp/bootstrap`).
-* 它应该是**最新的稳定**版本的包.
-* 它应该只依赖于**单个**第三方包. 它可以依赖于多个`@abp/*`包.
-* 包应包含一个`abp.resourcemapping.js`文件格式,如*映射库资源*部分中所定义. 此文件应仅映射所依赖包的资源.
-* 你还需要为你创建的包创建[bundle贡献者](Bundling-Minification.md).
-
-有关示例, 请参阅当前标准包.
-
-### 映射库资源
-
-使用NPM包和NPM/Yarn工具是客户端库的事实标准. NPM/Yarn工具在Web项目的根文件夹中创建一个**node_modules**文件夹.
-
-下一个挑战是将所需的资源(js,css,img ...文件)从`node_modules`复制到**wwwroot**文件夹内的文件夹中,以使其可供客户端/浏览器访问.
-
-ABP将基于[Gulp](https://gulpjs.com/)的任务定义为**将资源**从**node_modules**复制到**wwwroot/libs**文件夹. 每个**标准包**(参见*@ABP NPM Packages*部分)定义了自己文件的映射. 因此, 大多数情况你只配置依赖项.
-
-**启动模板**已经配置为开箱即用的所有这些. 本节将介绍配置选项.
-
-#### 资源映射定义文件
-
-模块应该定义一个名为`abp.resourcemapping.js`的JavaScript文件,其格式如下例所示:
-
-````js
-module.exports = {
- aliases: {
- "@node_modules": "./node_modules",
- "@libs": "./wwwroot/libs"
- },
- clean: [
- "@libs"
- ],
- mappings: {
-
- }
-}
-````
-
-* **aliases**部分定义了可在映射路径中使用的标准别名(占位符). **@node_modules**和 **@libs**是必需的(通过标准包), 你可以定义自己的别名以减少重复.
-* **clean**部分是在复制文件之前要清理的文件夹列表.
-* **mappings**部分是要复制的文件/文件夹的映射列表.此示例不会复制任何资源本身,但取决于标准包.
-
-示例映射配置如下所示:
-
-````js
-mappings: {
- "@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/",
- "@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/"
-}
-````
-
-#### 使用 Gulp
-
-正确配置`abp.resourcemapping.js`文件后, 可以从命令行运行gulp命令:
-
-````
-gulp
-````
-
-当你运行`gulp`时,所有包都会将自己的资源复制到**wwwroot/libs**文件夹中. 只有在**package.json**文件中对依赖项进行更改时, 才需要运行`yarn&gulp`.
-
-> 运行Gulp命令时, 使用package.json文件解析应用程序的依赖关系. Gulp任务自动发现并映射来自所有依赖项的所有资源(递归).
-
-#### 参见
-
-* [捆绑 & 压缩](Bundling-Minification.md)
-* [主题](Theming.md)
+[点击链接跳转到ASP.NET Core MVC 客户端包管理文档](../UI/AspNetCore/Client-Side-Package-Management.md)
\ No newline at end of file
diff --git a/docs/zh-Hans/AspNetCore/Dynamic-CSharp-API-Clients.md b/docs/zh-Hans/AspNetCore/Dynamic-CSharp-API-Clients.md
index f964ac9013..6f45ba4549 100644
--- a/docs/zh-Hans/AspNetCore/Dynamic-CSharp-API-Clients.md
+++ b/docs/zh-Hans/AspNetCore/Dynamic-CSharp-API-Clients.md
@@ -1,165 +1,3 @@
-# 动态 C# API 客户端
+文档已经移动到其他位置.
-ABP可以自动创建C# API 客户端代理来调用远程HTTP服务(REST APIS).通过这种方式,你不需要通过 `HttpClient` 或者其他低级的HTTP功能调用远程服务并获取数据.
-
-## 服务接口
-
-你的service或controller需要实现一个在服务端和客户端共享的接口.因此,首先需要在一个共享的类库项目中定义一个服务接口.例如:
-
-````csharp
-public interface IBookAppService : IApplicationService
-{
- Task> GetListAsync();
-}
-````
-
-为了能自动被发现,你的接口需要实现`IRemoteService`接口.由于`IApplicationService`继承自`IRemoteService`接口.所以`IBookAppService`完全满足这个条件.
-
-在你的服务中实现这个类,你可以使用[auto API controller system](Auto-API-Controllers.md)将你的服务暴漏为一个REST API 端点.
-
-## 客户端代理生成
-
-首先,将[Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget包添加到你的客户端项目中:
-
-````
-Install-Package Volo.Abp.Http.Client
-````
-
-然后给你的模块添加`AbpHttpClientModule`依赖:
-
-````csharp
-[DependsOn(typeof(AbpHttpClientModule))] //添加依赖
-public class MyClientAppModule : AbpModule
-{
-}
-````
-
-现在,已经可以创建客户端代理了.例如:
-
-````csharp
-[DependsOn(
- typeof(AbpHttpClientModule), //用来创建客户端代理
- typeof(BookStoreApplicationModule) //包含应用服务接口
- )]
-public class MyClientAppModule : AbpModule
-{
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- //创建动态客户端代理
- context.Services.AddHttpClientProxies(
- typeof(BookStoreApplicationModule).Assembly
- );
- }
-}
-````
-
-`AddHttpClientproxies`方法获得一个程序集,找到这个程序集中所有的服务接口,创建并注册代理类.
-
-### Endpoint配置
-
-`appsettings.json`文件中的`RemoteServices`节点被用来设置默认的服务地址.下面是最简单的配置:
-
-````
-{
- "RemoteServices": {
- "Default": {
- "BaseUrl": "http://localhost:53929/"
- }
- }
-}
-````
-
-查看下面的"RemoteServiceOptions"章节获取更多详细配置.
-
-## 使用
-
-可以很直接地使用.只需要在你的客户端程序中注入服务接口:
-
-````csharp
-public class MyService : ITransientDependency
-{
- private readonly IBookAppService _bookService;
-
- public MyService(IBookAppService bookService)
- {
- _bookService = bookService;
- }
-
- public async Task DoIt()
- {
- var books = await _bookService.GetListAsync();
- foreach (var book in books)
- {
- Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}");
- }
- }
-}
-````
-
-本例注入了上面定义的`IBookAppService`服务接口.当客户端调用服务方法的时候动态客户端代理就会创建一个HTTP调用.
-
-### IHttpClientProxy接口
-
-你可以像上面那样注入`IBookAppService`来使用客户端代理,也可以注入`IHttpClientProxy`获取更多明确的用法.这种情况下你可以使用`IHttpClientProxy`接口的`Service`属性.
-
-## 配置
-
-### RemoteServiceOptions
-
-默认情况下`RemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如:
-
-````csharp
-public override void ConfigureServices(ServiceConfigurationContext context)
-{
- context.Services.Configure(options =>
- {
- options.RemoteServices.Default =
- new RemoteServiceConfiguration("http://localhost:53929/");
- });
-
- //...
-}
-````
-
-### 多个远程服务端点
-
-上面的例子已经配置了"Default"远程服务端点.你可能需要为不同的服务创建不同的端点.(就像在微服务方法中一样,每个微服务具有不同的端点).在这种情况下,你可以在你的配置文件中添加其他的端点:
-
-````json
-{
- "RemoteServices": {
- "Default": {
- "BaseUrl": "http://localhost:53929/"
- },
- "BookStore": {
- "BaseUrl": "http://localhost:48392/"
- }
- }
-}
-````
-
-`AddHttpClientProxies`方法有一个可选的参数来定义远程服务的名字:
-
-````csharp
-context.Services.AddHttpClientProxies(
- typeof(BookStoreApplicationModule).Assembly,
- remoteServiceName: "BookStore"
-);
-````
-
-`remoteServiceName`参数会匹配通过`RemoteServiceOptions`配置的服务端点.如果`BookStore`端点没有定义就会使用默认的`Default`端点.
-
-### 作为默认服务
-
-当你为`IBookAppService`创建了一个服务代理,你可以直接注入`IBookAppService`来使用代理客户端(像上面章节中将的那样).你可以传递`asDefaultService:false`到`AddHttpClientProxies`方法来禁用此功能.
-
-````csharp
-context.Services.AddHttpClientProxies(
- typeof(BookStoreApplicationModule).Assembly,
- asDefaultServices: false
-);
-````
-
-如果你的程序中已经有一个服务的实现并且你不想用你的客户端代理重写或替换其他的实现,就需要使用`asDefaultServices:false`
-
-> 如果你禁用了`asDefaultService`,你只能使用`IHttpClientProxy`接口去使用客户端代理.(参见上面的相关章节).
\ No newline at end of file
+[点击链接跳转到动态 C# API 客户端文档](../API/Dynamic-CSharp-API-Clients.md)
\ No newline at end of file
diff --git a/docs/zh-Hans/AspNetCore/JavaScript-API/Auth.md b/docs/zh-Hans/AspNetCore/JavaScript-API/Auth.md
index 60c9eb8866..857606a0f2 100644
--- a/docs/zh-Hans/AspNetCore/JavaScript-API/Auth.md
+++ b/docs/zh-Hans/AspNetCore/JavaScript-API/Auth.md
@@ -1,3 +1,3 @@
-# abp.auth JavaScript API
+文档已经移动到其他位置.
-TODO
\ No newline at end of file
+[点击链接跳转到JavaScript Auth文档](../../API/JavaScript-API/Auth.md)
diff --git a/docs/zh-Hans/AspNetCore/JavaScript-API/Index.md b/docs/zh-Hans/AspNetCore/JavaScript-API/Index.md
index 87d3b3021d..9f3e4d1645 100644
--- a/docs/zh-Hans/AspNetCore/JavaScript-API/Index.md
+++ b/docs/zh-Hans/AspNetCore/JavaScript-API/Index.md
@@ -1,24 +1,3 @@
-# JavaScript API
-
-ABP为ASP.NET Core MVC / Razor页面应用程序提供了一些执行客户端常见需求的JavaScrpt Api.
-
-## APIs
-
-* abp.ajax
-* [abp.auth](Auth.md)
-* abp.currentUser
-* abp.dom
-* abp.event
-* abp.features
-* abp.localization
-* abp.log
-* abp.ModalManager
-* abp.notify
-* abp.security
-* abp.setting
-* abp.ui
-* abp.utils
-* abp.ResourceLoader
-* abp.WidgetManager
-* Other APIs
+文档已经移动到其他位置.
+[点击链接跳转到JavaScript API文档](../../API/JavaScript-API/Index.md)
diff --git a/docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md b/docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md
new file mode 100644
index 0000000000..848d043312
--- /dev/null
+++ b/docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md
@@ -0,0 +1,94 @@
+# 按钮
+
+ABP框架定义了Tag Helper用于简单的创建bootstrap按钮.
+
+``
+
+## 属性
+
+`` 有7个不同的属性.
+
+* [`button-type`](#button-type)
+* [`size`](#size)
+* [`busy-text`](#busy-text)
+* [`text`](#text)
+* [`icon`](#icon)
+* [`disabled`](#disabled)
+* [`icon-type`](#icon-type)
+
+### `button-type`
+
+`button-type` 是一个可选参数. 它的默认值是 `Default`.
+
+`Button`
+
+你可以为按钮选择以下按钮类型:
+
+* `Default`
+* `Primary`
+* `Secondary`
+* `Success`
+* `Danger`
+* `Warning`
+* `Info`
+* `Light`
+* `Dark`
+* `Outline_Primary`
+* `Outline_Secondary`
+* `Outline_Success`
+* `Outline_Danger`
+* `Outline_Warning`
+* `Outline_Info`
+* `Outline_Light`
+* `Outline_Dark`
+* `Link`
+
+### `size`
+
+`size` 是一个可选参数. 它的默认值是 `Default`.
+
+`Button`
+
+你可以为按钮选择以下size类型:
+
+* `Default`
+* `Small`
+* `Medium`
+* `Large`
+* `Block`
+* `Block_Small`
+* `Block_Medium`
+* `Block_Large`
+
+### `busy-text`
+
+`busy-text` 是一个字符串类型参数. 当按钮繁忙时设置该文本.
+
+### `text`
+
+`text` 是一个字符串类型参数,显示在按钮上.
+
+### `icon`
+
+`icon` 是一个字符串类型参数. 它的值取决于[`icon-type`](#`icon-type`). 默认情况下,我们对图标使用[Font Awesome](https://fontawesome.com/). 要使用它,你需要将 `icon` 参数设置为图标名称.
+
+##### 示例
+
+[fa-address-card](https://fontawesome.com/icons/address-card): 
+
+``
+
+> 不要忘记: 你不需要写前缀,如果你没有更改 `icon-type` ,它会为[Font Awesome](https://fontawesome.com/)图标自动添加 `fa` 前缀.
+
+### `disabled`
+
+`disabled` 是一个布尔类型参数. 如果你设值为 `true`, 按钮会被禁用.
+
+### `icon-type`
+
+`icon-type` 是一个可选参数。它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它.
+
+你可以为按钮选择以下图标类型:
+
+* `FontAwesome`
+* `Other`
diff --git a/docs/zh-Hans/AspNetCore/Tag-Helpers/Dynamic-Forms.md b/docs/zh-Hans/AspNetCore/Tag-Helpers/Dynamic-Forms.md
index ceaa3d3d69..71e25d25ea 100644
--- a/docs/zh-Hans/AspNetCore/Tag-Helpers/Dynamic-Forms.md
+++ b/docs/zh-Hans/AspNetCore/Tag-Helpers/Dynamic-Forms.md
@@ -1,3 +1,3 @@
-## Dynamic Forms
+文档已经移动到其他位置.
-目前还没有文档. 你现在可以看到[组件演示](http://bootstrap-taghelpers.abp.io/Components/DynamicForms).
\ No newline at end of file
+[点击链接跳转到Dynamic Forms文档](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md)
\ No newline at end of file
diff --git a/docs/zh-Hans/AspNetCore/Tag-Helpers/Index.md b/docs/zh-Hans/AspNetCore/Tag-Helpers/Index.md
index d4cadc9b18..96ca033863 100644
--- a/docs/zh-Hans/AspNetCore/Tag-Helpers/Index.md
+++ b/docs/zh-Hans/AspNetCore/Tag-Helpers/Index.md
@@ -1,3 +1,3 @@
-## ABP Tag Helpers
+文档已经移动到其他位置.
-"ABP tag helpers" 目前还没有文档. 你现在可以看到[组件演示](http://bootstrap-taghelpers.abp.io/).
\ No newline at end of file
+[点击链接跳转到ABP Tag Helpers文档](../../UI/AspNetCore/Tag-Helpers/Index.md)
diff --git a/docs/zh-Hans/AspNetCore/Tag-Helpers/fa-address-card.png b/docs/zh-Hans/AspNetCore/Tag-Helpers/fa-address-card.png
new file mode 100644
index 0000000000..a3b2c815b8
Binary files /dev/null and b/docs/zh-Hans/AspNetCore/Tag-Helpers/fa-address-card.png differ
diff --git a/docs/zh-Hans/AspNetCore/Theming.md b/docs/zh-Hans/AspNetCore/Theming.md
index 470ef1a458..4a863567f1 100644
--- a/docs/zh-Hans/AspNetCore/Theming.md
+++ b/docs/zh-Hans/AspNetCore/Theming.md
@@ -1,3 +1,4 @@
-# Theming
-TODO
\ No newline at end of file
+文档已经移动到其他位置.
+
+[点击链接跳转到Theming文档](../UI/AspNetCore/Theming.md)
\ No newline at end of file
diff --git a/docs/zh-Hans/AspNetCore/Widgets.md b/docs/zh-Hans/AspNetCore/Widgets.md
index a10e7e462f..423169b6fc 100644
--- a/docs/zh-Hans/AspNetCore/Widgets.md
+++ b/docs/zh-Hans/AspNetCore/Widgets.md
@@ -1,274 +1,4 @@
-# 小部件
-ABP为创建**可重用的部件**提供了模型和基础设施. 部件系统是[ASP.NET Core ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components)的扩展. 在你有以下需求时,小部件会非常有用;
+文档已经移动到其他位置.
-* 在可复用的 **[模块](../Module-Development-Basics.md)** 中定义部件.
-* 在部件中引用 **scripts & styles** 脚本.
-* 使用部件创建 **[仪表盘](Dashboards.md)**.
-* 支持 **[授权](../Authorization.md)** 与 **[捆绑`bundling`](Bundling-Minification.md)** 的部件
-
-## 基本部件定义
-
-### 创建一个视图组件
-
-第一部,创建一个新的ASP.NET Core View Component:
-
-
-
-**MySimpleWidgetViewComponent.cs**:
-
-````csharp
-using Microsoft.AspNetCore.Mvc;
-using Volo.Abp.AspNetCore.Mvc;
-
-namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
-{
- public class MySimpleWidgetViewComponent : AbpViewComponent
- {
- public IViewComponentResult Invoke()
- {
- return View();
- }
- }
-}
-````
-
-继承 `AbpViewComponent` 不是必需的. 你也可以继承ASP.NET Core的 `ViewComponent`. `AbpViewComponent` 只是定义了一些基本的实用属性.
-
-**Default.cshtml**:
-
-```xml
-
}
@(await Component.InvokeAsync())
@RenderBody()
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/Default.cshtml
index ea379156a4..39b079f802 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/Default.cshtml
@@ -26,15 +26,30 @@
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/Default.cshtml
index b37c877f34..d40637735c 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/Default.cshtml
@@ -2,7 +2,7 @@
-
+ Card TitleSome quick example text to build on the card title and make up the bulk of the card's content.
@@ -46,7 +46,7 @@
-
+ Card TitleSome quick example text to build on the card title and make up the bulk of the card's content.
@@ -97,6 +97,155 @@
+
+
+
+
+ Quote
+
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.
+
+
+
+
+
+
+
+ Featured
+
+ Special title treatment
+ With supporting text below as a natural lead-in to additional content.
+ Go somewhere
+
+ 2 days ago
+
+
+
+
+
+
+
+ Quote
+
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.
+
+
+
+
+
+ Featured
+
+ Special title treatment
+ With supporting text below as a natural lead-in to additional content.
+ Go somewhere
+
+ 2 days ago
+
+
+
+
+
+
+ Special title treatment
+ With supporting text below as a natural lead-in to additional content.
+ Go somewhere
+
+
+
+
+ Special title treatment
+ With supporting text below as a natural lead-in to additional content.
+ Go somewhere
+
+
+
+
+ Special title treatment
+ With supporting text below as a natural lead-in to additional content.
+ Go somewhere
+
+
+
+
+
+
+
+
+ Card Title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+ Go somewhere
+
+
+
+
+
+
+ Header
+
+ Primary card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
+ Header
+
+ Secondary card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
+ Header
+
+ Success card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
+ Header
+
+ Danger card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
+ Header
+
+ Warning card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
+ Header
+
+ Info card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
+ Header
+
+ Light card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
+ Header
+
+ Dark card title
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+
+
Featured
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/Default.cshtml
index f21d4fc3c7..1a9587b600 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/Default.cshtml
@@ -10,9 +10,6 @@
Something else here
-
-
-
@@ -21,9 +18,6 @@
Something else here
-
-
-
@@ -47,9 +41,6 @@
Separated link
-
-
-
@@ -59,7 +50,8 @@
Separated link
-
+
+ Action
@@ -68,7 +60,8 @@
Separated link
-
+
+ Action
@@ -77,7 +70,8 @@
Separated link
-
+
+ Action
@@ -89,6 +83,7 @@
+
@@ -97,14 +92,16 @@
Another actionSomething else here
-
+
+ ActionAnother actionSomething else here
-
+
+ Action
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs
index 63b411f1a2..3681eda627 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs
@@ -6,7 +6,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S
[Widget]
public class DynamicFormsDemoViewComponent : AbpViewComponent
{
- public const string ViewPath = "/Views/Components/Themes/Shared/Demos/DynamicForms/Default.cshtml";
+ public const string ViewPath = "/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/Default.cshtml";
public IViewComponentResult Invoke()
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs
index ef1f509e02..01c477ecb1 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs
@@ -6,7 +6,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S
[Widget]
public class FormElementsDemoViewComponent : AbpViewComponent
{
- public const string ViewPath = "/Views/Components/Themes/Shared/Demos/FormElements/Default.cshtml";
+ public const string ViewPath = "/Views/Components/Themes/Shared/Demos/FormElementsDemo/Default.cshtml";
public IViewComponentResult Invoke()
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/Default.cshtml
index ff11769976..69dfbd24c4 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/Default.cshtml
@@ -18,7 +18,7 @@
-
+Cras justo odioDapibus ac facilisis in
@@ -27,12 +27,12 @@
-
+
- Cras justo odio
- Dapibus ac facilisis in
- Morbi leo risus
- Vestibulum at eros
+ Cras justo odio
+ Dapibus ac facilisis in
+ Morbi leo risus
+ Vestibulum at eros
@@ -59,7 +59,7 @@
-
+Cras justo odioA simple Primary list group item
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/Default.cshtml
new file mode 100644
index 0000000000..f0c5113054
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/Default.cshtml
@@ -0,0 +1,71 @@
+@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.NavbarsDemo
+
+
+
+ Navbar
+
+
+
+ Home (current)
+
+
+ Link
+
+
+
+
+
+ Dropdown header
+ Action
+ Another disabled action
+ Something else here
+
+ Separated link
+
+
+
+
+ Disabled
+
+
+
+ Sample Text
+
+
+
+
+
+
+
+ Navbar
+
+
+
+ Home (current)
+
+
+ Link
+
+
+
+
+
+ Dropdown header
+ Action
+ Another disabled action
+ Something else here
+
+ Separated link
+
+
+
+
+ Disabled
+
+
+
+ Sample Text
+
+
+
+
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/NavbarsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/NavbarsDemoViewComponent.cs
new file mode 100644
index 0000000000..e282f1b923
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/NavbarsDemoViewComponent.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
+
+namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.NavbarsDemo
+{
+ [Widget]
+ public class NavbarsDemoViewComponent : AbpViewComponent
+ {
+ public const string ViewPath = "/Views/Components/Themes/Shared/Demos/NavbarsDemo/Default.cshtml";
+
+ public IViewComponentResult Invoke()
+ {
+ return View(ViewPath);
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/Default.cshtml
index 033fefa45b..1423aea2b6 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/Default.cshtml
@@ -8,6 +8,16 @@
Longer nav link
+
+
+
+
+ Action
+ Another action
+ Something else here
+
+
+ link
@@ -17,37 +27,104 @@
-
-
- Navbar
-
-
-
- Home (current)
-
-
- Link
-
-
-
-
-
- Dropdown header
- Action
- Another disabled action
- Something else here
-
- Separated link
-
-
-
-
- Disabled
-
-
-
- Sample Text
-
-
-
+
+
+
+ Active
+
+
+ Longer nav link
+
+
+
+
+
+ Action
+ Another action
+ Something else here
+
+
+
+
+ link
+
+
+ disabled
+
+
+
+
+ Active
+
+
+ Longer nav link
+
+
+
+
+
+ Action
+ Another action
+ Something else here
+
+
+
+
+ link
+
+
+ disabled
+
+
+
+
+ Active
+
+
+ Longer nav link
+
+
+
+
+
+ Action
+ Another action
+ Something else here
+
+
+
+
+ link
+
+
+ disabled
+
+
+
+
+
+
+ Active
+
+
+ Longer nav link
+
+
+
+
+
+ Action
+ Another action
+ Something else here
+
+
+
+
+ link
+
+
+ disabled
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/Default.cshtml
index 8e171cb640..1efee90533 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/Default.cshtml
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/Default.cshtml
@@ -1,16 +1,16 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.PopoversDemo
-
+
Popover Default
-
+
Popover With Title
-
+
Dismissible Popover
-
+
Disabled Popover
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs
index c808250a3e..31768b5e6a 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Http
{
@@ -11,5 +12,13 @@ namespace Microsoft.AspNetCore.Http
return stream.GetAllBytes();
}
}
+
+ public static async Task GetAllBytesAsync(this IFormFile file)
+ {
+ using (var stream = file.OpenReadStream())
+ {
+ return await stream.GetAllBytesAsync();
+ }
+ }
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
index 06ce49551b..21a675ddc4 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
@@ -43,21 +43,6 @@ namespace Volo.Abp.AspNetCore.Mvc
public override void ConfigureServices(ServiceConfigurationContext context)
{
- //Configure Razor
- context.Services.Insert(0,
- ServiceDescriptor.Singleton>(
- new ConfigureOptions(options =>
- {
- options.FileProviders.Add(
- new RazorViewEngineVirtualFileProvider(
- context.Services.GetSingletonInstance>()
- )
- );
- }
- )
- )
- );
-
Configure(options =>
{
options.IgnoredInterfaces.AddIfNotContains(typeof(IAsyncActionFilter));
@@ -101,7 +86,16 @@ namespace Volo.Abp.AspNetCore.Mvc
};
})
.AddViewLocalization(); //TODO: How to configure from the application? Also, consider to move to a UI module since APIs does not care about it.
-
+
+ Configure(options =>
+ {
+ options.FileProviders.Add(
+ new RazorViewEngineVirtualFileProvider(
+ context.Services.GetSingletonInstance>()
+ )
+ );
+ });
+
context.Services.ExecutePreConfiguredActions(mvcBuilder);
//TODO: AddViewLocalization by default..?
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
index 69f75a31b2..4b4cf521c2 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
@@ -8,9 +8,11 @@ using System.Globalization;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services;
+using Volo.Abp.AspNetCore.Mvc.MultiTenancy;
using Volo.Abp.Authorization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
+using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.Users;
@@ -19,6 +21,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApplicationConfigurationAppService
{
private readonly AbpLocalizationOptions _localizationOptions;
+ private readonly AbpMultiTenancyOptions _multiTenancyOptions;
private readonly IServiceProvider _serviceProvider;
private readonly IAbpAuthorizationPolicyProvider _abpAuthorizationPolicyProvider;
private readonly IAuthorizationService _authorizationService;
@@ -30,6 +33,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
public AbpApplicationConfigurationAppService(
IOptions localizationOptions,
+ IOptions multiTenancyOptions,
IServiceProvider serviceProvider,
IAbpAuthorizationPolicyProvider abpAuthorizationPolicyProvider,
IAuthorizationService authorizationService,
@@ -48,6 +52,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
_featureDefinitionManager = featureDefinitionManager;
_languageProvider = languageProvider;
_localizationOptions = localizationOptions.Value;
+ _multiTenancyOptions = multiTenancyOptions.Value;
}
public virtual async Task GetAsync()
@@ -60,11 +65,31 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
Features = await GetFeaturesConfigAsync(),
Localization = await GetLocalizationConfigAsync(),
CurrentUser = GetCurrentUser(),
- Setting = await GetSettingConfigAsync()
+ Setting = await GetSettingConfigAsync(),
+ MultiTenancy = GetMultiTenancy(),
+ CurrentTenant = GetCurrentTenant()
};
}
+ protected virtual CurrentTenantDto GetCurrentTenant()
+ {
+ return new CurrentTenantDto()
+ {
+ Id = CurrentTenant.Id,
+ Name = CurrentTenant.Name,
+ IsAvailable = CurrentTenant.IsAvailable
+ };
+ }
+
+ protected virtual MultiTenancyInfoDto GetMultiTenancy()
+ {
+ return new MultiTenancyInfoDto
+ {
+ IsEnabled = _multiTenancyOptions.IsEnabled
+ };
+ }
+
protected virtual CurrentUserDto GetCurrentUser()
{
return new CurrentUserDto
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
index 9875ccd808..0254a6022b 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
@@ -12,6 +12,7 @@ using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.Http.Modeling;
+using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.Conventions
@@ -81,7 +82,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
continue;
}
- if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType))
+ if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType, includeEnums: true))
{
if (CanUseFormBodyBinding(action, prm))
{
@@ -94,7 +95,15 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
{
- if (_options.ConventionalControllers.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
+ //We want to use "id" as path parameter, not body!
+ if (parameter.ParameterName == "id")
+ {
+ return false;
+ }
+
+ if (_options.ConventionalControllers
+ .FormBodyBindingIgnoredTypes
+ .Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
{
return false;
}
@@ -251,7 +260,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
if (!selector.ActionConstraints.OfType().Any())
{
- selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] {httpMethod}));
+ selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod }));
}
}
}
@@ -295,9 +304,24 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
var url = $"api/{rootPath}/{controllerNameInUrl.ToCamelCase()}";
//Add {id} path if needed
- if (action.Parameters.Any(p => p.ParameterName == "id"))
+ var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
+ if (idParameterModel != null)
{
- url += "/{id}";
+ if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
+ {
+ url += "/{id}";
+ }
+ else
+ {
+ var properties = idParameterModel
+ .ParameterType
+ .GetProperties(BindingFlags.Instance | BindingFlags.Public);
+
+ foreach (var property in properties)
+ {
+ url += "/{" + property.Name + "}";
+ }
+ }
}
//Add action name if needed
@@ -341,7 +365,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration)
{
- if(configuration?.UrlControllerNameNormalizer == null)
+ if (configuration?.UrlControllerNameNormalizer == null)
{
return controllerName;
}
@@ -364,7 +388,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual bool IsEmptySelector(SelectorModel selector)
{
- return selector.AttributeRouteModel == null
+ return selector.AttributeRouteModel == null
&& selector.ActionConstraints.IsNullOrEmpty()
&& selector.EndpointMetadata.IsNullOrEmpty();
}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs
index 1ce71ca3f9..3326c4e65f 100644
--- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs
+++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs
@@ -1,5 +1,7 @@
+using System;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@@ -11,14 +13,34 @@ namespace Volo.Abp.BackgroundWorkers
public abstract class BackgroundWorkerBase : IBackgroundWorker
{
//TODO: Add UOW, Localization and other useful properties..?
+ public IServiceProvider ServiceProvider { get; set; }
+ protected readonly object ServiceProviderLock = new object();
- public ILogger Logger { protected get; set; }
+ protected TService LazyGetRequiredService(ref TService reference)
+ => LazyGetRequiredService(typeof(TService), ref reference);
- protected BackgroundWorkerBase()
+ protected TRef LazyGetRequiredService(Type serviceType, ref TRef reference)
{
- Logger = NullLogger.Instance;
+ if (reference == null)
+ {
+ lock (ServiceProviderLock)
+ {
+ if (reference == null)
+ {
+ reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
+ }
+ }
+ }
+
+ return reference;
}
-
+
+ public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
+ private ILoggerFactory _loggerFactory;
+
+ protected ILogger Logger => _lazyLogger.Value;
+ private Lazy _lazyLogger => new Lazy(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
+
public virtual Task StartAsync(CancellationToken cancellationToken = default)
{
Logger.LogDebug("Started background worker: " + ToString());
diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs
new file mode 100644
index 0000000000..ccb3beac5d
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs
@@ -0,0 +1,38 @@
+using System;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Volo.Abp.BackgroundWorkers
+{
+ public static class BackgroundWorkersApplicationInitializationContextExtensions
+ {
+ public static ApplicationInitializationContext AddBackgroundWorker([NotNull] this ApplicationInitializationContext context)
+ where TWorker : IBackgroundWorker
+ {
+ Check.NotNull(context, nameof(context));
+
+ context.AddBackgroundWorker(typeof(TWorker));
+
+ return context;
+ }
+
+ public static ApplicationInitializationContext AddBackgroundWorker([NotNull] this ApplicationInitializationContext context, [NotNull] Type workerType)
+ {
+ Check.NotNull(context, nameof(context));
+ Check.NotNull(workerType, nameof(workerType));
+
+ if (!workerType.IsAssignableTo())
+ {
+ throw new AbpException($"Given type ({workerType.AssemblyQualifiedName}) must implement the {typeof(IBackgroundWorker).AssemblyQualifiedName} interface, but it doesn't!");
+ }
+
+ context.ServiceProvider
+ .GetRequiredService()
+ .Add(
+ (IBackgroundWorker)context.ServiceProvider.GetRequiredService(workerType)
+ );
+
+ return context;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
index 34bf95fdf1..63fb4f8eee 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
@@ -30,6 +30,8 @@ namespace Volo.Abp.Cli
options.Commands["login"] = typeof(LoginCommand);
options.Commands["logout"] = typeof(LogoutCommand);
options.Commands["suite"] = typeof(SuiteCommand);
+ options.Commands["switch-to-preview"] = typeof(SwitchNightlyPreviewCommand);
+ options.Commands["switch-to-stable"] = typeof(SwitchStableCommand);
});
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
index f25a4ad89f..04b9168a44 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
@@ -162,6 +162,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("");
sb.AppendLine("-t|--template (default: app)");
sb.AppendLine("-u|--ui (if supported by the template)");
+ sb.AppendLine("-m|--mobile (if supported by the template)");
sb.AppendLine("-d|--database-provider (if supported by the template)");
sb.AppendLine("-o|--output-folder (default: current folder)");
sb.AppendLine("-v|--version (default: latest version)");
@@ -177,6 +178,8 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp new Acme.BookStore --tiered");
sb.AppendLine(" abp new Acme.BookStore -u angular");
sb.AppendLine(" abp new Acme.BookStore -u angular -d mongodb");
+ sb.AppendLine(" abp new Acme.BookStore -m none");
+ sb.AppendLine(" abp new Acme.BookStore -m react-native");
sb.AppendLine(" abp new Acme.BookStore -d mongodb");
sb.AppendLine(" abp new Acme.BookStore -d mongodb -o d:\\my-project");
sb.AppendLine(" abp new Acme.BookStore -t module");
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs
new file mode 100644
index 0000000000..883ac4db2f
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs
@@ -0,0 +1,44 @@
+using System.Text;
+using System.Threading.Tasks;
+using Volo.Abp.Cli.Args;
+using Volo.Abp.Cli.ProjectModification;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public class SwitchNightlyPreviewCommand : IConsoleCommand, ITransientDependency
+ {
+ private readonly PackageSourceSwitcher _packageSourceSwitcher;
+
+ public SwitchNightlyPreviewCommand(PackageSourceSwitcher packageSourceSwitcher)
+ {
+ _packageSourceSwitcher = packageSourceSwitcher;
+ }
+
+ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
+ {
+ await _packageSourceSwitcher.SwitchToPreview(commandLineArgs);
+ }
+
+ public string GetUsageInfo()
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("");
+ sb.AppendLine("Usage:");
+ sb.AppendLine(" abp switch-to-preview [options]");
+ sb.AppendLine("");
+ sb.AppendLine("Options:");
+ sb.AppendLine("-sd|--solution-directory");
+ sb.AppendLine("");
+ sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
+
+ return sb.ToString();
+ }
+
+ public string GetShortDescription()
+ {
+ return "Switches packages to nightly preview ABP version.";
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs
new file mode 100644
index 0000000000..54db7bc082
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs
@@ -0,0 +1,44 @@
+using System.Text;
+using System.Threading.Tasks;
+using Volo.Abp.Cli.Args;
+using Volo.Abp.Cli.ProjectModification;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public class SwitchStableCommand : IConsoleCommand, ITransientDependency
+ {
+ private readonly PackageSourceSwitcher _packageSourceSwitcher;
+
+ public SwitchStableCommand(PackageSourceSwitcher packageSourceSwitcher)
+ {
+ _packageSourceSwitcher = packageSourceSwitcher;
+ }
+
+ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
+ {
+ await _packageSourceSwitcher.SwitchToStable(commandLineArgs);
+ }
+
+ public string GetUsageInfo()
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("");
+ sb.AppendLine("Usage:");
+ sb.AppendLine(" abp switch-to-stable [options]");
+ sb.AppendLine("");
+ sb.AppendLine("Options:");
+ sb.AppendLine("-sd|--solution-directory");
+ sb.AppendLine("");
+ sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
+
+ return sb.ToString();
+ }
+
+ public string GetShortDescription()
+ {
+ return "Switches packages to stable ABP version from preview version.";
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
index d201790c94..c44f77b749 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
@@ -42,13 +42,13 @@ namespace Volo.Abp.Cli.Commands
if (updateNpm || !updateNuget)
{
- UpdateNpmPackages(directory);
+ await UpdateNpmPackages(directory);
}
}
- private void UpdateNpmPackages(string directory)
+ private async Task UpdateNpmPackages(string directory)
{
- _npmPackagesUpdater.Update(directory);
+ await _npmPackagesUpdater.Update(directory);
}
private async Task UpdateNugetPackages(CommandLineArgs commandLineArgs, string directory)
@@ -94,7 +94,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine("");
- sb.AppendLine(" abp update [options]");
+ sb.AppendLine(" abp update [options]");
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine("-p|--include-previews (if supported by the template)");
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs
index 3cdd658403..0b70ae6cca 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs
@@ -95,21 +95,19 @@ namespace Volo.Abp.Cli.NuGet
.OrResult(msg => !msg.IsSuccessStatusCode)
.WaitAndRetryAsync(new[]
{
- TimeSpan.FromSeconds(1),
- TimeSpan.FromSeconds(3),
- TimeSpan.FromSeconds(7)
+ TimeSpan.FromSeconds(1)
},
(responseMessage, timeSpan, retryCount, context) =>
{
if (responseMessage.Exception != null)
{
- Logger.LogWarning(
+ Logger.LogDebug(
$"{retryCount}. HTTP request attempt failed to {url} with an error: HTTP {(int)responseMessage.Result.StatusCode}-{responseMessage.Exception.Message}. " +
$"Waiting {timeSpan.TotalSeconds} secs for the next try...");
}
else if (responseMessage.Result != null)
{
- Logger.LogWarning(
+ Logger.LogDebug(
$"{retryCount}. HTTP request attempt failed to {url} with an error: {(int)responseMessage.Result.StatusCode}-{responseMessage.Result.ReasonPhrase}. " +
$"Waiting {timeSpan.TotalSeconds} secs for the next try...");
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
index 03504251f5..263b675a2a 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
@@ -54,19 +54,21 @@ namespace Volo.Abp.Cli.ProjectBuilding
version = latestVersion;
}
+ var nugetVersion = (await GetTemplateNugetVersionAsync(name, type, version)) ?? version;
+
DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache);
if (!string.IsNullOrWhiteSpace(templateSource) && !IsNetworkSource(templateSource))
{
Logger.LogInformation("Using local " + type + ": " + name + ", version: " + version);
- return new TemplateFile(File.ReadAllBytes(Path.Combine(templateSource, name + "-" + version + ".zip")), version, latestVersion);
+ return new TemplateFile(File.ReadAllBytes(Path.Combine(templateSource, name + "-" + version + ".zip")), version, latestVersion, nugetVersion);
}
var localCacheFile = Path.Combine(CliPaths.TemplateCache, name + "-" + version + ".zip");
if (Options.CacheTemplates && File.Exists(localCacheFile) && templateSource.IsNullOrWhiteSpace())
{
Logger.LogInformation("Using cached " + type + ": " + name + ", version: " + version);
- return new TemplateFile(File.ReadAllBytes(localCacheFile), version, latestVersion);
+ return new TemplateFile(File.ReadAllBytes(localCacheFile), version, latestVersion, nugetVersion);
}
Logger.LogInformation("Downloading " + type + ": " + name + ", version: " + version);
@@ -86,7 +88,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
File.WriteAllBytes(localCacheFile, fileContent);
}
- return new TemplateFile(fileContent, version, latestVersion);
+ return new TemplateFile(fileContent, version, latestVersion, nugetVersion);
}
@@ -110,7 +112,38 @@ namespace Volo.Abp.Cli.ProjectBuilding
var result = await response.Content.ReadAsStringAsync();
- return JsonSerializer.Deserialize(result).Version;
+ return JsonSerializer.Deserialize(result).Version;
+ }
+ }
+
+ private async Task GetTemplateNugetVersionAsync(string name, string type, string version)
+ {
+ try
+ {
+ using (var client = new CliHttpClient(TimeSpan.FromMinutes(10)))
+ {
+ var response = await client.PostAsync(
+ $"{CliUrls.WwwAbpIo}api/download/{type}/get-nuget-version/",
+ new StringContent(
+ JsonSerializer.Serialize(
+ new GetTemplateNugetVersionDto { Name = name, Version = version }
+ ),
+ Encoding.UTF8,
+ MimeTypes.Application.Json
+ ),
+ CancellationTokenProvider.Token
+ );
+
+ await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response);
+
+ var result = await response.Content.ReadAsStringAsync();
+
+ return JsonSerializer.Deserialize(result).Version;
+ }
+ }
+ catch (Exception)
+ {
+ return null;
}
}
@@ -162,7 +195,14 @@ namespace Volo.Abp.Cli.ProjectBuilding
public string Name { get; set; }
}
- public class GetLatestSourceCodeVersionResultDto
+ public class GetTemplateNugetVersionDto
+ {
+ public string Name { get; set; }
+
+ public string Version { get; set; }
+ }
+
+ public class GetVersionResultDto
{
public string Version { get; set; }
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/MobileApp.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/MobileApp.cs
index 1a85471529..97132e8f79 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/MobileApp.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/MobileApp.cs
@@ -17,9 +17,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
{
case MobileApp.ReactNative:
return "react-native";
- default:
- return null;
}
+
+ throw new Exception("Mobile app folder name is not set!");
}
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs
index a7fd3f529f..5aaf4b1488 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs
@@ -29,7 +29,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
}
else
{
- var nugetPackageVersion = context.TemplateFile.Version;
+ var nugetPackageVersion = context.TemplateFile.RepositoryNugetVersion;
if (IsBranchName(nugetPackageVersion))
{
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateFile.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateFile.cs
index c7f9e77cb2..fc514e8a74 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateFile.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateFile.cs
@@ -6,13 +6,16 @@
public string LatestVersion { get; }
+ public string RepositoryNugetVersion { get; }
+
public byte[] FileBytes { get; }
- public TemplateFile(byte[] fileBytes, string version, string latestVersion)
+ public TemplateFile(byte[] fileBytes, string version, string latestVersion, string repositoryNugetVersion)
{
FileBytes = fileBytes;
Version = version;
LatestVersion = latestVersion;
+ RepositoryNugetVersion = repositoryNugetVersion;
}
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
index 9df2c03ee4..07ada2fe3d 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
@@ -77,7 +77,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
if (context.BuildArgs.MobileApp != MobileApp.ReactNative)
{
- steps.Add(new RemoveFolderStep(MobileApp.ReactNative.GetFolderName()?.EnsureStartsWith('/')));
+ steps.Add(new RemoveFolderStep(MobileApp.ReactNative.GetFolderName().EnsureStartsWith('/')));
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/TemplateRandomSslPortStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/TemplateRandomSslPortStep.cs
index c5f5292e1a..12bc34221a 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/TemplateRandomSslPortStep.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/TemplateRandomSslPortStep.cs
@@ -137,12 +137,15 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates
{
environment.NormalizeLineEndings();
+ var buildInUrlHttp = buildInUrl.Replace("https://", "http://");
+ var buildInUrlWithoutPortHttp = buildInUrlWithoutPort.Replace("https://", "http://");
+
var environmentLines = environment.GetLines();
for (var i = 0; i < environmentLines.Length; i++)
{
- if (environmentLines[i].Contains(buildInUrl))
+ if (environmentLines[i].Contains(buildInUrlHttp))
{
- environmentLines[i] = environmentLines[i].Replace(buildInUrl, $"{buildInUrlWithoutPort}:{newPort}");
+ environmentLines[i] = environmentLines[i].Replace(buildInUrlHttp, $"{buildInUrlWithoutPortHttp}:{newPort}");
}
}
environment.SetLines(environmentLines);
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs
new file mode 100644
index 0000000000..2d1e237933
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs
@@ -0,0 +1,198 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Cli.ProjectModification
+{
+ public class AngularModuleSourceCodeAdder : ITransientDependency
+ {
+ public async Task AddAsync(string solutionFilePath, string angularPath)
+ {
+ var angularProjectsPath = Path.Combine(angularPath, "projects");
+
+ var projects = await CopyAndGetNamesOfAngularProjectsAsync(solutionFilePath, angularProjectsPath);
+
+ if (!projects.Any())
+ {
+ return;
+ }
+
+ await ReplaceProjectNamesAndCopySourCodeRequirementsToProjectsAsync(angularProjectsPath, projects);
+
+ await RemoveRedundantFilesAsync(angularProjectsPath, projects);
+
+ await AddPathsToTsConfigAsync(angularPath, angularProjectsPath, projects);
+
+ await AddProjectToAngularJsonAsync(angularPath, projects);
+ }
+
+ private async Task AddProjectToAngularJsonAsync(string angularPath, List projects)
+ {
+ var angularJsonFilePath = Path.Combine(angularPath, "angular.json");
+ var fileContent = File.ReadAllText(angularJsonFilePath);
+
+ var json = JObject.Parse(fileContent);
+
+ var projectsJobject = (JObject)json["projects"];
+
+ foreach (var project in projects)
+ {
+ projectsJobject.Add(project, new JObject(
+ new JProperty("projectType", "library"),
+ new JProperty("root", $"projects/{project}"),
+ new JProperty("sourceRoot", $"projects/{project}/src"),
+ new JProperty("prefix", "lib"),
+ new JProperty("architect", new JObject(
+ new JProperty("build", new JObject(
+ new JProperty("builder", "@angular-devkit/build-ng-package:build"),
+ new JProperty("options", new JObject(
+ new JProperty("tsConfig", $"projects/{project}/tsconfig.lib.json"),
+ new JProperty("project", $"projects/{project}/ng-package.json")
+ )),
+ new JProperty("configurations", new JObject(
+ new JProperty("production", new JObject(
+ new JProperty("tsConfig", $"projects/{project}/tsconfig.lib.prod.json")))
+ )))),
+ new JProperty("test", new JObject(
+ new JProperty("builder", "@angular-devkit/build-angular:karma"),
+ new JProperty("options", new JObject(
+ new JProperty("main", $"projects/{project}/src/test.ts"),
+ new JProperty("tsConfig", $"projects/{project}/tsconfig.spec.json"),
+ new JProperty("karmaConfig", $"projects/{project}/karma.conf.js")
+ )
+ ))),
+ new JProperty("lint", new JObject(
+ new JProperty("builder", "@angular-devkit/build-angular:tslint"),
+ new JProperty("options", new JObject(
+ new JProperty("tsConfig", new JArray(new JValue($"projects/{project}/tsconfig.lib.json"), new JValue($"projects/{project}/tsconfig.spec.json"))),
+ new JProperty("exclude", new JArray(new JValue("**/node_modules/**")))
+ ))
+ ))
+ ))
+ ));
+ }
+
+ File.WriteAllText(angularJsonFilePath, json.ToString(Formatting.Indented));
+ }
+
+ private async Task AddPathsToTsConfigAsync(string angularPath, string angularProjectsPath, List projects)
+ {
+ var tsConfigPath = Path.Combine(angularPath, "tsconfig.json");
+ var fileContent = File.ReadAllText(tsConfigPath);
+
+ var tsConfigAsJson = JObject.Parse(fileContent);
+
+ var compilerOptions = (JObject)tsConfigAsJson["compilerOptions"];
+
+ foreach (var project in projects)
+ {
+ var projectPackageName = await GetProjectPackageNameAsync(angularProjectsPath, project);
+
+ var property = new JProperty($"{projectPackageName}",
+ new JArray(new object[] { $"projects/{project}/src/public-api.ts" })
+ );
+ var property2 = new JProperty($"{projectPackageName}/*",
+ new JArray(new object[] { $"projects/{project}/src/lib/*" })
+ );
+
+ if (compilerOptions["paths"] == null)
+ {
+
+ compilerOptions.Add("paths", new JObject());
+ }
+
+ ((JObject)compilerOptions["paths"]).Add(property);
+ ((JObject)compilerOptions["paths"]).Add(property2);
+ }
+
+ File.WriteAllText(tsConfigPath, tsConfigAsJson.ToString(Formatting.Indented));
+ }
+
+ private async Task GetProjectPackageNameAsync(string angularProjectsPath, string project)
+ {
+ var packageJsonPath = Path.Combine(angularProjectsPath, project, "package.json");
+
+ var fileContent = File.ReadAllText(packageJsonPath);
+
+ return (string)JObject.Parse(fileContent)["name"];
+ }
+
+ private async Task RemoveRedundantFilesAsync(string angularProjectsPath, List projects)
+ {
+ foreach (var project in projects)
+ {
+ var jestConfigPath = Path.Combine(angularProjectsPath, project, "jest.config.js");
+
+ File.Delete(jestConfigPath);
+
+ var testPath = Path.Combine(angularProjectsPath, project, "src", "test.ts");
+
+ File.Delete(testPath);
+ }
+ }
+
+ private async Task ReplaceProjectNamesAndCopySourCodeRequirementsToProjectsAsync(string angularProjectsPath, List projects)
+ {
+ foreach (var project in projects)
+ {
+ var sourceCodeReqFolder = Path.Combine(angularProjectsPath, project, "source-code-requirements");
+
+ Directory.CreateDirectory(sourceCodeReqFolder);
+
+ var filesUnderSourceCodeReqFolder = Directory.GetFiles(sourceCodeReqFolder);
+
+ foreach (var fileUnderSourceCodeReqFolder in filesUnderSourceCodeReqFolder)
+ {
+ var newDest = Path.Combine(angularProjectsPath, project, Path.GetFileName(fileUnderSourceCodeReqFolder));
+ File.Move(fileUnderSourceCodeReqFolder, newDest);
+
+ var fileContent = File.ReadAllText(newDest);
+ fileContent = fileContent.Replace("{{project-name}}", project);
+ fileContent = fileContent.Replace("{{library-name-kebab-case}}", project);
+ File.WriteAllText(newDest, fileContent);
+ }
+ }
+ }
+
+ private async Task> CopyAndGetNamesOfAngularProjectsAsync(string solutionFilePath, string angularProjectsPath)
+ {
+ var projects = new List();
+
+ if (!Directory.Exists(angularProjectsPath))
+ {
+ Directory.CreateDirectory(angularProjectsPath);
+ }
+
+ var angularPathsInDownloadedSourceCode = Directory.GetDirectories(Path.Combine(Path.Combine(Path.GetDirectoryName(solutionFilePath), "modules"))).Select(p => Path.Combine(p, "angular"))
+ .Where(Directory.Exists);
+
+
+ foreach (var folder in angularPathsInDownloadedSourceCode)
+ {
+ var projectsInFolder = Directory.GetDirectories(folder);
+ foreach (var projectInFolder in projectsInFolder)
+ {
+ var projectName = Path.GetFileName(projectInFolder.TrimEnd('\\').TrimEnd('/'));
+
+ var destDirName = Path.Combine(angularProjectsPath, projectName);
+ if (Directory.Exists(destDirName))
+ {
+ Directory.Delete(projectInFolder, true);
+ continue;
+ }
+
+
+ projects.Add(projectName);
+
+ Directory.Move(projectInFolder, destDirName);
+ }
+ }
+
+ return projects;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/MyGetApiResponse.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/MyGetApiResponse.cs
new file mode 100644
index 0000000000..9a32d8d6fc
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/MyGetApiResponse.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.Cli.ProjectModification
+{
+ public class MyGetApiResponse
+ {
+ public string _date { get; set; }
+
+ public List