Browse Source

Merge branch 'dev' into maliming/CascadeTiming

pull/2971/head
maliming 6 years ago
parent
commit
d98c393e99
  1. 1
      .gitignore
  2. 2
      README.md
  3. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json
  4. 13
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sl.json
  5. 90
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json
  6. 31
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sl.json
  7. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/sl.json
  8. 35
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json
  9. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/sl.json
  10. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/sl.json
  11. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  12. 6
      common.props
  13. 10
      docs/cs/AspNetCore/Widgets.md
  14. 3
      docs/cs/CLI.md
  15. 4
      docs/cs/Getting-Started-Angular-Template.md
  16. 2
      docs/cs/Getting-Started-AspNetCore-Application.md
  17. 4
      docs/cs/Getting-Started-AspNetCore-MVC-Template.md
  18. 140
      docs/en/API/Auto-API-Controllers.md
  19. 165
      docs/en/API/Dynamic-CSharp-API-Clients.md
  20. 3
      docs/en/API/JavaScript-API/Auth.md
  21. 24
      docs/en/API/JavaScript-API/Index.md
  22. 52
      docs/en/Application-Services.md
  23. 141
      docs/en/AspNetCore/Auto-API-Controllers.md
  24. 352
      docs/en/AspNetCore/Bundling-Minification.md
  25. 116
      docs/en/AspNetCore/Client-Side-Package-Management.md
  26. 166
      docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md
  27. 4
      docs/en/AspNetCore/JavaScript-API/Auth.md
  28. 25
      docs/en/AspNetCore/JavaScript-API/Index.md
  29. 4
      docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md
  30. 4
      docs/en/AspNetCore/Tag-Helpers/Index.md
  31. 5
      docs/en/AspNetCore/Theming.md
  32. 505
      docs/en/AspNetCore/Widgets.md
  33. 101
      docs/en/Authorization.md
  34. 73
      docs/en/Background-Jobs-Quartz.md
  35. 7
      docs/en/Background-Jobs.md
  36. 68
      docs/en/Background-Workers-Quartz.md
  37. 140
      docs/en/Background-Workers.md
  38. 2
      docs/en/Blog-Posts/2018-09-24-Announcement/Post.md
  39. 2
      docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
  40. 10
      docs/en/CLI.md
  41. 149
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  42. 62
      docs/en/Customizing-Application-Modules-Guide.md
  43. 166
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  44. 9
      docs/en/Customizing-Application-Modules-Overriding-User-Interface.md
  45. 95
      docs/en/Entities.md
  46. 21
      docs/en/Entity-Framework-Core-Migrations.md
  47. 6
      docs/en/Getting-Started-Angular-Template.md
  48. 2
      docs/en/Getting-Started-AspNetCore-Application.md
  49. 6
      docs/en/Getting-Started-AspNetCore-MVC-Template.md
  50. 3
      docs/en/IdentityServer-Integration.md
  51. 10
      docs/en/Localization.md
  52. 4
      docs/en/Microservice-Architecture.md
  53. 11
      docs/en/Module-Development-Basics.md
  54. 3
      docs/en/Modules/Account.md
  55. 2
      docs/en/Modules/Blogging.md
  56. 160
      docs/en/Modules/Docs.md
  57. 14
      docs/en/Multi-Tenancy.md
  58. 101
      docs/en/Samples/Microservice-Demo.md
  59. 6
      docs/en/Startup-Templates/Application.md
  60. 2
      docs/en/Startup-Templates/Module.md
  61. 3
      docs/en/Themes/Basic.md
  62. 8
      docs/en/Tutorials/Part-1.md
  63. 1
      docs/en/Tutorials/Part-2.md
  64. 0
      docs/en/Tutorials/Part-3.md
  65. 52
      docs/en/UI/Angular/Component-Replacement.md
  66. 290
      docs/en/UI/Angular/Config-State.md
  67. 42
      docs/en/UI/Angular/Custom-Setting-Page.md
  68. 3
      docs/en/UI/Angular/Customization-User-Interface.md
  69. 136
      docs/en/UI/Angular/Localization.md
  70. 79
      docs/en/UI/Angular/Permission-Management.md
  71. BIN
      docs/en/UI/Angular/images/component-replacement.gif
  72. BIN
      docs/en/UI/Angular/images/custom-settings.png
  73. 352
      docs/en/UI/AspNetCore/Bundling-Minification.md
  74. 116
      docs/en/UI/AspNetCore/Client-Side-Package-Management.md
  75. 470
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  76. 3
      docs/en/UI/AspNetCore/Layout-Hooks.md
  77. 3
      docs/en/UI/AspNetCore/Navigation-Menu.md
  78. 86
      docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md
  79. 3
      docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md
  80. 26
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  81. 3
      docs/en/UI/AspNetCore/Theming.md
  82. 3
      docs/en/UI/AspNetCore/Toolbars.md
  83. 505
      docs/en/UI/AspNetCore/Widgets.md
  84. 103
      docs/en/docs-nav.json
  85. BIN
      docs/en/images/bookstore-added-brand-files.png
  86. BIN
      docs/en/images/bookstore-added-logo.png
  87. BIN
      docs/en/images/bookstore-added-role-js-file.png
  88. BIN
      docs/en/images/bookstore-brand-area-highlighted.png
  89. BIN
      docs/en/images/bookstore-global-css-file.png
  90. BIN
      docs/en/images/bookstore-google-analytics-view-component.png
  91. BIN
      docs/en/images/bookstore-menus-highlighted.png
  92. BIN
      docs/en/images/bookstore-notification-icon-on-toolbar.png
  93. BIN
      docs/en/images/bookstore-notification-view-component.png
  94. BIN
      docs/en/images/bookstore-toolbar-highlighted.png
  95. BIN
      docs/en/images/docs-module_download-new-abp-project.png
  96. BIN
      docs/en/images/docs-module_solution-explorer.png
  97. BIN
      docs/en/images/microservice-sample-diagram-3.png
  98. BIN
      docs/en/images/microservice-sample-solution-2.png
  99. BIN
      docs/en/images/overriding-login-cshtml.png
  100. 6
      docs/pt-BR/Getting-Started-Angular-Template.md

1
.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

2
README.md

@ -17,7 +17,7 @@ See the <a href="https://docs.abp.io/" target="_blank">documentation</a>.
#### Pre Requirements
- Visual Studio 2019 16.3.0+
- Visual Studio 2019 16.4.0+
#### Framework

2
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",

13
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"
}
}

90
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"
}
}

31
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"
}
}

5
abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/sl.json

@ -0,0 +1,5 @@
{
"culture": "sl",
"texts": {
}
}

35
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."
}
}

5
abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/sl.json

@ -0,0 +1,5 @@
{
"culture": "sl",
"texts": {
}
}

5
abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/sl.json

@ -0,0 +1,5 @@
{
"culture": "sl",
"texts": {
}
}

4
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",

6
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>2.2.0</Version>
<LangVersion>latest</LangVersion>
<Version>2.3.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl>
@ -9,9 +9,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/abpframework/abp/</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" />
</ItemGroup>
</Project>

10
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<WidgetOptions>(options =>
Configure<AbpWidgetOptions>(options =>
{
options.Widgets.Add<MySimpleWidgetViewComponent>();
});
```
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<WidgetOptions>(options =>
Configure<AbpWidgetOptions>(options =>
{
options.Widgets
.Add<MySimpleWidgetViewComponent>()
@ -498,7 +498,7 @@ Configure<WidgetOptions>(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

3
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

4
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í

2
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+):
![](images/create-new-aspnet-core-application-v2.png)

4
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í

140
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<AbpAspNetCoreMvcOptions>(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/).
![bookstore-apis](../images/bookstore-apis.png)
### 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<AbpAspNetCoreMvcOptions>(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<AbpAspNetCoreMvcOptions>(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.

165
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<List<BookDto>> 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<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` 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<AbpRemoteServiceOptions>(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<T>` interface to use the client proxies (see the related section above).

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

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

24
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

52
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<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> 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<District> 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.

141
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<AbpAspNetCoreMvcOptions>(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/).
![bookstore-apis](../images/bookstore-apis.png)
### 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<AbpAspNetCoreMvcOptions>(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<AbpAspNetCoreMvcOptions>(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)

352
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
<abp-style-bundle name="MyGlobalBundle">
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/styles/my-global-style.css" />
</abp-style-bundle>
````
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
<abp-style-bundle>
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
@if (ViewBag.IncludeCustomStyles != false)
{
<abp-style src="/styles/my-global-style.css" />
}
</abp-style-bundle>
````
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
<abp-script src="/scripts/my-script.js" />
````
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<AbpBundlingOptions>(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
<abp-script-bundle name="MyGlobalBundle" />
````
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<AbpBundlingOptions>(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<AbpBundlingOptions>(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-bundle>
<abp-style type="@typeof(BootstrapStyleContributor)" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
</abp-style-bundle>
````
`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<BundleContributorOptions>(options =>
{
options
.Extensions<PrismjsStyleBundleContributor>()
.Add<MyPrismjsStyleExtension>();
});
````
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<AbpBundlingOptions>(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)

116
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)

166
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<List<BookDto>> 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<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` 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<RemoteServiceOptions>(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<T>` interface to use the client proxies (see the related section above).
[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md)

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

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

25
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)

4
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.
[Click to navigate to Dynamic Forms document](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md)

4
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.
[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md)

5
docs/en/AspNetCore/Theming.md

@ -1,3 +1,4 @@
# Theming
TODO
This document has moved.
[Click to navigate to Theming document](../UI/AspNetCore/Theming.md)

505
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:
![widget-basic-files](../images/widget-basic-files.png)
**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<IViewComponentResult> 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
<div class="my-simple-widget">
<h2>My Simple Widget</h2>
<p>This is a simple widget!</p>
</div>
```
### 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<IViewComponentResult> 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
<form method="get" id="MyDashboardFilterForm">
...form elements
</form>
<div id="MyDashboardWidgetsArea" data-widget-filter="#MyDashboardFilterForm">
...widgets
</div>
````
`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<string>`): 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<WidgetOptions>(options =>
{
options.Widgets.Add<MySimpleWidgetViewComponent>();
});
```
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<WidgetOptions>(options =>
{
options.Widgets
.Add<MySimpleWidgetViewComponent>()
.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).
[Click to navigate to Widgets document](../UI/AspNetCore/Widgets.md)

101
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
![authorization-new-permission-ui](images/authorization-new-permission-ui.png)
* 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<BookStoreResource>("BookStore")
@ -107,14 +107,14 @@ myGroup.AddPermission(
"BookStore_Author_Create",
LocalizableString.Create<BookStoreResource>("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<BookStoreResource>("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<PermissionGrantResult>
public override async Task<PermissionGrantResult>
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<PermissionOptions>(options =>
{
options.ValueProviders.Add<SystemAdminPermissionValueProvider>();
});
````
```
### Permission Store
@ -354,16 +358,17 @@ Configure<PermissionOptions>(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)

73
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<AbpQuartzPreOptions>(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).

7
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)
* [RabbitMQ Background Job Manager](Background-Jobs-RabbitMq.md)
## See Also
* [Background Workers](Background-Workers.md)

68
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<MyLogWorker>().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.

140
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<IUserRepository>();
//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<PassiveUserCheckerWorker>();
}
}
````
`context.AddBackgroundWorker(...)` is a shortcut extension method for the expression below:
````
context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.Add(
context
.ServiceProvider
.GetRequiredService<PassiveUserCheckerWorker>()
);
````
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)

2
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.

2
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

10
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

149
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<string>("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<EntityChangedEventData<IdentityUser>>` interface.
````csharp
public class MyLocalIdentityUserChangeEventHandler :
ILocalEventHandler<EntityChangedEventData<IdentityUser>>,
ITransientDependency
{
public async Task HandleEventAsync(EntityChangedEventData<IdentityUser> eventData)
{
var userId = eventData.Entity.Id;
var userName = eventData.Entity.UserName;
//...
}
}
````
* `EntityChangedEventData<T>` 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<T>` 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<EntityCreatedEto<EntityEto>>,
IDistributedEventHandler<EntityUpdatedEto<EntityEto>>,
IDistributedEventHandler<EntityDeletedEto<EntityEto>>,
ITransientDependency
{
public async Task HandleEventAsync(EntityCreatedEto<EntityEto> 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<EntityEto> 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<EntityEto> 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<EntityCreatedEto<IdentityUserEto>>,
ITransientDependency
{
public async Task HandleEventAsync(EntityCreatedEto<IdentityUserEto> 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<UserEto>` 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)

62
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.

166
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<IIdentityUserAppService, MyIdentityUserAppService>()
);
````
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<IdentityUserDto> CreateAsync(IdentityUserCreateDto input)
{
if (input.PhoneNumber.IsNullOrWhiteSpace())
{
throw new AbpValidationException(
"Phone number is required for new users!",
new List<ValidationResult>
{
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<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher,
IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider
) : base(
store,
optionsAccessor,
passwordHasher,
userValidators,
passwordValidators,
keyNormalizer,
errors,
services,
logger,
cancellationTokenProvider)
{
}
public override async Task<IdentityResult> CreateAsync(IdentityUser user)
{
if (user.PhoneNumber.IsNullOrWhiteSpace())
{
throw new AbpValidationException(
"Phone number is required for new users!",
new List<ValidationResult>
{
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.

9
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)

95
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<TUser>` and`FullAuditedAggregateRootWithUser<TKey, TUser>`. 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<string> GetTitle(Guid userId)
{
var user = await _identityUserRepository.GetAsync(userId);
//GET A PROPERTY
return user.GetProperty<string>("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<string>(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<string, object>` 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)
* [Best practice guide to design the entities](Best-Practices/Entities.md)

21
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<IBookStoreDbSchemaMigrator>` 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.

6
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)

2
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+):
![](images/create-new-aspnet-core-application-v2.png)

6
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)

3
docs/en/IdentityServer-Integration.md

@ -0,0 +1,3 @@
# IdentityServer Integration
TODO

10
docs/en/Localization.md

@ -46,7 +46,7 @@ public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<VirtualFileSystemOptions>(options =>
Configure<AbpVirtualFileSystemOptions>(options =>
{
// "YourRootNameSpace" is the root namespace of your project. It can be empty if your root namespace is empty.
options.FileSets.AddEmbedded<MyModule>("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');
````
````
## See Also
* [Localization in Angular UI](UI/Angular/Localization.md)

4
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.

11
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<DbConnectionOptions>(options =>
Configure<AbpDbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = "......";
});

3
docs/en/Modules/Account.md

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

2
docs/en/Modules/Blogging.md

@ -1,3 +1,3 @@
# IdentityServer Module
# Blogging Module
TODO

160
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.
![Create new project](../images/docs-module_download-new-abp-project.png)
### 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**.
![Create a new project](../images/docs-module_solution-explorer.png)
@ -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
<PackageReference Include="Volo.Docs.Domain" Version="0.9.0" />
```
* [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
<PackageReference Include="Volo.Docs.EntityFrameworkCore" Version="0.9.0" />
```
* [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
<PackageReference Include="Volo.Docs.Application" Version="0.9.0" />
```
* [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
<PackageReference Include="Volo.Docs.Web" Version="0.9.0" />
```
`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<MyProjectDbContext>
{
public MyProjectDbContext(DbContextOptions<MyProjectDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
modelBuilder.ConfigureDocs();
}
}
public class MyProjectMigrationsDbContext : AbpDbContext<MyProjectMigrationsDbContext>
{
public MyProjectMigrationsDbContext(DbContextOptions<MyProjectMigrationsDbContext> 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<IdentityUser>(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`.
![Navigation menu](../images/docs-module_download-sample-navigation-menu.png)
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<DocsElasticSearchOptions>(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).

14
docs/en/Multi-Tenancy.md

@ -168,7 +168,7 @@ namespace MyCompany.MyProject
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<ConfigurationTenantStoreOptions>(options =>
Configure<AbpDefaultTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
@ -214,7 +214,7 @@ namespace MyCompany.MyProject
{
var configuration = BuildConfiguration();
Configure<ConfigurationTenantStoreOptions>(configuration);
Configure<AbpDefaultTenantStoreOptions>(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<AbpTenantResolveOptions>(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");

101
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:
![microservice-sample-diagram-2](../images/microservice-sample-diagram-2.png)
![microservice-sample-diagram-2](../images/microservice-sample-diagram-3.png)
### 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.
![microservice-sample-update-database-authserver](../images/microservice-sample-update-database-authserver.png)
##### 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.
![microservice-sample-update-database-products](../images/microservice-sample-update-database-products.png)
#### 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:
![microservice-sample-solution](../images/microservice-sample-solution.png)
![microservice-sample-solution](../images/microservice-sample-solution-2.png)
### 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.

6
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.

2
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

3
docs/en/Themes/Basic.md

@ -0,0 +1,3 @@
## Basic Theme
TODO

8
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-card>
````
* `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:

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

@ -641,7 +641,6 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
</div>
<div class="card-body">
<abp-table
*ngIf="[150, 0] as columnWidths"
[value]="books$ | async"
[abpLoading]="loading"
[headerTemplate]="tableHeader"

0
docs/en/Tutorials/part-3.md → docs/en/Tutorials/Part-3.md

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

@ -0,0 +1,52 @@
# Component Replacement
You can replace some ABP components with your custom components.
The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_.
## How to Replace a Component
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core';
export class AppComponent {
constructor(..., private store: Store) {}
ngOnInit() {
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: 'Identity.RolesComponent',
}),
);
//...
}
}
```
![Example Usage](./images/component-replacement.gif)
## Available Replaceable Components
| Component key | Description |
| -------------------------------------------------- | --------------------------------------------- |
| Account.LoginComponent | Login page |
| Account.RegisterComponent | Register page |
| Account.ManageProfileComponent | Manage Profile page |
| Account.AuthWrapperComponent | This component wraps register and login pages |
| Account.ChangePasswordComponent | Change password form |
| Account.PersonalSettingsComponent | Personal settings form |
| Account.TenantBoxComponentInputs | Tenant changing box |
| FeatureManagement.FeatureManagementComponent | Features modal |
| Identity.UsersComponent | Users page |
| Identity.RolesComponent | Roles page |
| PermissionManagement.PermissionManagementComponent | Permissions modal |
| SettingManagement.SettingManagementComponent | Setting Management page |
| TenantManagement.TenantsComponent | Tenants page |
## What's Next?
- [Custom Setting Page](./Custom-Setting-Page.md)

290
docs/en/UI/Angular/Config-State.md

@ -0,0 +1,290 @@
# Config State
`ConfigStateService` is a singleton service, i.e. provided in root level of your application, and is actually a façade for interacting with application configuration state in the `Store`.
## Before Use
In order to use the `ConfigStateService` you must inject it in your class as a dependency.
```js
import { ConfigStateService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private config: ConfigStateService) {}
}
```
You do not have to provide the `ConfigStateService` at module or component/directive level, because it is already **provided in root**.
## Selector Methods
`ConfigStateService` has numerous selector methods which allow you to get a specific configuration or all configurations from the `Store`.
### How to Get All Configurations From the Store
You can use the `getAll` method of `ConfigStateService` to get all of the configuration object from the store. It is used as follows:
```js
// this.config is instance of ConfigStateService
const config = this.config.getAll();
```
### How to Get a Specific Configuration From the Store
You can use the `getOne` method of `ConfigStateService` to get a specific configuration property from the store. For that, the property name should be passed to the method as parameter.
```js
// this.config is instance of ConfigStateService
const currentUser = this.config.getOne("currentUser");
```
On occasion, you will probably want to be more specific than getting just the current user. For example, here is how you can get the `tenantId`:
```js
const tenantId = this.config.getDeep("currentUser.tenantId");
```
or by giving an array of keys as parameter:
```js
const tenantId = this.config.getDeep(["currentUser", "tenantId"]);
```
FYI, `getDeep` is able to do everything `getOne` does. Just keep in mind that `getOne` is slightly faster.
#### Config State Properties
Please refer to `Config.State` type for all the properties you can get with `getOne` and `getDeep`. 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#L7).
### How to Get the Application Information From the Store
The `getApplicationInfo` method is used to get the application information from the environment variables stored as the config state. This is how you can use it:
```js
// this.config is instance of ConfigStateService
const appInfo = this.config.getApplicationInfo();
```
This method never returns `undefined` or `null` and returns an empty object literal (`{}`) instead. In other words, you will never get an error when referring to the properties of `appInfo` above.
#### Application Information Properties
Please refer to `Config.Application` type for all the properties you can get with `getApplicationInfo`. 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#L21).
### How to Get API URL From the Store
The `getApplicationInfo` method is used to get a specific API URL from the environment variables stored as the config state. This is how you can use it:
```js
// this.config is instance of ConfigStateService
const apiUrl = this.config.getApiUrl();
// environment.apis.default.url
const searchUrl = this.config.getApiUrl("search");
// environment.apis.search.url
```
This method returns the `url` of a specific API based on the key given as its only parameter. If there is no key, `'default'` is used.
### How to Get All Settings From the Store
You can use the `getSettings` method of `ConfigStateService` to get all of the settings object from the configuration state. Here is how you get all settings:
```js
// this.config is instance of ConfigStateService
const settings = this.config.getSettings();
```
In addition, the method lets you search settings by **passing a keyword** to it.
```js
const localizationSettings = this.config.getSettings("Localization");
/*
{
'Abp.Localization.DefaultLanguage': 'en'
}
*/
```
Beware though, **settings search is case sensitive**.
### How to Get a Specific Setting From the Store
You can use the `getSetting` method of `ConfigStateService` to get a specific setting from the configuration state. Here is an example:
```js
// this.config is instance of ConfigStateService
const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage");
// 'en'
```
### How to Get a Specific Permission From the Store
You can use the `getGrantedPolicy` method of `ConfigStateService` to get a specific permission from the configuration state. For that, you should pass a policy key as parameter to the method.
```js
// this.config is instance of ConfigStateService
const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity");
// true
```
You may also **combine policy keys** to fine tune your selection:
```js
// this.config is instance of ConfigStateService
const hasIdentityAndAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity && Abp.Account"
);
// false
const hasIdentityOrAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity || Abp.Account"
);
// true
```
Please consider the following **rules** when creating your permission selectors:
- Maximum 2 keys can be combined.
- `&&` operator looks for both keys.
- `||` operator looks for either key.
- Empty string `''` as key will return `true`
- Using an operator without a second key will return `false`
### How to Get Translations From the Store
The `getLocalization` method of `ConfigStateService` is used for translations. Here are some examples:
```js
// this.config is instance of ConfigStateService
const identity = this.config.getLocalization("AbpIdentity::Identity");
// 'identity'
const notFound = this.config.getLocalization("AbpIdentity::IDENTITY");
// 'AbpIdentity::IDENTITY'
const defaultValue = this.config.getLocalization({
key: "AbpIdentity::IDENTITY",
defaultValue: "IDENTITY"
});
// 'IDENTITY'
```
Please check out the [localization documentation](./Localization.md) for details.
## Dispatch Methods
`ConfigStateService` has several dispatch methods which allow you to conveniently dispatch predefined actions to the `Store`.
### How to Get Application Configuration From Server
The `dispatchGetAppConfiguration` triggers a request to an endpoint that responds with the application state and then places this response to the `Store` as configuration state.
```js
// this.config is instance of ConfigStateService
this.config.dispatchGetAppConfiguration();
// 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 application configuration is already being received from the server at start.
### How to Patch Route Configuration
The `dispatchPatchRouteByName` finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter.
```js
// this.config is instance of ConfigStateService
const newRouteConfig: Partial<ABP.Route> = {
name: "Home",
path: "home",
children: [
{
name: "Dashboard",
path: "dashboard"
}
]
};
this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
### 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).

42
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:
![Custom Settings Tab](./images/custom-settings.png)

3
docs/en/UI/Angular/Customization-User-Interface.md

@ -0,0 +1,3 @@
# Angular User Interface Customization Guide
* [Replacing a component](Component-Replacement.md)

136
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
<h1>{%{{{ '::Key' | abpLocalization }}}%}</h1>
<h1>{%{{{ 'MyProjectName::Key' | abpLocalization }}}%}</h1>
```
## Using the Localization Pipe
You can use the `abpLocalization` pipe to get localized text as in this example:
```html
<h1>{%{{{ 'Resource::Key' | abpLocalization }}}%}</h1>
```
The pipe will replace the key with the localized text.
You can also specify a default value as shown below:
```html
<h1>{%{{{ { key: 'Resource::Key', defaultValue: 'Default Value' } | abpLocalization }}}%}</h1>
```
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
<h1>{%{{{ 'AbpAccount::PagerInfo' | abpLocalization:'20':'30':'50' }}}%}</h1>
<!-- Output: Showing 20 to 30 of 50 entries -->
```
### 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)

79
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
<div *abpPermission="AbpIdentity.Roles">
This content is only visible if the user has 'AbpIdentity.Roles' permission.
</div>
```
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)

BIN
docs/en/UI/Angular/images/component-replacement.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
docs/en/UI/Angular/images/custom-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

352
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
<abp-style-bundle name="MyGlobalBundle">
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/styles/my-global-style.css" />
</abp-style-bundle>
````
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
<abp-style-bundle>
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
@if (ViewBag.IncludeCustomStyles != false)
{
<abp-style src="/styles/my-global-style.css" />
}
</abp-style-bundle>
````
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
<abp-script src="/scripts/my-script.js" />
````
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<AbpBundlingOptions>(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
<abp-script-bundle name="MyGlobalBundle" />
````
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<AbpBundlingOptions>(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<AbpBundlingOptions>(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-bundle>
<abp-style type="@typeof(BootstrapStyleContributor)" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
</abp-style-bundle>
````
`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<BundleContributorOptions>(options =>
{
options
.Extensions<PrismjsStyleBundleContributor>()
.Add<MyPrismjsStyleExtension>();
});
````
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<AbpBundlingOptions>(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)

116
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)

470
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<IActionResult> 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:
![overriding-login-cshtml](../../images/overriding-login-cshtml.png)
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<AbpAccountOptions> accountOptions
) : base(
schemeProvider,
accountOptions)
{
}
public override Task<IActionResult> 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.
![bookstore-brand-area-highlighted](../../images/bookstore-brand-area-highlighted.png)
[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:
![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png)
Then change the `Default.cshtml` as you like. Example content can be like that:
````xml
<a href="/">
<img src="~/logos/bookstore-logo.png" width="250" height="60"/>
</a>
````
Now, you can run the application to see the result:
![bookstore-added-logo](../../images/bookstore-added-logo.png)
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`:
![bookstore-global-css-file](../../images/bookstore-global-css-file.png)
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<AbpBundlingOptions>(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<AbpBundlingOptions>(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:
![bookstore-added-role-js-file](../../images/bookstore-added-role-js-file.png)
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<AbpBundlingOptions>(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:
![bookstore-menus-highlighted](../../images/bookstore-menus-highlighted.png)
* `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:![bookstore-toolbar-highlighted](../../images/bookstore-toolbar-highlighted.png)
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:
![bookstore-notification-view-component](../../images/bookstore-notification-view-component.png)
**NotificationViewComponent.cs**
````csharp
public class NotificationViewComponent : AbpViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
return View("/Pages/Shared/Components/Notification/Default.cshtml");
}
}
````
**Default.cshtml**
````xml
<div id="MainNotificationIcon" style="color: white; margin: 8px;">
<i class="far fa-bell"></i>
</div>
````
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<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new MyToolbarContributor());
});
````
That's all, you will see the notification icon on the toolbar when you run the application:
![bookstore-notification-icon-on-toolbar](../../images/bookstore-notification-icon-on-toolbar.png)
`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:
![bookstore-google-analytics-view-component](../../images/bookstore-google-analytics-view-component.png)
**NotificationViewComponent.cs**
````csharp
public class GoogleAnalyticsViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml");
}
}
````
**Default.cshtml**
````html
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-xxxxxx-1', 'auto');
ga('send', 'pageview');
</script>
````
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<AbpLayoutHookOptions>(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<AbpLayoutHookOptions>(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.

3
docs/en/UI/AspNetCore/Layout-Hooks.md

@ -0,0 +1,3 @@
# Layout Hooks
TODO

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

@ -0,0 +1,3 @@
# Navigation Menu
TODO

86
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
<abp-button button-type="Primary">Click Me</abp-button>
````
## 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
<abp-button button-type="Primary" text="Click Me" />
````
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
<abp-button icon="address-card" text="Address" />
````
##### 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.

3
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.

26
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).

3
docs/en/UI/AspNetCore/Theming.md

@ -0,0 +1,3 @@
# ASP.NET Core MVC / Razor Pages Theming
TODO

3
docs/en/UI/AspNetCore/Toolbars.md

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

505
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:
![widget-basic-files](../../images/widget-basic-files.png)
**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<IViewComponentResult> 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
<div class="my-simple-widget">
<h2>My Simple Widget</h2>
<p>This is a simple widget!</p>
</div>
```
### 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<IViewComponentResult> 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
<form method="get" id="MyDashboardFilterForm">
...form elements
</form>
<div id="MyDashboardWidgetsArea" data-widget-filter="#MyDashboardFilterForm">
...widgets
</div>
````
`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<string>`): 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<AbpWidgetOptions>(options =>
{
options.Widgets.Add<MySimpleWidgetViewComponent>();
});
```
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<AbpWidgetOptions>(options =>
{
options.Widgets
.Add<MySimpleWidgetViewComponent>()
.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).

103
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"
}
]
}

BIN
docs/en/images/bookstore-added-brand-files.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/images/bookstore-added-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
docs/en/images/bookstore-added-role-js-file.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/images/bookstore-brand-area-highlighted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/en/images/bookstore-global-css-file.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/en/images/bookstore-google-analytics-view-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/en/images/bookstore-menus-highlighted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/images/bookstore-notification-icon-on-toolbar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/en/images/bookstore-notification-view-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/en/images/bookstore-toolbar-highlighted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/images/docs-module_download-new-abp-project.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
docs/en/images/docs-module_solution-explorer.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/en/images/microservice-sample-diagram-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

BIN
docs/en/images/microservice-sample-solution-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/images/overriding-login-cshtml.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

6
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

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

Loading…
Cancel
Save