Browse Source

Merge branch 'dev' into #1666-organization-units

pull/2563/head
Halil İbrahim Kalkan 7 years ago
committed by GitHub
parent
commit
37feb46d0f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .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. 10
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  6. 90
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json
  7. 31
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sl.json
  8. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/sl.json
  9. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  10. 35
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json
  11. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/sl.json
  12. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/sl.json
  13. 8
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  14. 3
      build/common.ps1
  15. 1
      common.DotSettings
  16. 4
      common.props
  17. 10
      docs/cs/AspNetCore/Widgets.md
  18. 3
      docs/cs/CLI.md
  19. 4
      docs/cs/Getting-Started-Angular-Template.md
  20. 4
      docs/cs/Getting-Started-AspNetCore-Application.md
  21. 4
      docs/cs/Getting-Started-AspNetCore-MVC-Template.md
  22. 2
      docs/cs/Getting-Started-Console-Application.md
  23. 140
      docs/en/API/Auto-API-Controllers.md
  24. 165
      docs/en/API/Dynamic-CSharp-API-Clients.md
  25. 3
      docs/en/API/JavaScript-API/Auth.md
  26. 24
      docs/en/API/JavaScript-API/Index.md
  27. 103
      docs/en/Application-Services.md
  28. 141
      docs/en/AspNetCore/Auto-API-Controllers.md
  29. 352
      docs/en/AspNetCore/Bundling-Minification.md
  30. 116
      docs/en/AspNetCore/Client-Side-Package-Management.md
  31. 166
      docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md
  32. 4
      docs/en/AspNetCore/JavaScript-API/Auth.md
  33. 25
      docs/en/AspNetCore/JavaScript-API/Index.md
  34. 4
      docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md
  35. 4
      docs/en/AspNetCore/Tag-Helpers/Index.md
  36. 5
      docs/en/AspNetCore/Theming.md
  37. 505
      docs/en/AspNetCore/Widgets.md
  38. 129
      docs/en/Authorization.md
  39. 43
      docs/en/Background-Jobs-Hangfire.md
  40. 73
      docs/en/Background-Jobs-Quartz.md
  41. 8
      docs/en/Background-Jobs.md
  42. 68
      docs/en/Background-Workers-Quartz.md
  43. 140
      docs/en/Background-Workers.md
  44. 9
      docs/en/Best-Practices/Entity-Framework-Core-Integration.md
  45. 4
      docs/en/Best-Practices/MongoDB-Integration.md
  46. 2
      docs/en/Blog-Posts/2018-09-24-Announcement/Post.md
  47. 6
      docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md
  48. 142
      docs/en/Blog-Posts/2020-03-19 v2_3_Release/Post.md
  49. BIN
      docs/en/Blog-Posts/2020-03-19 v2_3_Release/abp-io-release-2-3-0-blog.png
  50. BIN
      docs/en/Blog-Posts/2020-03-19 v2_3_Release/react-native-ui.png
  51. 69
      docs/en/CLI.md
  52. 179
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  53. 62
      docs/en/Customizing-Application-Modules-Guide.md
  54. 166
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  55. 6
      docs/en/Customizing-Application-Modules-Overriding-User-Interface.md
  56. 98
      docs/en/Entities.md
  57. 885
      docs/en/Entity-Framework-Core-Migrations.md
  58. 30
      docs/en/Entity-Framework-Core-Other-DBMS.md
  59. 121
      docs/en/Entity-Framework-Core.md
  60. 52
      docs/en/Exception-Handling.md
  61. 10
      docs/en/Getting-Started-Angular-Template.md
  62. 4
      docs/en/Getting-Started-AspNetCore-Application.md
  63. 6
      docs/en/Getting-Started-AspNetCore-MVC-Template.md
  64. 2
      docs/en/Getting-Started-Console-Application.md
  65. 6
      docs/en/Getting-Started-With-Startup-Templates.md
  66. 201
      docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md
  67. 113
      docs/en/How-To/Customize-Login-Page-MVC.md
  68. 101
      docs/en/How-To/Customize-SignIn-Manager.md
  69. 9
      docs/en/How-To/Index.md
  70. 3
      docs/en/IdentityServer-Integration.md
  71. 10
      docs/en/Localization.md
  72. 4
      docs/en/Microservice-Architecture.md
  73. 11
      docs/en/Module-Development-Basics.md
  74. 3
      docs/en/Modules/Account.md
  75. 2
      docs/en/Modules/Blogging.md
  76. 162
      docs/en/Modules/Docs.md
  77. 4
      docs/en/Modules/Identity.md
  78. 3
      docs/en/Modules/Tenant-Management.md
  79. 15
      docs/en/Multi-Tenancy.md
  80. 3
      docs/en/Object-Extensions.md
  81. 101
      docs/en/Samples/Microservice-Demo.md
  82. 196
      docs/en/Startup-Templates/Application.md
  83. 2
      docs/en/Startup-Templates/Module.md
  84. 3
      docs/en/Themes/Basic.md
  85. 661
      docs/en/Tutorials/Angular/Part-I.md
  86. 589
      docs/en/Tutorials/Angular/Part-II.md
  87. 180
      docs/en/Tutorials/Angular/Part-III.md
  88. BIN
      docs/en/Tutorials/Angular/images/bookstore-actions-buttons.png
  89. BIN
      docs/en/Tutorials/Angular/images/bookstore-angular-file-tree.png
  90. BIN
      docs/en/Tutorials/Angular/images/bookstore-backend-solution-v2.png
  91. BIN
      docs/en/Tutorials/Angular/images/bookstore-book-list.png
  92. BIN
      docs/en/Tutorials/Angular/images/bookstore-creating-book-list-terminal.png
  93. BIN
      docs/en/Tutorials/Angular/images/bookstore-creating-books-module-terminal.png
  94. BIN
      docs/en/Tutorials/Angular/images/bookstore-edit-modal.png
  95. BIN
      docs/en/Tutorials/Angular/images/bookstore-final-actions-dropdown.png
  96. BIN
      docs/en/Tutorials/Angular/images/bookstore-initial-book-list-page.png
  97. BIN
      docs/en/Tutorials/Angular/images/bookstore-initial-books-page-with-layout.png
  98. BIN
      docs/en/Tutorials/Angular/images/bookstore-initial-books-page.png
  99. BIN
      docs/en/Tutorials/Angular/images/bookstore-service-terminal-output.png
  100. BIN
      docs/en/Tutorials/Angular/images/bookstore-swagger-api.png

2
.gitignore

@ -291,3 +291,5 @@ samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/Logs/logs.txt
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"
}
}

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

@ -53,6 +53,7 @@
"CreateANugetPackage": "Create a Nuget package",
"AddNew": "Add new",
"PackageAlreadyExist{0}": "\"{0}\" package is already added.",
"ModuleAlreadyExist{0}": "\"{0}\" module is already added.",
"ClearCache": "Clear cache",
"SuccessfullyCleared": "Successfully cleared",
"Menu:NpmPackages": "NPM Packages",
@ -80,9 +81,16 @@
"LastSeenDate": "Last seen date",
"{0}Computer{1}WillBeRemovedFromRecords": "Computer of {0} ({1}) will be removed from records",
"OrganizationDeletionWarningMessage": "Organization will be deleted",
"DeletingLastOwnerWarningMessage": "An organization must have at least one owner! Therefore you cannot remove this owner",
"This{0}AlreadyExistInThisOrganization": "This {0} already exist in this organization",
"AreYouSureYouWantToDeleteAllComputers": "Are you sure you want to delete all computers?",
"DeleteAll": "Delete all",
"DoYouWantToCreateNewUser": "Do you want to create new user?"
"DoYouWantToCreateNewUser": "Do you want to create new user?",
"MasterModules": "Master Modules",
"OrganizationName": "Organization name",
"OrganizationNamePlaceholder": "Organization name...",
"UsernameOrEmail": "Username or email",
"UsernameOrEmailPlaceholder": "Username or email...",
"Member": "Member"
}
}

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": {
}
}

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

@ -5,8 +5,9 @@
"OrganizationList": "Organization list",
"Volo.AbpIo.Commercial:010003": "You are not owner of this organization!",
"OrganizationNotFoundMessage": "No organization found!",
"DeveloperCount": "Developer count",
"QuestionCount": "Question count",
"DeveloperCount": "Allocated / total developers",
"QuestionCount": "Remaining / total questions",
"Unlimited": "Unlimited",
"Owners": "Owners",
"AddMember": "Add member",
"AddOwner": "Add owner",
@ -28,7 +29,7 @@
"ApiKey": "API key",
"UserNameNotFound": "There is no user with username {0}",
"SuccessfullyAddedToNewsletter": "Thanks you for subscribing to our newsletter!",
"ManageProfile": "Manage your profile",
"MyProfile": "My profile",
"EmailNotValid": "Please enter a valid email address."
}
}
}

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": {
}
}

8
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",
@ -154,6 +154,8 @@
"SeeTheDocumentForMoreInformation": "See the <a href=\"{1}\">{0} document</a> for more information",
"IndexPageHeroSection": "<span class=\"first-line shine\"><strong>open source</strong></span><span class=\"second-line text-uppercase\">Web Application<br />Framework </span><span class=\"third-line shine2\"><strong>for asp.net core</strong></span>",
"UiFramework": "UI Framework",
"EmailAddress": "Email address"
"EmailAddress": "Email address",
"Mobile": "Mobile",
"ReactNative": "React Native"
}
}

3
build/common.ps1

@ -21,13 +21,12 @@ $solutionPaths = (
"../modules/client-simulation",
"../templates/module/aspnet-core",
"../templates/app/aspnet-core",
"../samples/BasicAspNetCoreApplication",
"../samples/BasicConsoleApplication",
"../samples/BookStore",
"../samples/BookStore-Angular-MongoDb/aspnet-core",
"../samples/BookStore-Modular/modules/book-management",
"../samples/BookStore-Modular/application",
"../samples/DashboardDemo",
"../samples/EfCoreMigrationDemo",
"../samples/MicroserviceDemo",
"../samples/RabbitMqEventBus",
"../abp_io/AbpIoLocalization"

1
common.DotSettings

@ -20,4 +20,5 @@
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Async/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Mutable/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Volo/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

4
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>2.1.0</Version>
<Version>2.5.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í

4
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)
@ -153,5 +153,5 @@ namespace BasicAspNetCoreApplication
## Zdrojový kód
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication).
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication).

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í

2
docs/cs/Getting-Started-Console-Application.md

@ -178,4 +178,4 @@ Stačí volat metodu `options.UseAutofac()` v možnostech `AbpApplicationFactory
## Zdrojový kód
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp/tree/master/samples/BasicConsoleApplication).
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication).

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 "AbpRemoteServiceOptions" 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
### AbpRemoteServiceOptions
`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

103
docs/en/Application-Services.md

@ -132,6 +132,8 @@ The `CreateAsync` method above manually creates a `Book` entity from given `Crea
However, in many cases, it's very practical to use **auto object mapping** to set properties of an object from a similar object. ABP provides an [object to object mapping](Object-To-Object-Mapping.md) infrastructure to make this even easier.
Object to object mapping provides abstractions and it is implemented by the [AutoMapper](https://automapper.org/) library by default.
Let's create another method to get a book. First, define the method in the `IBookAppService` interface:
````csharp
@ -146,7 +148,6 @@ public interface IBookAppService : IApplicationService
`BookDto` is a simple [DTO](Data-Transfer-Objects.md) class defined as below:
````csharp
[AbpAutoMapFrom(typeof(Book))] //Defines the mapping
public class BookDto
{
public Guid Id { get; set; }
@ -159,21 +160,38 @@ public class BookDto
}
````
* `BookDto` defines `[AbpAutoMapFrom(typeof(Book))]` attribute to create the object mapping from `Book` to `BookDto`.
AutoMapper requires to create a mapping [profile class](https://docs.automapper.org/en/stable/Configuration.html#profile-instances). Example:
Then you can implement the `GetAsync` method as shown below:
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
}
}
````
You should then register profiles using the `AbpAutoMapperOptions`:
````csharp
public async Task<BookDto> GetAsync(Guid id)
[DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{
var book = await _bookRepository.GetAsync(id);
return book.MapTo<BookDto>();
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
//Add all mappings defined in the assembly of the MyModule class
options.AddMaps<MyModule>();
});
}
}
````
`MapTo` extension method converts `Book` object to `BookDto` object by copying all properties with the same naming.
`AddMaps` registers all profile classes defined in the assembly of the given class, typically your module class. It also registers for the [attribute mapping](https://docs.automapper.org/en/stable/Attribute-mapping.html).
An alternative to the `MapTo` is using the `IObjectMapper` service:
Then you can implement the `GetAsync` method as shown below:
````csharp
public async Task<BookDto> GetAsync(Guid id)
@ -183,8 +201,6 @@ public async Task<BookDto> GetAsync(Guid id)
}
````
While the second syntax is a bit harder to write, it better works if you write unit tests.
See the [object to object mapping document](Object-To-Object-Mapping.md) for more.
## Validation
@ -201,7 +217,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 +235,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:
@ -248,7 +266,6 @@ public interface ICrudAppService<
DTO classes used in this example are `BookDto` and `CreateUpdateBookDto`:
````csharp
[AbpAutoMapFrom(typeof(Book))]
public class BookDto : AuditedEntityDto<Guid>
{
public string Name { get; set; }
@ -258,7 +275,6 @@ public class BookDto : AuditedEntityDto<Guid>
public float Price { get; set; }
}
[AbpAutoMapTo(typeof(Book))]
public class CreateUpdateBookDto
{
[Required]
@ -273,6 +289,19 @@ public class CreateUpdateBookDto
}
````
[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances) class of DTO class.
```csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
CreateMap<CreateUpdateBookDto, Book>();
}
}
```
* `CreateUpdateBookDto` is shared by create and update operations, but you could use separated DTO classes as well.
And finally, the `BookAppService` implementation is very simple:
@ -292,6 +321,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)

129
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,34 +126,48 @@ 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!
);
```
#### Enable/Disable Permissions
A permission is enabled by default. It is possible to disable a permission. A disabled permission will be prohibited for everyone. You can still check for the permission, but it will always return prohibited.
Example definition:
````csharp
myGroup.AddPermission("Author_Management", isEnabled: false);
````
You normally don't need to define a disabled permission (unless you temporary want disable a feature of your application). However, you may want to disable a permission defined in a depended module. In this way you can disable the related application functionality. See the "*Changing Permission Definitions of a Depended Module*" section below for an example usage.
> Note: Checking an undefined permission will throw an exception while a disabled permission check simply returns prohibited (false).
#### Child Permissions
A permission may have child permissions. It is especially useful when you want to create a hierarchical permission tree where a permission may have additional sub permissions which are available only if the parent permission has been granted.
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 +175,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 +207,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 +218,29 @@ 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.
Example:
````csharp
context
.GetPermissionOrNull(IdentityPermissions.Roles.Delete)
.IsEnabled = false;
````
When you write this code inside your permission definition provider, it finds the "role deletion" permission of the [Identity Module](Modules/Identity.md) and disabled the permission, so no one can delete a role on the application.
> Tip: It is better to check the value returned by the `GetPermissionOrNull` method since it may return null if the given permission was not defined.
## 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 +253,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 +261,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 +280,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 +294,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 +314,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 +326,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 +344,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 +355,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:
Once a provider is defined, it should be added to the `AbpPermissionOptions` as shown below:
````csharp
Configure<PermissionOptions>(options =>
```csharp
Configure<AbpPermissionOptions>(options =>
{
options.ValueProviders.Add<SystemAdminPermissionValueProvider>();
});
````
```
### Permission Store
@ -354,16 +384,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)

43
docs/en/Background-Jobs-Hangfire.md

@ -40,4 +40,45 @@ public class YourModule : AbpModule
## Configuration
TODO...
You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package).
After you have installed these NuGet packages, you need to configure your project to use Hangfire.
1.First, we change the `Module` class (example: `<YourProjectName>HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
//... other configarations.
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
}
````
2. We need to add `UseHangfireServer` call in the `OnApplicationInitialization` method in `Module` class
If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard`
````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
// ... others
app.UseHangfireServer();
app.UseHangfireDashboard();
}
````

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

8
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,8 @@ 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)
* [Quartz Background Job Manager](Background-Jobs-Quartz.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:
````csharp
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)

9
docs/en/Best-Practices/Entity-Framework-Core-Integration.md

@ -89,13 +89,15 @@ public static class IdentityDbContextModelBuilderExtensions
builder.Entity<IdentityUser>(b =>
{
b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity
});
builder.Entity<IdentityUserClaim>(b =>
{
b.ToTable(options.TablePrefix + "UserClaims", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity
});
@ -104,10 +106,11 @@ public static class IdentityDbContextModelBuilderExtensions
}
````
* **Do** create a **configuration options** class by inheriting from the `ModelBuilderConfigurationOptions`. Example:
* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above).
* **Do** create a **configuration options** class by inheriting from the `AbpModelBuilderConfigurationOptions`. Example:
````C#
public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions
public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{
public IdentityModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema)

4
docs/en/Best-Practices/MongoDB-Integration.md

@ -90,11 +90,11 @@ public static class AbpIdentityMongoDbContextExtensions
}
```
- **Do** create a **configuration options** class by inheriting from the `MongoModelBuilderConfigurationOptions`. Example:
- **Do** create a **configuration options** class by inheriting from the `AbpMongoModelBuilderConfigurationOptions`. Example:
```c#
public class IdentityMongoModelBuilderConfigurationOptions
: MongoModelBuilderConfigurationOptions
: AbpMongoModelBuilderConfigurationOptions
{
public IdentityMongoModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix)

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.

6
docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md

@ -12,11 +12,11 @@ Finally, ABP has a **SPA UI** option with the latest [Angular](https://angular.i
* Created Angular UI packages for the modules like account, identity and tenant-management.
* Created a minimal startup template that authenticates using IdentityServer and uses the ASP.NET Core backend. This template uses the packages mentioned above.
* Worked on the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) and the [download page](https://abp.io/get-started) to be able to generate projects with the new UI option.
* Created a [tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Angular/Part-I) to jump start with the new UI option.
* Created a [tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG) to jump start with the new UI option.
We've created the template, document and infrastructure based on the latest Angular tools and trends:
* Uses [NgBootstrap](https://ng-bootstrap.github.io/) and [PrimeNG](https://www.primefaces.org/primeng/) as the UI component libraries. You can use your favorite library, no problem, but pre-built modules work with these libraries.
* Uses [NgBootstrap](https://ng-bootstrap.github.io/) as the UI component library. You can use your favorite library, but pre-built modules work with these libraries.
* Uses [NGXS](https://ngxs.gitbook.io/ngxs/) as the state management library.
Angular was the first SPA UI option, but it is not the last. After v1.0 release, we will start to work on a second UI option. Not decided yet, but candidates are Blazor, React and Vue.js. Waiting your feedback. You can thumb up using the following issues:
@ -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

142
docs/en/Blog-Posts/2020-03-19 v2_3_Release/Post.md

@ -0,0 +1,142 @@
# ABP Framework v2.3.0 Has Been Released!
In the days of **coronavirus**, we have released **ABP Framework v2.3** and this post will explain **what's new** with this release and **what we've done** in the last two weeks.
## About the Coronavirus & Our Team
**We are very sad** about the coronavirus case. As [Volosoft](https://volosoft.com/) team, we have **remote workers** working in their home in different countries. Beginning from the last week, we've **completely started to work remotely** from home including our main office employees.
We believe in and pray for that the humanity will overcome this issue in a short time.
## About the Release Cycle
Beginning from the ABP v2.1.0, we have started to release feature versions once **in two weeks**, on Thursdays. This is the 3rd release after that decision and we see that it works fine for now and improved our agility.
We will continue to release **feature versions** (like v2.4, v2.5) in every two weeks. In addition, we may release **hotfix versions** (like v2.3.1, v2.3.2) whenever needed.
## What's New in ABP Framework v2.3.0
We've completed & merged **[104](https://github.com/abpframework/abp/milestone/30?closed=1) issues and pull requests** with **393 commits** in this two weeks development period.
I will introduce some new features and enhancements introduced with this release.
### React Native Mobile Application
We have finally completed the **react native mobile application**. It currently allows you to **login**, manage your **users** and **tenants**. It utilizes the same setting, authorization and localization systems of the ABP Framework.
A few screenshots from the application:
![mobile-ui](react-native-ui.png)
It doesn't have much functionality but it is a **perfect starting point** for your own mobile application since it is completely integrated to the backend and supports multi-tenancy.
### Angular TypeScript Proxy Generator
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on he server side) and **model objects** (matches to [DTOs](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects) in the server side).
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced:
* It generates a **big, single** .ts file which has some problems;
* It get **too large** when your application grows.
* It doesn't fit into the **[modular](https://docs.abp.io/en/abp/latest/Module-Development-Basics) approach** of the ABP framework.
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually).
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies.
So, we've decided to create an ABP CLI command to automatically generate the typescript client proxies ([#2222](https://github.com/abpframework/abp/issues/2222)) for your REST API developed with the ABP Framework.
It is easy to use. Just run the following command in the **root folder** of the angular application:
````bash
abp generate-proxy
````
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](https://docs.abp.io/en/abp/latest/CLI).
### CRUD Application Services for Entities with Composite Keys
` CrudAppService ` is a useful base class to create CRUD application services for your entities. But it doesn't support entities with **composite primary keys**. `AbstractKeyCrudAppService` is the new base class that is developed to support entities with composite primary keys. See [the documentation](https://docs.abp.io/en/abp/latest/Application-Services#abstractkeycrudappservice) for more.
### Add Source Code of the Modules
The application startup template comes with some [application modules](https://docs.abp.io/en/abp/latest/Modules/Index) **pre-installed** as **NuGet & NPM packages**. This have a few important advantages:
* You can **easily [upgrade](https://docs.abp.io/en/abp/latest/CLI#update)** these modules when a new version is available.
* Your solution becomes **cleaner**, so you can focus on your own code.
However, when you need to make **major customizations** for a depended module, it is not easy as its source code is in your applications. To solve this problem, we've introduces a new command to the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) that **replaces** NuGet packages with their **source code** in your solution. The usage is simple:
````bash
abp add-module --with-source-code
````
This command adds a module with source code or replaces with its source code if it is already added as package references.
> It is suggested to **save your changes** to your source control system before using this command since it makes a lot of changes in your source code.
In addition, we've documented how to customize depended modules without changing their source code (see the section below). It is suggested to use modules as packages to easily upgrade them in the future.
> Source code of the free modules are licensed under **MIT**, so you can freely change them and add into your solution.
### Switch to Preview
ABP Framework is rapidly evolving and we are frequently releasing new versions. However, if you want to follow it closer, you can use the **daily preview packages**.
We've created an ABP CLI command to easily **update to the latest preview packages** for your solution. Run the following command in the root folder of your solution:
````bash
abp switch-to-preview
````
It will change the versions of all ABP related NuGet and NPM packages. You can **switch back to the latest stable** when you want:
````bash
abp switch-to-stable
````
See the [ABP CLI document](https://docs.abp.io/en/abp/latest/CLI#switch-to-preview) fore more.
### Documentation Improvements
#### Extending/Customizing Depended Application Modules
We've created a huge documentation that explains how to customize a depended module without changing its source code. See [the documentation](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide).
In addition to the documentation, we've revised all the modules ([#3166](https://github.com/abpframework/abp/issues/3166)) to make their services easily extensible & customizable.
#### EF Core Migration Guide
We've recently created a guide to explain the migration system that is used by the ABP startup templates. [This guide](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Migrations) also explains how to customize the migration structure, split your modules across multiple databases, reusing a module's table and son on.
#### Migration from the ASP.NET Boilerplate
If you have a solution built on the ASP.NET Boilerplate, we've [created a guide](https://docs.abp.io/en/abp/latest/AspNet-Boilerplate-Migration-Guide) that tries to help you if you want to migrate your solution to the new ABP Framework.
### Some Other Features
#### The Framework
* Add `IRepository.GetAsync` and `IRepository.FindAsync` methods ([#3184](https://github.com/abpframework/abp/issues/3148)).
#### Modules
* Get password & email address of the admin while creating a new tenant, for the tenant management module ([#3088](https://github.com/abpframework/abp/issues/3088)).
* Elastic search integrated full text search for the docs module ([#2901](https://github.com/abpframework/abp/pull/2901)).
* New Quartz background worker module ([#2762](https://github.com/abpframework/abp/issues/2762))
#### Samples
* Add multi-tenancy support to the microservice demo ([#3032](https://github.com/abpframework/abp/pull/3032)).
See [the release notes](https://github.com/abpframework/abp/releases/tag/2.3.0) for all feature, enhancement and bugfixes.
## What's Next?
We have the following goals for the next few months:
* Complete the **documentation and samples**, write more tutorials.
* Make the framework and existing modules more **customizable and extensible**.
* Integrate to **gRPC** & implement gRPC endpoint for pre-built modules ([#2882](https://github.com/abpframework/abp/issues/2882)).
* Create a **Blazor UI** for the ABP Framework & implement it for all the modules and startup templates ([#394](https://github.com/abpframework/abp/issues/394)).
* Add **new features** to pre-built modules and create new modules for the [ABP Commercial](https://commercial.abp.io/).
See [the GitHub milestones](https://github.com/abpframework/abp/milestones) for details.

BIN
docs/en/Blog-Posts/2020-03-19 v2_3_Release/abp-io-release-2-3-0-blog.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

BIN
docs/en/Blog-Posts/2020-03-19 v2_3_Release/react-native-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

69
docs/en/CLI.md

@ -22,7 +22,7 @@ dotnet tool update -g Volo.Abp.Cli
Generates a new solution based on the ABP [startup templates](Startup-Templates/Index.md).
Basic usage:
Usage:
````bash
abp new <solution-name> [options]
@ -48,13 +48,19 @@ 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:
* `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI).
* `module`: [Module template](Startup-Templates/Module.md). Additional options:
* `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI).
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server!
### add-package
@ -89,7 +95,7 @@ Adds a [multi-package application module](Modules/Index) to a solution by findin
> A business module generally consists of several packages (because of layering, different database provider options or other reasons). Using `add-module` command dramatically simplifies adding a module to a solution. However, each module may require some additional configurations which is generally indicated in the documentation of the related module.
Basic usage:
Usage
````bash
abp add-module <module-name> [options]
@ -108,6 +114,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
@ -128,9 +135,35 @@ abp update [options]
* `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages.
### switch-to-preview
You can use this command to switch your project to latest preview version of the ABP framework packages.
Usage:
````bash
abp switch-to-preview [options]
````
#### Options
`--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
If you're using the ABP Framework preview packages, you can switch back to stable version using this command.
Usage:
````bash
abp switch-to-stable [options]
````
#### Options
`--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
Some features of the CLI requires to be logged in to abp.io platform. To login with your username write
Some features of the CLI requires to be logged in to abp.io platform. To login with your username write:
```bash
abp login <username>
@ -146,6 +179,29 @@ Logs you out by removing the session token from your computer.
abp logout
```
### generate-proxy
Generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running `generate-proxy` command, your host must be up and running.
Usage:
````bash
abp generate-proxy [options]
````
#### Options
* `--apiUrl` or `-a`: Specifies the root URL of the HTTP API. The default value is being retrieved from the `environment.ts` file for the Angular application. Make sure your host is up and running before running `abp generate-proxy`.
* `--ui` or `-u`: Specifies the UI framework. Default value is `angular` and it is the only UI framework supported for now. Creates TypeScript code.
* `--module` or `-m`: Specifies the module name. Default module name is `app`, which indicates your own application (you typically want this since every module is responsible to maintain its own client proxies). Set `all` for to generate proxies for all the modules.
Example usage with the options:
````bash
abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
````
### help
Writes basic usage information of the CLI.
@ -162,4 +218,3 @@ Examples:
abp help # Shows a general help.
abp help new # Shows help about the "new" command.
````

179
docs/en/Customizing-Application-Modules-Extending-Entities.md

@ -0,0 +1,179 @@
# 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 objects.
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](Customizing-Application-Modules-Overriding-Services.md), then get or set the value as shown above.
## Entity Extensions (EF Core)
As mentioned above, all extra properties of an entity are stored as a single JSON object in the database table. This is not so natural especially when you want to;
* Create **indexes** and **foreign keys** for an extra property.
* Write **SQL** or **LINQ** using the extra property (search table by the property value, for example).
* Creating your **own entity** maps to the same table, but defines an extra property as a **regular property** in the entity (see the [EF Core migration document](Entity-Framework-Core-Migrations.md) for more).
To overcome the difficulties described above, ABP Framework entity extension system for the Entity Framework Core that allows you to use the same extra properties API defined above, but store a desired property as a separate field in the database table.
Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `ObjectExtensionManager`:
````csharp
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
);
````
* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table).
* You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties).
> This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEntityExtensions`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself.
Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database.
You can then use the same extra properties system defined in the previous section to manipulate the property over the entity.
## 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
* [Migration System for the EF Core](Entity-Framework-Core-Migrations.md)
* [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 because the module source is not in your 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 `IIdentityUserAppService`, 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.

6
docs/en/Customizing-Application-Modules-Overriding-User-Interface.md

@ -0,0 +1,6 @@
# 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)

98
docs/en/Entities.md

@ -295,6 +295,102 @@ 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), here are two type of configurations;
* By default, it is stored in a single `ExtraProperties` field as a `JSON` string (that means all extra properties stored in a single database table field). 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.
* If you want, you can use the `ObjectExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `ObjectExtensionManager` will continue to use a single `JSON` field as described above. This feature is especially useful when you are using a pre-built [application module](Modules/Index.md) and want to [extend its entities](Customizing-Application-Modules-Extending-Entities.md). See the [EF Core integration document](Entity-Framework-Core.md) to learn how to use the `ObjectExtensionManager`.
* 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** since it works with strings as property names.
* It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects.
### 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)

885
docs/en/Entity-Framework-Core-Migrations.md

@ -0,0 +1,885 @@
# EF Core Database Migrations
This document begins by **introducing the default structure** provided by [the application startup template](Startup-Templates/Application.md) and **discusses various scenarios** you may want to implement for your own application.
> This document is for who want to fully understand and customize the database structure comes with [the application startup template](Startup-Templates/Application.md). If you simply want to create entities and manage your code first migrations, just follow [the startup tutorials](Tutorials/Index.md).
### Source Code
You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code.
## About the EF Core Code First Migrations
Entity Framework Core provides an easy to use and powerful [database migration system](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). ABP Framework [startup templates](Startup-Templates/Index.md) take the advantage of this system to allow you to develop your application in a standard way.
However, EF Core migration system is **not so good in a modular environment** where each module maintains its **own database schema** while two or more modules may **share a single database** in practical.
Since ABP Framework cares about modularity in all aspects, it provides a **solution** to this problem. It is important to understand this solution if you need to **customize your database structure**.
> See [EF Core's own documentation](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to fully learn the EF Core Code First Migrations and why you need to such a system.
## The Default Solution & Database Configuration
When you [create a new web application](https://abp.io/get-started) (with EF Core, which is the default database provider), your solution structure will be similar to the picture below:
![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png)
Actual solution structure may be a bit different based on your preferences, but the database part will be same.
> This document will use the `Acme.BookStore` example project name to refer the projects and classes. You need to find the corresponding class/project in your solution.
### The Database Structure
The startup template has some [application modules](Modules/Index.md) pre-installed. Each layer of the solution has corresponding module **package references**. So, the `.EntityFrameworkCore` project has the NuGet references for the `.EntityFrameworkCore` packages of the used modules:
![bookstore-efcore-dependencies](images/bookstore-efcore-dependencies.png)
In this way, you collect all the **EF Core dependencies** under the `.EntityFrameworkCore` project.
> In addition to the module references, it references to the `Volo.Abp.EntityFrameworkCore.SqlServer` package since the startup template is pre-configured for the **SQL Server**. See the documentation if you want to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
While every module has its own `DbContext` class by design and can use its **own physical database**, the solution is configured to use a **single shared database** as shown in the figure below:
![single-database-usage](images/single-database-usage.png)
This is **the simplest configuration** and suitable for most of the applications. `appsettings.json` file has a **single connection string**, named `Default`:
````json
"ConnectionStrings": {
"Default": "..."
}
````
So, you have a **single database schema** which contains all the tables of the modules **sharing** this database.
ABP Framework's [connection string](Connection-Strings.md) system allows you to easily **set a different connection string** for a desired module:
````json
"ConnectionStrings": {
"Default": "...",
"AbpAuditLogging": "..."
}
````
The example configuration about tells to the ABP Framework to use the second connection string for the [Audit Logging module](Modules/Audit-Logging.md).
**However, this is just the beginning**. You also need to create the second database, create audit log tables inside it and maintain the database tables using the code first migrations approach. One of the main purposes of this document is to guide you on such **database separation** scenarios.
#### Module Tables
Every module uses its **own databases tables**. For example, the [Identity Module](Modules/Identity.md) has some tables to manage the users and roles in the system.
##### Table Prefixes
Since it is allowed to share a single database by all modules (it is the default configuration), a module typically uses a **table name prefix** to group its own tables.
The fundamental modules, like [Identity](Modules/Identity.md), [Tenant Management](Modules/Tenant-Management.md) and [Audit Logs](Modules/Audit-Logging.md), use the `Abp` prefix, while some other modules use their own prefixes. [Identity Server](Modules/IdentityServer.md) module uses the `IdentityServer` prefix for example.
If you want, you can **change the database table name prefix** for a module for your application. Example:
````csharp
Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids";
````
This code changes the prefix of the [Identity Server](Modules/IdentityServer.md) module. Write this code **at the very beginning** in your application.
> Every module also defines `DbSchema` property (near to `DbTablePrefix`), so you can set it for the databases support the schema usage.
### The Projects
From the database point of view, there are three important projects those will be explained in the next sections.
#### .EntityFrameworkCore Project
This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application.
**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity:
````csharp
[ConnectionStringName("Default")]
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
public DbSet<AppUser> Users { get; set; }
/* Add DbSet properties for your Aggregate Roots / Entities here. */
public BookStoreDbContext(DbContextOptions<BookStoreDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Configure the shared tables (with included modules) here */
builder.Entity<AppUser>(b =>
{
//Sharing the same Users table with the IdentityUser
b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users");
b.ConfigureByConvention();
b.ConfigureAbpUser();
/* Configure mappings for your additional properties
* Also see the MyProjectNameEntityExtensions class
*/
});
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
}
}
````
This simple `DbContext` class still needs some explanations:
* It defines a `[ConnectionStringName]` attribute which tells ABP to always use the `Default` connection string for this `Dbcontext`.
* It inherits from the `AbpDbContext<T>` instead of the standard `DbContext` class. You can see the [EF Core integration](Entity-Framework-Core.md) document for more. For now, know that the `AbpDbContext<T>` base class implements some conventions of the ABP Framework to automate some common tasks for you.
* It declares a `DbSet` property for the `AppUser` entity. `AppUser` shares the same table (named `AbpUsers` by default) with the `IdentityUser` entity of the [Identity module](Modules/Identity.md). The startup template provides this entity inside the application since we think that the User entity is generally needs to be customized in your application.
* The constructor takes a `DbContextOptions<T>` instance.
* It overrides the `OnModelCreating` method to define the EF Core mappings.
* It first calls the the `base.OnModelCreating` method to let the ABP Framework to implement the base mappings for us.
* It then configures the mapping for the `AppUser` entity. There is a special case for this entity (it shares a table with the Identity module), which will be explained in the next sections.
* It finally calls the `builder.ConfigureBookStore()` extension method to configure other entities of your application.
This design will be explained in more details after introducing the other database related projects.
#### .EntityFrameworkCore.DbMigrations Project
As mentioned in the previous section, every module (and your application) have **their own** separate `DbContext` classes. Each `DbContext` class only defines the entity to table mappings related to its own module and each module (and your application) use the related `DbContext` class **on runtime**.
As you know, EF Core Code First migration system relies on a `DbContext` class **to track and generate** the code first migrations. So, which `DbContext` we should use for the migrations? The answer is *none of them*. There is another `DbContext` defined in the `.EntityFrameworkCore.DbMigrations` project (which is the `BookStoreMigrationsDbContext` for this example solution).
##### The MigrationsDbContext
The `MigrationsDbContext` is only used to create and apply the database migrations. It is **not used on runtime**. It **merges** all the entity to table mappings of all the used modules plus the application's mappings.
In this way, you create and maintain a **single database migration path**. However, there are some difficulties of this approach and the next sections explains how ABP Framework overcomes these difficulties. But first, see the `BookStoreMigrationsDbContext` class as an example:
````csharp
/* This DbContext is only used for database migrations.
* It is not used on runtime. See BookStoreDbContext for the runtime DbContext.
* It is a unified model that includes configuration for
* all used modules and your application.
*/
public class BookStoreMigrationsDbContext : AbpDbContext<BookStoreMigrationsDbContext>
{
public BookStoreMigrationsDbContext(
DbContextOptions<BookStoreMigrationsDbContext> 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();
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
}
}
````
##### Sharing the Mapping Code
First problem is that: A module uses its own `DbContext` which needs to the database mappings. The `MigrationsDbContext` also needs to the same mapping in order to create the database tables for this module. We definitely **don't want to duplicate** the mapping code.
The solution is to define an **extension method** (on the `ModelBuilder`) that can be called by both `DbContext` classes. So, all modules define such extension methods.
For example, the `builder.ConfigureBackgroundJobs()` method call configures the database tables for the [Background Jobs module](Modules/Background-Jobs.md). The definition of this extension method is something like that:
````csharp
public static class BackgroundJobsDbContextModelCreatingExtensions
{
public static void ConfigureBackgroundJobs(
this ModelBuilder builder,
Action<BackgroundJobsModelBuilderConfigurationOptions> optionsAction = null)
{
var options = new BackgroundJobsModelBuilderConfigurationOptions(
BackgroundJobsDbProperties.DbTablePrefix,
BackgroundJobsDbProperties.DbSchema
);
optionsAction?.Invoke(options);
builder.Entity<BackgroundJobRecord>(b =>
{
b.ToTable(options.TablePrefix + "BackgroundJobs", options.Schema);
b.ConfigureCreationTime();
b.ConfigureExtraProperties();
b.Property(x => x.JobName)
.IsRequired()
.HasMaxLength(BackgroundJobRecordConsts.MaxJobNameLength);
//...
});
}
}
````
This extension method also gets options to change the database table prefix and schema for this module, but it is not important here.
The final application calls the extension methods inside the `MigrationsDbContext` class, so it can decide which modules are included in the database maintained by this `MigrationsDbContext`. If you want to create a second database and move some module tables to the second database, then you need to have a second `MigrationsDbContext` class which only calls the extension methods of the related modules. This topic will be detailed in the next sections.
The same `ConfigureBackgroundJobs` method is also called in the `DbContext` of the Background Jobs module:
````csharp
[ConnectionStringName(BackgroundJobsDbProperties.ConnectionStringName)]
public class BackgroundJobsDbContext
: AbpDbContext<BackgroundJobsDbContext>, IBackgroundJobsDbContext
{
public DbSet<BackgroundJobRecord> BackgroundJobs { get; set; }
public BackgroundJobsDbContext(DbContextOptions<BackgroundJobsDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//Reuse the same extension method!
builder.ConfigureBackgroundJobs();
}
}
````
In this way, the mapping configuration of a module can be shared between `DbContext` classes. The code above is inside the related module NuGet package, so you don't care about it.
##### Reusing a Table of a Module
You may want to **reuse a table** of a depended module in your application. In this case, you have two options:
1. You can **directly use the entity** defined by the module (you can still [extend the entity](Customizing-Application-Modules-Extending-Entities.md) in some level).
2. You can **create a new entity** mapping to the same database table.
###### Use the Entity Defined by a Module
Using an entity defined a module is pretty easy and standard. For example, Identity module defines the `IdentityUser` entity. You can inject the [repository](Repositories.md) for the `IdentityUser` and perform the standard repository operations for this entity. Example:
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
namespace Acme.BookStore
{
public class MyService : ITransientDependency
{
private readonly IRepository<IdentityUser, Guid> _identityUserRepository;
public MyService(IRepository<IdentityUser, Guid> identityUserRepository)
{
_identityUserRepository = identityUserRepository;
}
public async Task DoItAsync()
{
//Get all users
var users = await _identityUserRepository.GetListAsync();
}
}
}
````
This example injects the `IRepository<IdentityUser, Guid>` (default repository) which defines the standard repository methods and implements the `IQueryable` interface.
> In addition, Identity module defines the `IIdentityUserRepository` (custom repository) that can also be injected and used by your application. `IIdentityUserRepository` provides additional custom methods for the `IdentityUser` entity while it does not implement the `IQueryable` interface.
###### Create a New Entity
Working with an entity of a module is easy if you want to use the entity as is. However, you may want to define your own entity class and map to the same database table in the following cases;
* You want to **add a new field** to the table and map it to a property in the entity. You can't use the module's entity since it doesn't have the related property.
* You want to **use a subset of the table fields**. You don't want to access to all properties of the entity and hide the unrelated properties (from a security perspective or just by design).
* You don't want to directly **depend on** a module entity class.
In any case, the progress is same. Assume that you want to create an entity, named `AppRole`, mapped to the same table of the `IdentityRole` entity of the [Identity module](Modules/Identity.md).
Here, we will show the implementation, then **will discuss the limitations** of this approach.
First, create a new `AppRole` class in your `.Domain` project:
````csharp
using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace Acme.BookStore.Roles
{
public class AppRole : AggregateRoot<Guid>, IMultiTenant
{
// Properties shared with the IdentityRole class
public Guid? TenantId { get; private set; }
public string Name { get; private set; }
//Additional properties
public string Title { get; set; }
private AppRole()
{
}
}
}
````
* It's inherited from [the `AggregateRoot<Guid>` class](Entities.md) and implements [the `IMultiTenant` interface](Multi-Tenancy.md) because the `IdentityRole` also does the same.
* You can add any properties defined by the `IdentityRole` entity. This examples add only the `TenantId` and `Name` properties since we only need them here. You can make the setters private (like in this example) to prevent changing Identity module's properties accidently.
* You can add custom (additional) properties. This example adds the `Title` property.
* The **constructor is private**, so it is not allowed to directly create a new `AppRole` entity. Creating a role is a responsibility of the Identity module. You can query roles, set/update your custom properties, but you should not create or delete a role in your code, as a best practice (while there is nothing restricts you).
Now, it is time to define the EF Core mappings. Open the `DbContext` of your application (`BookStoreDbContext` in this sample) and add the following property:
````csharp
public DbSet<AppRole> Roles { get; set; }
````
Then configure the mapping inside the `OnModelCreating` method (after calling the `base.OnModelCreating(builder)`):
````csharp
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Configure the shared tables (with included modules) here */
//CONFIGURE THE AppRole ENTITY
builder.Entity<AppRole>(b =>
{
b.ToTable("AbpRoles");
b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(128);
});
...
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
}
````
We added the following lines:
````csharp
builder.Entity<AppRole>(b =>
{
b.ToTable("AbpRoles");
b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(128);
});
````
* It maps to the same `AbpRoles` table shared with the `IdentityRole` entity.
* `ConfigureByConvention()` configures the standard/base properties (like `TenantId`) and recommended to always call it.
You've configured the custom property for your `DbContext` that is used by your application on the runtime. We also need to configure the `MigrationsDbContext`.
Instead of directly changing the `MigrationsDbContext`, we should use the entity extension system of the ABP Framework. Find the `YourProjectNameEntityExtensions` class in the `.EntityFrameworkCore` project of your solution (`BookStoreEntityExtensions` for this example) and change it as shown below:
````csharp
public static class MyProjectNameEntityExtensions
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
);
});
}
}
````
> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)` or use a constant string.
`ObjectExtensionManager` is used to add properties to existing entities. Since `ObjectExtensionManager.Instance` is a static instance (singleton), we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework.
See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system.
> We've repeated a similar database mapping code, like `HasMaxLength(128)`, in both classes.
Now, you can add a new EF Core database migration using the standard `Add-Migration` command in the Package Manager Console (remember to select `.EntityFrameworkCore.DbMigrations` as the Default Project in the PMC and make sure that the `.Web` project is still the startup project):
![pmc-add-migration-role-title](images/pmc-add-migration-role-title.png)
This command will create a new code first migration class as shown below:
````csharp
public partial class Added_Title_To_Roles : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Title",
table: "AbpRoles",
maxLength: 128,
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Title",
table: "AbpRoles");
}
}
````
All done! Just run the `Update-Database` command in the PMC or run the `.DbMigrator` project in your solution to apply changes to database.
Now, you can work with the `AppRole` entity just like any other entity of your application. An example [application service](Application-Services.md) that queries and updates roles:
````csharp
public class AppRoleAppService : ApplicationService, IAppRoleAppService
{
private readonly IRepository<AppRole, Guid> _appRoleRepository;
public AppRoleAppService(IRepository<AppRole, Guid> appRoleRepository)
{
_appRoleRepository = appRoleRepository;
}
public async Task<List<AppRoleDto>> GetListAsync()
{
var roles = await _appRoleRepository.GetListAsync();
return roles
.Select(r => new AppRoleDto
{
Id = r.Id,
Name = r.Name,
Title = r.Title
})
.ToList();
}
public async Task UpdateTitleAsync(Guid id, string title)
{
var role = await _appRoleRepository.GetAsync(id);
role.Title = title;
await _appRoleRepository.UpdateAsync(role);
}
}
````
There are some **limitations** of creating a new entity and mapping it to a table of a depended module:
* Your **custom properties must be nullable**. For example, `AppRole.Title` was nullable here. Otherwise, Identity module throws exception because it doesn't know and can not fill the Title when it inserts a new role to the database.
* As a good practice, you should not update the **properties defined by the module**, especially if it requires a business logic. You typically want to manage your own properties.
##### Alternative Approaches
Instead of creating a new entity class to add a custom property, you can use the following approaches.
###### Using the ExtraProperties
All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property (because they implement the `IHasExtraProperties` interface), which is a `Dictionary<string, object>` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity.
For example, you can store query the title Property inside an `IdentityRole` instead of creating a new entity. Example:
````csharp
public class IdentityRoleExtendingService : ITransientDependency
{
private readonly IIdentityRoleRepository _identityRoleRepository;
public IdentityRoleExtendingService(IIdentityRoleRepository identityRoleRepository)
{
_identityRoleRepository = identityRoleRepository;
}
public async Task<string> GetTitleAsync(Guid id)
{
var role = await _identityRoleRepository.GetAsync(id);
return role.GetProperty<string>("Title");
}
public async Task SetTitleAsync(Guid id, string newTitle)
{
var role = await _identityRoleRepository.GetAsync(id);
role.SetProperty("Title", newTitle);
await _identityRoleRepository.UpdateAsync(role);
}
}
````
* `GetProperty` and `SetProperty` methods are shortcuts to get and set a value in the `role.ExtraProperties` dictionary and they are the recommended way to work with the extra properties.
In this way, you can easily attach any type of value to an entity of a depended module. However, there are some drawbacks of this usage:
* All the extra properties are stored as **a single JSON object** in the database. They are not stored as new table fields, as you may expect. Creating database table indexes and using SQL queries against these properties will be harder compared to simple table fields.
* Property names are strings, so they are **not type safe**. It is recommended to define constants for these kind of properties to prevent typo errors.
###### Using the Entity Extensions System
Entity extension system solves the main problem of the extra properties: It can store an extra property in a **standard table field** in the database.
All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
###### Creating a New Table
Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table.
In this case, you don't deal with migration problems, however you need to deal with the problems of data duplication. When the duplicated value changes, you should reflect the same change in your table. You can use local or distributed [event bus](Event-Bus.md) to subscribe to the change events for the original entity. This is the recommended way of depending on a microservice's data from another microservice, especially if they have separate physical databases (you can search on the web on data sharing on a microservice design, it is a wide topic to cover here).
> See the "[extending entities](Customizing-Application-Modules-Extending-Entities.md)" guide for more details on extending entities, including data duplication and synchronization tips.
#### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path
As mentioned before, `.EntityFrameworkCore.DbMigrations` merges all the database mappings of all the modules (plus your application's mappings) to create a unified migration path.
An alternative approach would be to allow each module to have its own migrations to maintain its database tables. While it seems more module in the beginning, it has some important drawbacks:
* **EF Core migration system depends on the DBMS provider**. For example, if a module has created migrations for SQL Server, then you can not use this migration code for MySQL. It is not practical for a module to maintain migrations for all available DBMS providers. Leaving the migration to the application code (as explained in this document) allows you to **choose the DBMS in the application** code.
* It would be harder or impossible to **share a table** between modules or **re-use a table** of a module in your application. Because EF Core migration system can not handle it and will throw exceptions like "Table XXX is already exists in the database".
* It would be harder to **customize/enhance** the mapping and the resulting migration code.
* It would be harder to track and **apply changes** to database when you use multiple modules.
## Using Multiple Databases
The default startup template is organized to use a single database used by all the modules and by your application. However, the ABP Framework and all the pre-built modules are designed so that **they can use multiple databases**. Each module can use its own database or you can group modules into a few databases.
This section will explain how to move Audit Logging, Setting Management and Permission Management module tables to a **second database** while the remaining modules continue to use the main ("Default") database.
The resulting structure will be like the figure below:
![single-database-usage](images/multiple-database-usage.png)
### Change the Connection Strings Section
First step is to change the connection string section inside all the `appsettings.json` files. Initially, it is like that:
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True;MultipleActiveResultSets=true"
}
````
Change it as shown below:
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True;MultipleActiveResultSets=true",
"AbpPermissionManagement": "Server=localhost;Database=BookStore_SecondDb;Trusted_Connection=True;MultipleActiveResultSets=true",
"AbpSettingManagement": "Server=localhost;Database=BookStore_SecondDb;Trusted_Connection=True;MultipleActiveResultSets=true",
"AbpAuditLogging": "Server=localhost;Database=BookStore_SecondDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
````
Added **three more connection strings** for the related module to target the `BookStore_SecondDb` database (they are all same). For example, `AbpPermissionManagement` is the connection string for the permission management module.
The `AbpPermissionManagement` is a constant [defined](https://github.com/abpframework/abp/blob/97eaa6ff5a044f503465455c86332e5a277b077a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDbProperties.cs#L11) by the permission management module. ABP Framework [connection string selection system](Connection-Strings.md) selects this connection string for the permission management module if you define. If you don't define, it fallbacks to the `Default` connection string.
### Create a Second Migration Project
Defining the connection strings as explained above is enough **on runtime**. However, `BookStore_SecondDb` database doesn't exist yet. You need to create the database and the tables for the related modules.
Just like the main database, we want to use the EF Core Code First migration system to create and maintain the second database.
An easy way is to create a second project (`.csproj`) for the second migration `DbContext`.
So, create a new **class library project** in your solution named `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` (or name it better if you didn't like it).
The `.csproj` content should be something like that:
````xml
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Acme.BookStore.DbMigrationsForSecondDb</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Acme.BookStore.EntityFrameworkCore\Acme.BookStore.EntityFrameworkCore.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0" />
</ItemGroup>
</Project>
````
You can just copy & modify the content of the original `.DbMigrations` project. This project references to the `.EntityFrameworkCore` project. **Only difference** is the `RootNamespace` value.
**Add a reference** to this project from the `.Web` project (otherwise, EF Core tooling doesn't allow to use the `Add-Migration` command).
### Create the Second DbMigrationDbContext
Create a new `DbContext` for the migrations and call the **extension methods** of the modules to configure the database tables for the related modules:
````csharp
[ConnectionStringName("AbpPermissionManagement")]
public class BookStoreSecondMigrationsDbContext :
AbpDbContext<BookStoreSecondMigrationsDbContext>
{
public BookStoreSecondMigrationsDbContext(
DbContextOptions<BookStoreSecondMigrationsDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement();
builder.ConfigureSettingManagement();
builder.ConfigureAuditLogging();
}
}
````
> `[ConnectionStringName(...)]` attribute is important here and tells to the ABP Framework which connection string should be used for this `DbContext`. We've used `AbpPermissionManagement`, but all are the same.
Create a **Design Time Db Factory** class, that is used by the EF Core tooling (by `Add-Migration` and `Update-Database` PCM commands for example):
````csharp
/* This class is needed for EF Core console commands
* (like Add-Migration and Update-Database commands) */
public class BookStoreSecondMigrationsDbContextFactory
: IDesignTimeDbContextFactory<BookStoreSecondMigrationsDbContext>
{
public BookStoreSecondMigrationsDbContext CreateDbContext(string[] args)
{
var configuration = BuildConfiguration();
var builder = new DbContextOptionsBuilder<BookStoreSecondMigrationsDbContext>()
.UseSqlServer(configuration.GetConnectionString("AbpPermissionManagement"));
return new BookStoreSecondMigrationsDbContext(builder.Options);
}
private static IConfigurationRoot BuildConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false);
return builder.Build();
}
}
````
This is similar to the class inside the `.EntityFrameworCore.DbMigrations` project, except this one uses the `AbpPermissionManagement` connection string.
Now, you can open the Package Manager Console, select the `.EntityFrameworkCore.DbMigrationsForSecondDb` project as the default project (make sure the `.Web` project is still the startup project) and run the `Add-Migration "Initial"` and `Update-Database` commands as shown below:
![pmc-add-migration-initial-update-database](images/pmc-add-migration-initial-update-database.png)
Now, you should have a new database contains only the tables needed by the related modules:
![bookstore-second-database](images/bookstore-second-database.png)
### Remove Modules from the Main Database
We've **created a second database** contains tables for the Audit Logging, Permission Management and Setting Management modules. So, we should **delete these tables from the main database**. It is pretty easy.
First, remove the following lines from the `MigrationsDbContext` class (`BookStoreMigrationsDbContext` for this example):
````csharp
builder.ConfigurePermissionManagement();
builder.ConfigureSettingManagement();
builder.ConfigureAuditLogging();
````
Open the Package Manager Console, select the `.EntityFrameworkCore.DbMigrations` as the Default project (make sure that the `.Web` project is still the startup project) and run the following command:
````
Add-Migration "Removed_Audit_Setting_Permission_Modules"
````
This command will create a new migration class as shown below:
````csharp
public partial class Removed_Audit_Setting_Permission_Modules : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpAuditLogActions");
migrationBuilder.DropTable(
name: "AbpEntityPropertyChanges");
migrationBuilder.DropTable(
name: "AbpPermissionGrants");
migrationBuilder.DropTable(
name: "AbpSettings");
migrationBuilder.DropTable(
name: "AbpEntityChanges");
migrationBuilder.DropTable(
name: "AbpAuditLogs");
}
...
}
````
Be careful in this step:
* If you have a **live system**, then you should care about the **data loss**. You need to move the table contents to the second database before deleting the tables.
* If you **haven't started** your project yet, you can consider to **remove all the migrations** and re-create the initial one to have a cleaner migration history.
Run the `Update-Database` command to delete the tables from your main database.
Notice that you've also **deleted some initial seed data** (for example, permission grants for the admin role) if you haven't copied it to the new database. If you run the application, you may not login anymore. The solution is simple: **Re-run the `.DbMigrator` console application** in your solution, it will seed the new database.
### Automate the Second Database Schema Migration
`.DbMigrator` console application can run the database seed code across multiple databases, without any additional configuration. However, it can not run the EF Core Code First Migrations inside the second database migration project. Now, you will see how to configure the console migration application to handle both databases.
#### Implementing the IBookStoreDbSchemaMigrator
`EntityFrameworkCoreBookStoreDbSchemaMigrator` class inside the `Acme.BookStore.EntityFrameworkCore.DbMigrations` project is responsible to migrate the database schema for the `BookStoreMigrationsDbContext`. It should be like that:
````csharp
[Dependency(ReplaceServices = true)]
public class EntityFrameworkCoreBookStoreDbSchemaMigrator
: IBookStoreDbSchemaMigrator, ITransientDependency
{
private readonly IServiceProvider _serviceProvider;
public EntityFrameworkCoreBookStoreDbSchemaMigrator(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task MigrateAsync()
{
/* We are intentionally resolving the BookStoreMigrationsDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.
*/
await _serviceProvider
.GetRequiredService<BookStoreMigrationsDbContext>()
.Database
.MigrateAsync();
}
}
````
It implements the `IBookStoreDbSchemaMigrator` and **replaces existing services** (see the first line).
Remove the `[Dependency(ReplaceServices = true)]` line, because we will have two implementations of this interface and we want to use both. We don't want to replace one of them.
Create a copy of this class inside the new migration project (`Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb`), but use the `BookStoreSecondMigrationsDbContext`. Example implementation:
````csharp
public class EntityFrameworkCoreSecondBookStoreDbSchemaMigrator
: IBookStoreDbSchemaMigrator, ITransientDependency
{
private readonly IServiceProvider _serviceProvider;
public EntityFrameworkCoreSecondBookStoreDbSchemaMigrator(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task MigrateAsync()
{
/* We are intentionally resolving the BookStoreSecondMigrationsDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.
*/
await _serviceProvider
.GetRequiredService<BookStoreSecondMigrationsDbContext>()
.Database
.MigrateAsync();
}
}
````
> Name of this class is important for [dependency injection](Dependency-Injection.md). It should end with `BookStoreDbSchemaMigrator` to be injectable by `IBookStoreDbSchemaMigrator` reference.
We, now, have two implementations of the `IBookStoreDbSchemaMigrator` interface, each one responsible to migrate the related database schema.
#### Define a Module Class for the Second Migration Project
It is time to define the [module](Module-Development-Basics.md) class for this second migrations (`Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb`) project:
````csharp
[DependsOn(
typeof(BookStoreEntityFrameworkCoreModule)
)]
public class BookStoreEntityFrameworkCoreSecondDbMigrationsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<BookStoreSecondMigrationsDbContext>();
}
}
````
Now, reference `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` project from the `Acme.BookStore.DbMigrator` project and `typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule)` to the dependency list of the `BookStoreDbMigratorModule`. `BookStoreDbMigratorModule` class should be something like that:
````csharp
[DependsOn(
typeof(AbpAutofacModule),
typeof(BookStoreEntityFrameworkCoreDbMigrationsModule),
typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule), // ADDED THIS!
typeof(BookStoreApplicationContractsModule)
)]
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.
#### 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.
## Conclusion
This document explains how to split your databases and manage your database migrations of your solution for Entity Framework Core. In brief, you need to have a separate migration project per different databases.
## Source Code
You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code.

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

@ -1,12 +1,16 @@
# Switch to another DBMS for Entity Framework Core
# Switch to Another DBMS for Entity Framework Core
**[The application startup template](Startup-Templates/Application.md)** comes with SQL Server provider pre-configured for the Entity Framework Core. Entity Framework Core supports [many other DBMSs](https://docs.microsoft.com/en-us/ef/core/providers/) and you can use any of them with your ABP based applications.
**[The application startup template](Startup-Templates/Application.md)** comes with **SQL Server provider pre-configured** for the Entity Framework Core. However, EF Core supports [many other DBMSs](https://docs.microsoft.com/en-us/ef/core/providers/) and you can use any of them within your ABP based applications.
ABP framework provides integration packages for some common DBMSs to make the configuration a bit easier (see the [entity framework core document](Entity-Framework-Core.md) for a list of available integration packages). However, you can configure your DBMS provider without these integration packages.
ABP framework provides **integration packages** for some common DBMSs to make the configuration a bit easier. You can use the following documents to learn how to **switch to your favorite DBMS**:
While using the integration package is always recommended (it also makes standard for the depended version across different modules), you can do it manually if there is no integration package for your DBMS provider.
* [MySQL](Entity-Framework-Core-MySQL.md)
* [PostgreSQL](Entity-Framework-Core-PostgreSQL.md)
* [SQLite](Entity-Framework-Core-SQLite.md)
This document explains how to switch to MySQL without using [the MySQL integration package](Entity-Framework-Core-MySQL.md).
However, you can configure your DBMS provider **without** these integration packages. While using the integration package is always recommended (it also makes standard for the depended version across different modules), you can do it manually if there is no integration package for your DBMS provider.
For an example, this document explains how to switch to MySQL without using [the MySQL integration package](Entity-Framework-Core-MySQL.md).
## Replace the SQL Server Dependency
@ -87,4 +91,18 @@ Run the `.DbMigrator` project to create the database and seed the initial data.
## Run the Application
It is ready. Just run the application and enjoy coding.
It is ready. Just run the application and enjoy coding.
## DBMS restrictions
Different DBMS may have some restrictions, such as the maximum length of field names, index length, etc.
The module may provide some built-in solutions. You can configure it via `ModelBuilder`. eg: `Identity Server` module.
```csharp
builder.ConfigureIdentityServer(options =>
{
options.DatabaseProvider = EfCoreDatabaseProvider.MySql;
});
```
Related discussions: https://github.com/abpframework/abp/issues/1920

121
docs/en/Entity-Framework-Core.md

@ -32,14 +32,9 @@ namespace MyCompany.MyProject
Entity Framework Core supports various database management systems ([see all](https://docs.microsoft.com/en-us/ef/core/providers/)). ABP framework and this document doesn't depend on any specific DBMS.
If you are creating a reusable library, avoid to depend on a specific DBMS package. However, in a final application you eventually will select a DBMS.
If you are creating a [reusable application module](Modules/Index.md), avoid to depend on a specific DBMS package. However, in a final application you eventually will select a DBMS.
ABP framework provides integration packages for some common DBMSs to make the configuration a bit easier. [The startup templates](Startup-Templates/Index.md) come with **SQL Server (localdb) pre-configured**. See the following documents to learn how to configure for the other DBMS providers:
* [MySQL](Entity-Framework-Core-MySQL.md)
* [PostgreSQL](Entity-Framework-Core-PostgreSQL.md)
* [SQLite](Entity-Framework-Core-SQLite.md)
* [Others](Entity-Framework-Core-Other-DBMS.md)
See [Switch to Another DBMS for Entity Framework Core](Entity-Framework-Core-Other-DBMS.md) document to learn how to switch the DBMS.
## Creating DbContext
@ -63,6 +58,53 @@ namespace MyCompany.MyProject
}
````
### About the EF Core Fluent Mapping
The [application startup template](Startup-Templates/Application.md) has been configured to use the [EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/) to map your entities to your database tables.
You can still use the **data annotation attributes** (like `[Required]`) on the properties of your entity while the ABP documentation generally follows the **fluent mapping API** approach. It is up to you.
ABP Framework has some **base entity classes** and **conventions** (see the [entities document](Entities.md)) and it provides some useful **extension methods** to configure the properties inherited from the base entity classes.
#### ConfigureByConvention Method
`ConfigureByConvention()` is the main extension method that **configures all the base properties** and conventions for your entities. So, it is a **best practice** to call this method for all your entities, in your fluent mapping code.
**Example**: Assume that you've a `Book` entity derived from `AggregateRoot<Guid>` base class:
````csharp
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; }
}
````
You can override the `OnModelCreating` method in your `DbContext` and configure the mapping as shown below:
````csharp
protected override void OnModelCreating(ModelBuilder builder)
{
//Always call the base method
base.OnModelCreating(builder);
builder.Entity<Book>(b =>
{
b.ToTable("Books");
//Configure the base properties
b.ConfigureByConvention();
//Configure other properties (if you are using the fluent API)
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
}
````
* Calling `b.ConfigureByConvention()` is important here to properly **configure the base properties**.
* You can configure the `Name` property here or you can use the **data annotation attributes** (see the [EF Core document](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)).
> While there are many extension methods to configure your base properties, `ConfigureByConvention()` internally calls them if necessary. So, it is enough to call it.
### Configure the Connection String Selection
If you have multiple databases in your application, you can configure the connection string name for your DbContext using the `[ConnectionStringName]` attribute. Example:
@ -230,7 +272,7 @@ public override async Task DeleteAsync(
}
````
### Access to the EF Core API
## Access to the EF Core API
In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example:
@ -256,9 +298,60 @@ public class BookService
> Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the DbContext. This breaks encapsulation, but this is what you want in that case.
### Advanced Topics
## Extra Properties & Object Extension Manager
Extra Properties system allows you to set/get dynamic properties to entities those implement the `IHasExtraProperties` interface. It is especially useful when you want to add custom properties to the entities defined in an [application module](Modules/Index.md), when you use the module as package reference.
By default, all the extra properties of an entity are stored as a single `JSON` object in the database.
#### Set Default Repository Classes
Entity extension system allows you to to store desired extra properties in separate fields in the related database table. For more information about the extra properties & the entity extension system, see the following documents:
* [Customizing the Application Modules: Extending Entities](Customizing-Application-Modules-Extending-Entities.md)
* [Entities](Entities.md)
This section only explains the EF Core related usage of the `ObjectExtensionManager`.
### ObjectExtensionManager.Instance
`ObjectExtensionManager` implements the singleton pattern, so you need to use the static `ObjectExtensionManager.Instance` to perform all the operations.
### MapEfCoreProperty
`MapEfCoreProperty` is a shortcut extension method to define an extension property for an entity and map to the database.
**Example**: Add `Title` property (database field) to the `IdentityRole` entity:
````csharp
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
);
````
If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field.
>`MapEfCoreProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEntityExtensions` class that is safe to use this method inside.
### ConfigureEfCoreEntity
If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity` extension method in your entity mapping. However, there is a shortcut extension method `ConfigureObjectExtensions` that can be used while configuring the entity mapping:
````csharp
builder.Entity<YourEntity>(b =>
{
b.ConfigureObjectExtensions();
//...
});
````
> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention.
See the "*ConfigureByConvention Method*" section above for more information.
## Advanced Topics
### Set Default Repository Classes
Default generic repositories are implemented by `EfCoreRepository` class by default. You can create your own implementation and use it for all the default repository implementations.
@ -304,7 +397,7 @@ context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
});
```
#### Set Base DbContext Class or Interface for Default Repositories
### Set Base DbContext Class or Interface for Default Repositories
If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example:
@ -336,7 +429,7 @@ public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>,
One advantage of using an interface for a DbContext is then it will be replaceable by another implementation.
#### Replace Other DbContextes
### Replace Other DbContextes
Once you properly define and use an interface for DbContext, then any other implementation can replace it using the `ReplaceDbContext` option:
@ -349,3 +442,7 @@ context.Services.AddAbpDbContext<OtherDbContext>(options =>
````
In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime.
## See Also
* [Entities](Entities.md)

52
docs/en/Exception-Handling.md

@ -1,4 +1,4 @@
## Exception Handling
# Exception Handling
ABP provides a built-in infrastructure and offers a standard model for handling exceptions in a web application.
@ -7,7 +7,7 @@ ABP provides a built-in infrastructure and offers a standard model for handling
* Provides a configurable way to **localize** exception messages.
* Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map these to custom exceptions.
### Automatic Exception Handling
## Automatic Exception Handling
`AbpExceptionFilter` handles an exception if **any of the following conditions** are met:
@ -17,7 +17,7 @@ ABP provides a built-in infrastructure and offers a standard model for handling
If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client.
#### Error Message Format
### Error Message Format
Error Message is an instance of the `RemoteServiceErrorResponse` class. The simplest error JSON has a **message** property as shown below:
@ -83,11 +83,11 @@ Error **details** in an optional field of the JSON error message. Thrown `Except
`AbpValidationException` implements the `IHasValidationErrors` interface and it is automatically thrown by the framework when a request input is not valid. So, usually you don't need to deal with validation errors unless you have higly customised validation logic.
#### Logging
### Logging
Caught exceptions are automatically logged.
##### Log Level
#### Log Level
Exceptions are logged with the `Error` level by default. The Log level can be determined by the exception if it implements the `IHasLogLevel` interface. Example:
@ -100,7 +100,7 @@ public class MyException : Exception, IHasLogLevel
}
````
##### Self Logging Exceptions
#### Self Logging Exceptions
Some exception types may need to write additional logs. They can implement the `IExceptionWithSelfLogging` if needed. Example:
@ -116,7 +116,7 @@ public class MyException : Exception, IExceptionWithSelfLogging
> `ILogger.LogException` extension methods is used to write exception logs. You can use the same extension method when needed.
### Business Exceptions
## Business Exceptions
Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception.
@ -145,11 +145,11 @@ Volo.Qa:010002
* You can **directly throw** a `BusinessException` or **derive** your own exception types from it when needed.
* All properties are optional for the `BusinessException` class. But you generally set either `ErrorCode` or `Message` property.
### Exception Localization
## Exception Localization
One problem with throwing exceptions is how to localize error messages while sending it to the client. ABP offers two models and their variants.
#### User Friendly Exception
### User Friendly Exception
If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client.
@ -192,7 +192,7 @@ Then the localization text can be:
* The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class.
#### Using Error Codes
### Using Error Codes
`UserFriendlyException` is fine, but it has a few problems in advanced usages:
@ -204,7 +204,7 @@ Instead of localizing the message while throwing the exception, you can separate
First, define the **code-namespace** to **localization resource** mapping in the module configuration:
````C#
services.Configure<ExceptionLocalizationOptions>(options =>
services.Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("Volo.Qa", typeof(QaResource));
});
@ -230,7 +230,7 @@ throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);
* Throwing any exception implementing the `IHasErrorCode` interface behaves the same. So, the error code localization approach is not unique to the `BusinessException` class.
* Defining localized string is not required for an error message. If it's not defined, ABP sends the default error message to the client. It does not use the `Message` property of the exception! if you want that, use the `UserFriendlyException` (or use an exception type that implements the `IUserFriendlyException` interface).
##### Using Message Parameters
#### Using Message Parameters
If you have a parameterized error message, then you can set it with the exception's `Data` property. For example:
@ -265,7 +265,7 @@ Then the localized text can contain the `UserName` parameter:
* `WithData` can be chained with more than one parameter (like `.WithData(...).WithData(...)`).
### HTTP Status Code Mapping
## HTTP Status Code Mapping
ABP tries to automatically determine the most suitable HTTP status code for common exception types by following these rules:
@ -280,18 +280,38 @@ ABP tries to automatically determine the most suitable HTTP status code for comm
The `IHttpExceptionStatusCodeFinder` is used to automatically determine the HTTP status code. The default implementation is the `DefaultHttpExceptionStatusCodeFinder` class. It can be replaced or extended as needed.
#### Custom Mappings
### Custom Mappings
Automatic HTTP status code determination can be overrided by custom mappings. For example:
````C#
services.Configure<ExceptionHttpStatusCodeOptions>(options =>
services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
{
options.Map("Volo.Qa:010002", HttpStatusCode.Conflict);
});
````
### Built-In Exceptions
## Subscribing to the Exceptions
It is possible to be informed when the ABP Framework **handles an exception**. It automatically **logs** all the exceptions to the standard [logger](Logging.md), but you may want to do more.
In this case, create a class derived from the `ExceptionSubscriber` class in your application:
````csharp
public class MyExceptionSubscriber : ExceptionSubscriber
{
public override async Task HandleAsync(ExceptionNotificationContext context)
{
//TODO...
}
}
````
The `context` object contains necessary information about the exception occurred.
> You can have multiple subscribers, each gets a copy of the exception. Exceptions thrown by your subscriber is ignored (but still logged).
## Built-In Exceptions
Some exception types are automatically thrown by the framework:

10
docs/en/Getting-Started-Angular-Template.md

@ -1,6 +1,6 @@
## Getting Started With the Angular Application Template
This tutorial explain how to create a new Angular application using the startup template, configure and run it.
This tutorial explains how to create a new Angular application using the startup template, configure and run it.
### Creating a New Project
@ -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
@ -103,7 +103,7 @@ Most of the application APIs require authentication & authorization. If you want
#### Run the Angular Application (Client Side)
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com) package manager while npm install will also work in most cases):
Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest the [yarn](https://yarnpkg.com) package manager while `npm install` will also work in most cases)
````bash
yarn
@ -123,4 +123,4 @@ The startup template includes the **identity management** and **tenant managemen
### What's Next?
* [Application development tutorial](Tutorials/Angular/Part-I.md)
* [Application development tutorial](Tutorials/Part-1)

4
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)
@ -153,5 +153,5 @@ namespace BasicAspNetCoreApplication
## Source Code
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication).
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication).

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/AspNetCore-Mvc/Part-I.md)
* [Application development tutorial](Tutorials/Part-1.md)

2
docs/en/Getting-Started-Console-Application.md

@ -178,4 +178,4 @@ Just called `options.UseAutofac()` method in the `AbpApplicationFactory.Create`
## Source Code
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp/tree/master/samples/BasicConsoleApplication).
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication).

6
docs/en/Getting-Started-With-Startup-Templates.md

@ -0,0 +1,6 @@
# Getting Started with the Startup Templates
See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates:
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md)
* [Getting Started with the Angular UI](Getting-Started-Angular-Template.md)

201
docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md

@ -0,0 +1,201 @@
# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications
This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**.
Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly.
Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage.
1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.
2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections.
> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings.
>
> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below.
## 1. AddAzureAD
This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/).
If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project.
#### **Updating `appsettings.json`**
You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri.
Then, you need to configure the `OpenIdConnectOptions` to complete the integration.
#### Configuring OpenIdConnectOptions
In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "Acme.BookStore";
})
.AddAzureAD(options => configuration.Bind("AzureAd", options));
context.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Scope.Add("email");
});
}
````
> **Don't forget to:**
>
> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../Modules/Account.md) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
You are done and integration is completed.
## 2. Alternative Approach: AddOpenIdConnect
If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication.
You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`.
To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "BookStore";
})
.AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
});
}
````
And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
# FAQ
* Help! `GetExternalLoginInfoAsync` returns `null`!
* There can be 2 reasons for this;
1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`:
````csharp
options.SignInScheme = IdentityConstants.ExternalScheme;
````
2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping:
````csharp
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
````
* Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error!
* If you set your **CallbackPath** in appsettings as:
````csharp
"AzureAd": {
...
"CallbackPath": "/signin-azuread-oidc"
}
````
your **Redirect URI** of your application in azure portal must be with <u>domain</u> like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`.
* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error!
* This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add
````csharp
options.Scope.Add("email");
````
to your openid configuration.
* How can I **debug/watch** which claims I get before they get mapped?
* You can add a simple event under openid configuration to debug before mapping like:
````csharp
options.Events.OnTokenValidated = (async context =>
{
var claimsFromOidcProvider = context.Principal.Claims.ToList();
await Task.CompletedTask;
});
````
## See Also
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md).
* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md).

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

@ -0,0 +1,113 @@
# How to Customize the Login Page for MVC / Razor Page Applications
When you create a new application using the [application startup template](../Startup-Templates/Application.md), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](../Modules/Account.md) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference.
This document explains how to customize the login page for your own application.
## Create a Login PageModel
Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module.
````csharp
public class CustomLoginModel : LoginModel
{
public CustomLoginModel(
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider,
Microsoft.Extensions.Options.IOptions<Volo.Abp.Account.Web.AbpAccountOptions> accountOptions)
: base(schemeProvider, accountOptions)
{
}
}
````
> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](../Dependency-Injection.md) system.
Then you can override any method you need and add new methods and properties needed by the UI.
## Overriding the Login Page UI
Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module thanks to the [Virtual File System](../Virtual-File-System.md).
A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below:
````xml
@page
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<form method="post" class="mt-4">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
<div class="form-group">
<label asp-for="LoginInput.UserNameOrEmailAddress"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginInput.Password"></label>
<input asp-for="LoginInput.Password" class="form-control" />
<span asp-validation-for="LoginInput.Password" class="text-danger"></span>
</div>
<div class="form-check">
<label asp-for="LoginInput.RememberMe" class="form-check-label">
<input asp-for="LoginInput.RememberMe" class="form-check-input" />
@Html.DisplayNameFor(m => m.LoginInput.RememberMe)
</label>
</div>
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-block btn-lg mt-3">@L["Login"]</abp-button>
</form>
</div>
<div class="card-footer text-center border-0">
<abp-button type="button" button-type="Link" name="Action" value="Cancel" class="px-2 py-0">@L["Cancel"]</abp-button> @* TODO: Only show if identity server is used *@
</div>
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="col-md-6">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
````
Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
## See Also
* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md).

101
docs/en/How-To/Customize-SignIn-Manager.md

@ -0,0 +1,101 @@
# How to Customize the SignIn Manager for ABP Applications
After creating a new application using the [application startup template](../Startup-Templates/Application.md), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](../Modules/Account.md) uses the [Identity Management Module](../Modules/Identity.md) for SignIn Manager and the [Identity Management Module](../Modules/Identity.md) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)).
To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container.
This document explains how to customize the SignIn Manager for your own application.
## Create a CustomSignInManager
Create a new class inheriting the [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package.
````csharp
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
public CustomSignInManager(
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
}
````
> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application.
Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow.
## Overriding the GetExternalLoginInfoAsync Method
In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented.
A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept.
````csharp
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey"))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey("XsrfKey"))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens()
};
}
````
To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](../Dependency-Injection.md).
## Register to Dependency Injection
Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs).
Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one:
````csharp
PreConfigure<IdentityBuilder>(identityBuilder =>
{
identityBuilder.AddSignInManager<CustomSignInManager>();
});
````
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
## See Also
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md).
* [Identity Management Module](../Modules/Identity.md).

9
docs/en/How-To/Index.md

@ -0,0 +1,9 @@
# "How To" Guides
This section contains "how to" guides for some specific questions frequently asked. While some of them are common development tasks and not directly related to the ABP Framework, we think it is useful to have some concrete examples those directly work with your ABP based applications.
## Authentication
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md)
* [How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications](Azure-Active-Directory-Authentication-MVC.md)
* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.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

162
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
@ -144,7 +134,7 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<PermissionOptions>(options =>
Configure<AbpPermissionOptions>(options =>
{
options.DefinitionProviders.Add<MyProjectPermissionDefinitionProvider>();
});
@ -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).

4
docs/en/Modules/Identity.md

@ -0,0 +1,4 @@
# Identity Management Module
See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...

3
docs/en/Modules/Tenant-Management.md

@ -0,0 +1,3 @@
# Tenant Management Module
TODO

15
docs/en/Multi-Tenancy.md

@ -4,7 +4,7 @@ ABP Multi-tenancy module provides base functionality to create multi tenant appl
Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) multi-tenancy as like that:
> Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of a software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants.
> Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants.
### Volo.Abp.MultiTenancy Package
@ -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,6 +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 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.
@ -342,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"));
});
//...
@ -354,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");

3
docs/en/Object-Extensions.md

@ -0,0 +1,3 @@
# Object Extensions
TODO

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.

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

@ -2,11 +2,12 @@
## Introduction
This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. This document explains the solution structure and projects in details. If you want to start quickly, follow the guides below:
This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices.
* 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 (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [ASP.NET Core MVC Application Development Tutorial](../Tutorials/AspNetCore-Mvc/Part-I.md) to learn how to develop applications using this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [Angular Application Development Tutorial](../Tutorials/Angular/Part-I.md) to learn how to develop applications using this template (uses Angular as the UI framework and MongoDB as the database provider).
This document explains **the solution structure** and projects in details. If you want to start quickly, follow the guides below:
* [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes.
* [The application development tutorial](../Tutorials/Part-1) explains step by step application development.
## How to Start With?
@ -123,6 +124,8 @@ Notice that the migration `DbContext` is only used for database migrations and *
* Depends on the `.EntityFrameworkCore` project since it re-uses the configuration defined for the `DbContext` of the application.
> This project is available only if you are using EF Core as the database provider.
>
> See the [Entity Framework Core Migrations Guide](../Entity-Framework-Core-Migrations.md) to understand this project in details.
#### .DbMigrator Project
@ -145,7 +148,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 +156,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.
@ -258,16 +261,183 @@ You should run the application with the given order:
### Angular UI
If you choose Angular as the UI framework (using the `-u angular` option), the solution is separated into two folders:
If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into three folders:
* `angular` folder contains the Angular UI application, the client-side code.
* `aspnet-core` folder contains the ASP.NET Core solution, the server-side code.
* `react-native` folder contains the React Native UI application, the client-side code for mobile.
The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the `Angular` application consumes it.
Angular application folder structure looks like below:
![angular-folder-structure](../images/angular-folder-structure.png)
Each of ABP Commercial modules is an NPM package. Some ABP modules are added as a dependency in `package.json`. These modules install with their dependencies. To see all ABP packages, you can run the following command in the `angular` folder:
```bash
yarn list --pattern abp
```
Angular application module structure:
![Angular template structure diagram](../images/angular-template-structure-diagram.png)
#### AppModule
`AppModule` is the root module of the application. Some of ABP modules and some essential modules imported to the `AppModule`.
ABP Config modules also have imported to `AppModule`  for initially requirements of lazy-loadable ABP modules.
#### AppRoutingModule
There are lazy-loadable ABP modules in the `AppRoutingModule` as routes.
> Paths of ABP Modules should not be changed.
You should add `routes` property in the `data` object to add a link on the menu to redirect to your custom pages.
```js
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGuard, PermissionGuard],
data: {
routes: {
name: 'ProjectName::Menu:Dashboard',
order: 2,
iconClass: 'fa fa-dashboard',
requiredPolicy: 'ProjectName.Dashboard.Host'
} as ABP.Route
}
}
```
In the above example;
* If the user is not logged in, AuthGuard blocks access and redirects to the login page.
* PermissionGuard checks the user's permission with `requiredPolicy` property of the `rotues` object. If the user is not authorized to access the page, the 403 page appears.
* `name` property of `routes` is the menu link label. A localization key can be defined .
* `iconClass` property of `routes` object is the menu link icon class.
* `requiredPolicy` property of `routes` object is the required policy key to access the page.
After the above `routes` definition, if the user is authorized, the dashboard link will appear on the menu.
#### Shared Module
The modules that may be required for all modules have imported to the `SharedModule`. You should import the `SharedModule` to all modules.
See the [Sharing Modules](https://angular.io/guide/sharing-ngmodules) document.
#### Environments
The files under the `src/environments` folder has the essential configuration of the application.
#### Home Module
Home module is an example lazy-loadable module that loads on the root address of the application.
#### Styles
The required style files added to `styles` array in the `angular.json`. `AppComponent` loads some style files lazily via `LazyLoadService` after the main bundle is loaded to shorten the first rendering time.
#### Testing
You should create your tests in the same folder as the file file you want to test.
See the [testing document](https://angular.io/guide/testing).
#### Depended Packages
* [NG Bootstrap](https://ng-bootstrap.github.io/) is used as UI component library.
* [NGXS](https://www.ngxs.io/) is used as state management library.
* [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) is used to support for OAuth 2 and OpenId Connect (OIDC).
* [Chart.js](https://www.chartjs.org/) is used to create widgets.
* [ngx-validate](https://github.com/ng-turkey/ngx-validate) is used for dynamic validation of reactive forms.
### React Native
The solution includes the [React Native](https://reactnative.dev/) application in the `react-native` folder as default.
The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the React Native application consumes it.
The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features.
React Native application folder structure as like below:
![react-native-folder-structure](../images/react-native-folder-structure.png)
* `App.js` is bootstrap component of the application.
* `Environment.js` file has the essential configuration of the application. `prod` and `dev` configurations defined in this file.
* [Contexts](https://reactjs.org/docs/context.html) are created in the `src/contexts` folder.
* [Higher order components](https://reactjs.org/docs/higher-order-components.html) are created in the`src/hocs` folder.
* [Custom hooks](https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook) are created in the`src/hooks`.
* [Axios interceptors](https://github.com/axios/axios#interceptors) are created in the `src/interceptors` folder.
* Utility functions are exported from `src/utils` folder.
#### Components
Components that can be used on all screens are created in the `src/components` folder. All components have created as a function that able to use [hooks](https://reactjs.org/docs/hooks-intro.html).
#### Screens
![react-native-navigation-structure](../images/react-native-navigation-structure.png)
Screens are created by creating folders that separate their names in the `src/screens` folder. Certain parts of some screens can be split into components.
Each screen is used in a navigator in the `src/navigators` folder.
#### Navigation
[React Navigation](https://reactnavigation.org/) is used as a navigation library. Navigators are created in the `src/navigators`. A [drawer](https://reactnavigation.org/docs/drawer-based-navigation/) navigator and several [stack](https://reactnavigation.org/docs/hello-react-navigation/#installing-the-stack-navigator-library) navigators have created in this folder. See the [above diagram](#screens) for navigation structure.
#### State Management
[Redux](https://redux.js.org/) is used as state management library. [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development.
Actions, reducers, sagas, selectors are created in the `src/store` folder. Store folder as like below:
![react-native-store-folder](../images/react-native-store-folder.png)
* [**Store**](https://redux.js.org/basics/store) is defined in the `src/store/index.js` file.
* [**Actions**](https://redux.js.org/basics/actions/) are payloads of information that send data from your application to your store.
* [**Reducers**](https://redux.js.org/basics/reducers) specify how the application's state changes in response to actions sent to the store.
* [**Redux-Saga**](https://redux-saga.js.org/) is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage. Sagas are created in the `src/store/sagas` folder.
* [**Reselect**](https://github.com/reduxjs/reselect) library is used to create memoized selectors. Selectors are created in the `src/store/selectors` folder.
#### APIs
[Axios](https://github.com/axios/axios) is used as an HTTP client library. An Axios instance has exported from `src/api/API.js` file to make HTTP calls with the same config. `src/api` folder also has the API files that have been created for API calls.
#### Theming
[Native Base](https://nativebase.io/) is used as UI components library. Native Base components can customize easily. See the [Native Base customize](https://docs.nativebase.io/Customize.html#Customize) documentation. We followed the same way.
* Native Base theme variables are in the `src/theme/variables` folder.
* Native Base component styles are in the `src/theme/components` folder. These files have been generated with Native Base's `ejectTheme` script.
* Styles of components override with the files under the `src/theme/overrides` folder.
#### Testing
Unit tests will be created.
* `angular` folder contains the Angular UI solution, the client side.
* `aspnet-core` folder contains the ASP.NET Core solution, the server side.
See the [Testing Overview](https://reactjs.org/docs/testing.html) document.
Server side is very similar to the solution described above. `.HttpApi.Host` project serves the API, so the Angular application can consume it.
#### Depended Libraries
The files under the `angular/src/environments` folder has the essential configuration of the application.
* [Native Base](https://nativebase.io/) is used as UI components library.
* [React Navigation](https://reactnavigation.org/) is used as navigation library.
* [Axios](https://github.com/axios/axios) is used as HTTP client library.
* [Redux](https://redux.js.org/) is used as state management library.
* [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development.
* [Redux-Saga](https://redux-saga.js.org/) is used to manage asynchronous processes.
* [Redux Persist](https://github.com/rt2zz/redux-persist) is used as state persistance.
* [Reselect](https://github.com/reduxjs/reselect) is used to create memoized selectors.
* [i18n-js](https://github.com/fnando/i18n-js) is used as i18n library.
* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) library allows loading fonts easily.
* [Formik](https://github.com/jaredpalmer/formik) is used to build forms.
* [Yup](https://github.com/jquense/yup) is used for form validations.
## 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/AspNetCore-Mvc/Part-I.md) to learn how to develop applications using this template.
- [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes.
- [The application development tutorial](../Tutorials/Part-1) explains step by step application development.

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

661
docs/en/Tutorials/Angular/Part-I.md

@ -1,659 +1,6 @@
## Angular Tutorial - Part I
# Tutorials
### About this Tutorial
## Application Development
In this tutorial series, you will build an application that is used to manage a list of books & their authors. **Angular** will be used as the UI framework and **MongoDB** will be used as the database provider.
This is the first part of the Angular tutorial series. See all parts:
- **Part I: Create the project and a book list page (this tutorial)**
- [Part II: Create, Update and Delete books](Part-II.md)
- [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
### Creating the Project
Create a new project named `Acme.BookStore` by selecting the Angular as the UI framework and MongoDB as the database provider, create the database and run the application by following the [Getting Started document](../../Getting-Started-Angular-Template.md).
### Solution Structure (Backend)
This is how the layered solution structure looks after it's created:
![bookstore-backend-solution](images/bookstore-backend-solution-v2.png)
> You can see the [Application template document](../../Startup-Templates/Application.md) to understand the solution structure in details. However, you will understand the basics with this tutorial.
### Create the Book Entity
Domain layer in the startup template is separated into two projects:
- `Acme.BookStore.Domain` contains your [entities](../../Entities.md), [domain services](../../Domain-Services.md) and other core domain objects.
- `Acme.BookStore.Domain.Shared` contains constants, enums or other domain related objects those can be shared with clients.
Define [entities](../../Entities.md) in the **domain layer** (`Acme.BookStore.Domain` project) of the solution. The main entity of the application is the `Book`. Create a class, named `Book`, in the `Acme.BookStore.Domain` project as shown below:
```C#
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore
{
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; }
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
}
}
```
- ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for details and best practices.
- `Book` entity inherits `AuditedAggregateRoot` which adds some auditing properties (`CreationTime`, `CreatorId`, `LastModificationTime`... etc.) on top of the `AggregateRoot` class.
- `Guid` is the **primary key type** of the `Book` entity.
#### BookType Enum
Define the `BookType` enum in the `Acme.BookStore.Domain.Shared` project:
```C#
namespace Acme.BookStore
{
public enum BookType
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
}
```
#### Add Book Entity to Your DbContext
Add a `IMongoCollection` property to the `BookStoreMongoDbContext` inside the `Acme.BookStore.MongoDB` project:
```csharp
public class BookStoreMongoDbContext : AbpMongoDbContext
{
public IMongoCollection<Book> Books => Collection<Book>();
...
}
```
#### Add Seed (Sample) Data
This section is optional, but it would be good to have an initial data in the database in the first run. ABP provides a [data seed system](../../Data-Seeding.md). Create a class deriving from the `IDataSeedContributor` in the `.Domain` project:
```csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookStoreDataSeederContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() > 0)
{
return;
}
await _bookRepository.InsertAsync(
new Book
{
Name = "1984",
Type = BookType.Dystopia,
PublishDate = new DateTime(1949, 6, 8),
Price = 19.84f
}
);
await _bookRepository.InsertAsync(
new Book
{
Name = "The Hitchhiker's Guide to the Galaxy",
Type = BookType.ScienceFiction,
PublishDate = new DateTime(1995, 9, 27),
Price = 42.0f
}
);
}
}
}
```
`BookStoreDataSeederContributor` simply inserts two books into database if there is no book added before. ABP automatically discovers and executes this class when you seed the database by running the `Acme.BookStore.DbMigrator` project.
### Create the Application Service
The next step is to create an [application service](../../Application-Services.md) to manage (create, list, update, delete...) the books. Application layer in the startup template is separated into two projects:
- `Acme.BookStore.Application.Contracts` mainly contains your DTOs and application service interfaces.
- `Acme.BookStore.Application` contains the implementations of your application services.
#### BookDto
Create a DTO class named `BookDto` into the `Acme.BookStore.Application.Contracts` project:
```C#
using System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore
{
public class BookDto : AuditedEntityDto<Guid>
{
public string Name { get; set; }
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
}
}
```
- **DTO** classes are used to **transfer data** between the _presentation layer_ and the _application layer_. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for more details.
- `BookDto` is used to transfer book data to the presentation layer in order to show the book information on the UI.
- `BookDto` is derived from the `AuditedEntityDto<Guid>` which has audit properties just like the `Book` class defined above.
It will be needed to convert `Book` entities to `BookDto` objects while returning books to the presentation layer. [AutoMapper](https://automapper.org) library can automate this conversion when you define the proper mapping. Startup template comes with AutoMapper configured, so you can just define the mapping in the `BookStoreApplicationAutoMapperProfile` class in the `Acme.BookStore.Application` project:
```csharp
using AutoMapper;
namespace Acme.BookStore
{
public class BookStoreApplicationAutoMapperProfile : Profile
{
public BookStoreApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
}
}
}
```
#### CreateUpdateBookDto
Create a DTO class named `CreateUpdateBookDto` into the `Acme.BookStore.Application.Contracts` project:
```c#
using System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore
{
public class CreateUpdateBookDto
{
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
public DateTime PublishDate { get; set; }
[Required]
public float Price { get; set; }
}
}
```
- This DTO class is used to get book information from the user interface while creating or updating a book.
- It defines data annotation attributes (like `[Required]`) to define validations for the properties. DTOs are [automatically validated](../../Validation.md) by the ABP framework.
Next, add a mapping in `BookStoreApplicationAutoMapperProfile` from the `CreateUpdateBookDto` object to the `Book` entity:
```csharp
CreateMap<CreateUpdateBookDto, Book>();
```
#### IBookAppService
Define an interface named `IBookAppService` in the `Acme.BookStore.Application.Contracts` project:
```C#
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
public interface IBookAppService :
ICrudAppService< //Defines CRUD methods
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books
CreateUpdateBookDto, //Used to create a new book
CreateUpdateBookDto> //Used to update a book
{
}
}
```
- Defining interfaces for application services is <u>not required</u> by the framework. However, it's suggested as a best practice.
- `ICrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead, you could inherit from the empty `IApplicationService` interface and define your own methods manually.
- There are some variations of the `ICrudAppService` where you can use separated DTOs for each method.
#### BookAppService
Implement the `IBookAppService` as named `BookAppService` in the `Acme.BookStore.Application` project:
```C#
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}
}
```
- `BookAppService` is derived from `CrudAppService<...>` which implements all the CRUD methods defined above.
- `BookAppService` injects `IRepository<Book, Guid>` which is the default repository for the `Book` entity. ABP automatically creates default repositories for each aggregate root (or entity). See the [repository document](../../Repositories.md).
- `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. The Startup template uses the [AutoMapper](http://automapper.org/) library as the object mapping provider. You defined the mappings before, so it will work as expected.
### Auto API Controllers
You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus allowing browser or 3rd-party clients to call them via AJAX. ABP can [**automagically**](../../AspNetCore/Auto-API-Controllers.md) configures your application services as MVC API Controllers by convention.
#### Swagger UI
The startup template is configured to run the [swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the `Acme.BookStore.HttpApi.Host` application and enter `https://localhost:XXXX/swagger/` (replace XXXX by your own port) as URL on your browser.
You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints:
![bookstore-swagger](images/bookstore-swagger-api.png)
Swagger has a nice UI to test APIs. You can try to execute the `[GET] /api/app/book` API to get a list of books.
### Create the Books Page
In this tutorial;
- [Angular CLI](https://angular.io/cli) will be used to create modules, components and services
- [NGXS](https://ngxs.gitbook.io/ngxs/) will be used as the state management library
- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) will be used as the UI component library.
- [Visual Studio Code](https://code.visualstudio.com/) will be used as the code editor (you can use your favorite editor).
#### Install NPM Packages
Open a terminal window and go to `angular` folder and then run `yarn` command for installing NPM packages:
```
yarn
```
#### BooksModule
Run the following command line to create a new module, named `BooksModule`:
```bash
yarn ng generate module books --route books --module app.module
```
![creating-books-module.terminal](images/bookstore-creating-books-module-terminal.png)
Run `yarn start`, wait Angular to run the application and open `http://localhost:4200/books` on a browser:
![initial-books-page](images/bookstore-initial-books-page.png)
#### Routing
Open the `app-routing.module.ts` and replace `books` as shown below:
```js
import { ApplicationLayoutComponent } from '@abp/ng.theme.basic';
//...
{
path: 'books',
component: ApplicationLayoutComponent,
loadChildren: () => import('./books/books.module').then(m => m.BooksModule),
data: {
routes: {
name: 'Books',
} as ABP.Route,
},
},
```
`ApplicationLayoutComponent` configuration sets the application layout to the new page. If you would like to see your route on the navigation bar (main menu) you must also add the `data` object with `name` property in your route.
![initial-books-page](images/bookstore-initial-books-page-with-layout.png)
#### Book List Component
First, replace the `books.component.html` to the following line to place the router-outlet:
```html
<router-outlet></router-outlet>
```
Then run the command below on the terminal in the root folder to generate a new component, named book-list:
```bash
yarn ng generate component books/book-list
```
![creating-books-list-terminal](images/bookstore-creating-book-list-terminal.png)
Import the `SharedModule` to the `BooksModule` to reuse some components and services defined in:
```js
import { SharedModule } from '../shared/shared.module';
@NgModule({
//...
imports: [
//...
SharedModule,
],
})
export class BooksModule {}
```
Then, update the `routes` in the `books-routing.module.ts` to add the new book-list component:
```js
import { BookListComponent } from './book-list/book-list.component';
const routes: Routes = [
{
path: '',
component: BooksComponent,
children: [{ path: '', component: BookListComponent }],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class BooksRoutingModule {}
```
![initial-book-list-page](images/bookstore-initial-book-list-page.png)
#### Create BooksState
Run the following command in the terminal to create a new state, named `BooksState`:
```shell
yarn ng generate ngxs-schematic:state books
```
This command creates several new files and edits `app.modules.ts` to import the `NgxsModule` with the new state:
```js
// app.module.ts
import { BooksState } from './store/states/books.state';
@NgModule({
imports: [
//...
NgxsModule.forRoot([BooksState]),
],
//...
})
export class AppModule {}
```
#### Get Books Data from Backend
First, create data types to map data returning from the backend (you can check swagger UI or your backend API to know the data format).
Modify the `books.ts` as shown below:
```js
export namespace Books {
export interface State {
books: Response;
}
export interface Response {
items: Book[];
totalCount: number;
}
export interface Book {
name: string;
type: BookType;
publishDate: string;
price: number;
lastModificationTime: string;
lastModifierId: string;
creationTime: string;
creatorId: string;
id: string;
}
export enum BookType {
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry,
}
}
```
Added `Book` interface that represents a book object and `BookType` enum represents a book category.
#### BooksService
Now, create a new service, named `BooksService` to perform HTTP calls to the server:
```bash
yarn ng generate service books/shared/books
```
![service-terminal-output](images/bookstore-service-terminal-output.png)
Modify `books.service.ts` as shown below:
```js
import { Injectable } from '@angular/core';
import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class BooksService {
constructor(private restService: RestService) {}
get(): Observable<Books.Response> {
return this.restService.request<void, Books.Response>({
method: 'GET',
url: '/api/app/book'
});
}
}
```
Added the `get` method to get the list of books by performing an HTTP request to the related endpoint.
Replace `books.actions.ts` content as shown below:
```js
export class GetBooks {
static readonly type = '[Books] Get';
}
```
#### Implement the BooksState
Open the `books.state.ts` and change the file as shown below:
```js
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
@State<Books.State>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
})
export class BooksState {
@Selector()
static getBooks(state: Books.State) {
return state.books.items || [];
}
constructor(private booksService: BooksService) {}
@Action(GetBooks)
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
ctx.patchState({
books: booksResponse,
});
}),
);
}
}
```
Added the `GetBooks` action that uses the `BookService` defined above to get the books and patch the state.
> NGXS requires to return the observable without subscribing it, as done in this sample (in the get function).
#### BookListComponent
Modify the `book-list.component.ts` as shown below:
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
booksType = Books.BookType;
loading = false;
constructor(private store: Store) {}
ngOnInit() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
}
```
> See the [Dispatching Actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions) and [Select](https://ngxs.gitbook.io/ngxs/concepts/select) on the NGXS documentation for more information on these NGXS features.
Replace `book-list.component.html` content as shown below:
```html
<div id="wrapper" class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">
Books
</h5>
</div>
</div>
</div>
<div class="card-body">
<p-table [value]="books$ | async" [loading]="loading" [paginator]="true" [rows]="10">
<ng-template pTemplate="header">
<tr>
<th>Book name</th>
<th>Book type</th>
<th>Publish date</th>
<th>Price</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-data>
<tr>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
```
> We've used [PrimeNG table](https://www.primefaces.org/primeng/#/table) in this component.
The resulting books page is shown below:
![bookstore-book-list](images/bookstore-book-list.png)
And this is the folder & file structure by the end of this tutorial:
<img src="images/bookstore-angular-file-tree.png" height="75%">
> This tutorial follows the [Angular Style Guide](https://angular.io/guide/styleguide#file-tree).
### Next Part
See the [next part](Part-II.md) of this tutorial.
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

589
docs/en/Tutorials/Angular/Part-II.md

@ -1,587 +1,6 @@
## Angular Tutorial - Part II
# Tutorials
### About this Tutorial
## Application Development
This is the second part of the Angular tutorial series. See all parts:
- [Part I: Create the project and a book list page](Part-I.md)
- **Part II: Create, Update and Delete books (this tutorial)**
- [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
### Creating a New Book
In this section, you will learn how to create a new modal dialog form to create a new book.
#### Type Definition
Create an interface, named `CreateUpdateBookInput` in the `books.ts` as shown below:
```js
export namespace Books {
//...
export interface CreateUpdateBookInput {
name: string;
type: BookType;
publishDate: string;
price: number;
}
}
```
`CreateUpdateBookInput` interface matches the `CreateUpdateBookDto` in the backend.
#### Service Method
Open the `books.service.ts` and add a new method, named `create` to perform an HTTP POST request to the server:
```js
create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'POST',
url: '/api/app/book',
body: createBookInput
});
}
```
- `restService.request` function gets generic parameters for the types sent to and received from the server. This example sends a `CreateUpdateBookInput` object and receives a `Book` object (you can set `void` for request or return type if not used).
#### State Definitions
Add the `CreateUpdateBook` action to the `books.actions.ts` as shown below:
```js
import { Books } from '../models';
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: Books.CreateUpdateBookInput) {}
}
```
Open `books.state.ts` and define the `save` method that will listen to a `CreateUpdateBook` action to create a book:
```js
import { ... , CreateUpdateBook } from '../actions/books.actions';
import { ... , switchMap } from 'rxjs/operators';
//...
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
return this.booksService
.create(action.payload)
.pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
```
When the `SaveBook` action dispatched, the save method is executed. It call `create` method of the `BooksService` defined before. After the service call, `BooksState` dispatches the `GetBooks` action to get books again from the server to refresh the page.
#### Add a Modal to BookListComponent
Open the `book-list.component.html` and add the `abp-modal` to show/hide the modal to create a new book.
```html
<abp-modal [(visible)]="isModalOpen">
<ng-template #abpHeader>
<h3>New Book</h3>
</ng-template>
<ng-template #abpBody> </ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
Cancel
</button>
</ng-template>
</abp-modal>
```
`abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
Add a button, labeled `New book` to show the modal:
```html
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">
Books
</h5>
</div>
<div class="text-right col col-md-6">
<button id="create-role" class="btn btn-primary" type="button" (click)="createBook()">
<i class="fa fa-plus mr-1"></i> <span>New book</span>
</button>
</div>
</div>
```
Open the `book-list.component.ts` and add `isModalOpen` variable and `createBook` method to show/hide the modal.
```js
isModalOpen = false;
//...
createBook() {
this.isModalOpen = true;
}
```
![empty-modal](images/bookstore-empty-new-book-modal.png)
#### Create a Reactive Form
> [Reactive forms](https://angular.io/guide/reactive-forms) provide a model-driven approach to handling form inputs whose values change over time.
Add a `form` variable and inject a `FormBuilder` service to the `book-list.component.ts` as shown below (remember add the import statement).
```js
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
form: FormGroup;
constructor(
//...
private fb: FormBuilder
) {}
```
> The [FormBuilder](https://angular.io/api/forms/FormBuilder) service provides convenient methods for generating controls. It reduces the amount of boilerplate needed to build complex forms.
Add the `buildForm` method to create book form.
```js
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
type: [null, Validators.required],
publishDate: [null, Validators.required],
price: [null, Validators.required],
});
}
```
- The `group` method of `FormBuilder` (`fb`) creates a `FormGroup`.
- Added `Validators.required` static method that validates the related form element.
Modify the `createBook` method as shown below:
```js
createBook() {
this.buildForm();
this.isModalOpen = true;
}
```
#### Create the DOM Elements of the Form
Open `book-list.component.html` and add the form in the body template of the modal.
```html
<ng-template #abpBody>
<form [formGroup]="form">
<div class="form-group">
<label for="book-name">Name</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="form-group">
<label for="book-price">Price</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="form-group">
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {%{{{ type }}}%}</option>
</select>
</div>
<div class="form-group">
<label>Publish date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="publishDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
</form>
</ng-template>
```
- This template creates a form with Name, Price, Type and Publish date fields.
> We've used [NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview) in this component.
#### Datepicker Requirements
You need to import `NgbDatepickerModule` to the `books.module.ts`:
```js
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
imports: [
// ...
NgbDatepickerModule,
],
})
export class BooksModule {}
```
Then open the `book-list.component.ts` and add `providers` as shown below:
```js
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@Component({
// ...
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
// ...
```
> The `NgbDateAdapter` converts Datepicker value to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.
#### Create the Book Type Array
Open the `book-list.component.ts` and then create an array, named `bookTypeArr`:
```js
//...
booksType = Books.BookType;
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
```
The `bookTypeArr` contains the fields of the `BookType` enum. Resulting array is shown below:
```js
['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]
```
This array was used in the previous form template (in the `ngFor` loop).
![new-book-form](images/bookstore-new-book-form.png)
#### Saving the Book
Open the `book-list.component.html` and add an `abp-button` to save the form.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
Cancel
</button>
<button class="btn btn-primary" (click)="save()">
<i class="fa fa-check mr-1"></i>
Save
</button>
</ng-template>
```
This adds a save button to the bottom area of the modal:
![bookstore-new-book-form-v2](images/bookstore-new-book-form-v2.png)
Then define a `save` method in the `BookListComponent`:
```js
//...
import { ..., CreateUpdateBook } from '../../store/actions';
//...
save() {
if (this.form.invalid) {
return;
}
this.store.dispatch(new CreateUpdateBook(this.form.value)).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
});
}
```
### Updating An Existing Book
#### BooksService
Open the `books.service.ts` and then add the `getById` and `update` methods.
```js
getById(id: string): Observable<Books.Book> {
return this.restService.request<void, Books.Book>({
method: 'GET',
url: `/api/app/book/${id}`
});
}
update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'PUT',
url: `/api/app/book/${id}`,
body: updateBookInput
});
}
```
#### CreateUpdateBook Action
Open the `books.actions.ts` and add `id` parameter to the `CreateUpdateBook` action:
```js
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: Books.CreateUpdateBookInput, public id?: string) {}
}
```
Open `books.state.ts` and modify the `save` method as show below:
```js
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
let request;
if (action.id) {
request = this.booksService.update(action.payload, action.id);
} else {
request = this.booksService.create(action.payload);
}
return request.pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
```
#### BookListComponent
Inject `BooksService` dependency by adding it to the `book-list.component.ts` constructor and add a variable named `selectedBook`.
```js
import { BooksService } from '../shared/books.service';
//...
selectedBook = {} as Books.Book;
constructor(
//...
private booksService: BooksService
)
```
`booksService` is used to get the editing book to prepare the form. Modify the `buildForm` method to reuse the same form while editing a book.
```js
buildForm() {
this.form = this.fb.group({
name: [this.selectedBook.name || '', Validators.required],
type: this.selectedBook.type || null,
publishDate: this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
price: this.selectedBook.price || null,
});
}
```
Add the `editBook` method as shown below:
```js
editBook(id: string) {
this.booksService.getById(id).subscribe(book => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
});
}
```
Added `editBook` method to get the editing book, build the form and show the modal.
Now, add the `selectedBook` definition to `createBook` method to reuse the same form while creating a new book:
```js
createBook() {
this.selectedBook = {} as Books.Book;
//...
}
```
Modify the `save` method to pass the id of the selected book as shown below:
```js
save() {
if (this.form.invalid) {
return;
}
this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
});
}
```
#### Add "Actions" Dropdown to the Table
Open the `book-list.component.html` and add modify the `p-table` as shown below:
```html
<p-table [value]="books$ | async" [loading]="loading" [paginator]="true" [rows]="10">
<ng-template pTemplate="header">
<tr>
<th>Actions</th>
<th>Book name</th>
<th>Book type</th>
<th>Publish date</th>
<th>Price</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-data>
<tr>
<td>
<div ngbDropdown class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i class="fa fa-cog mr-1"></i>Actions
</button>
<div ngbDropdownMenu>
<button ngbDropdownItem (click)="editBook(data.id)">Edit</button>
</div>
</div>
</td>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>
```
- Added a `th` for the "Actions" column.
- Added `button` with `ngbDropdownToggle` to open actions when clicked the button.
> We've used to [NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples) for the dropdown menu of actions.
The final UI looks like:
![actions-buttons](images/bookstore-actions-buttons.png)
Update the modal header to change the title based on the current operation:
```html
<ng-template #abpHeader>
<h3>{%{{{ selectedBook.id ? 'Edit' : 'New Book' }}}%}</h3>
</ng-template>
```
![actions-buttons](images/bookstore-edit-modal.png)
### Deleting an Existing Book
#### BooksService
Open `books.service.ts` and add a `delete` method to delete a book with the `id` by performing an HTTP request to the related endpoint:
```js
delete(id: string): Observable<void> {
return this.restService.request<void, void>({
method: 'DELETE',
url: `/api/app/book/${id}`
});
}
```
#### DeleteBook Action
Add an action named `DeleteBook` to `books.actions.ts`:
```js
export class DeleteBook {
static readonly type = '[Books] Delete';
constructor(public id: string) {}
}
```
Open the `books.state.ts` and add the `delete` method that will listen to the `DeleteBook` action to delete a book:
```js
import { ... , DeleteBook } from '../actions/books.actions';
//...
@Action(DeleteBook)
delete(ctx: StateContext<Books.State>, action: DeleteBook) {
return this.booksService.delete(action.id).pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
```
- Added `DeleteBook` to the import list.
- Uses `bookService` to delete the book.
#### Add a Delete Button
Open `book-list.component.html` and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>
...
<button ngbDropdownItem (click)="delete(data.id, data.name)">
Delete
</button>
</div>
```
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown.png)
#### Delete Confirmation Dialog
Open `book-list.component.ts` and inject the `ConfirmationService`.
```js
import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
//...
private confirmationService: ConfirmationService
)
```
> `ConfirmationService` is a simple service provided by ABP framework that internally uses the PrimeNG.
Add a delete method to the `BookListComponent`:
```js
import { ... , DeleteBook } from '../../store/actions';
import { ... , Toaster } from '@abp/ng.theme.shared';
//...
delete(id: string, name: string) {
this.confirmationService
.error(`${name} will be deleted. Do you confirm that?`, 'Are you sure?')
.subscribe(status => {
if (status === Toaster.Status.confirm) {
this.store.dispatch(new DeleteBook(id));
}
});
}
```
The `delete` method shows a confirmation popup and subscribes for the user response. `DeleteBook` action dispatched only if user clicks to the `Yes` button. The confirmation popup looks like below:
![bookstore-confirmation-popup](images/bookstore-confirmation-popup.png)
### Next Part
See the [next part](Part-III.md) of this tutorial.
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

180
docs/en/Tutorials/Angular/Part-III.md

@ -1,178 +1,6 @@
## Angular Tutorial - Part III
# Tutorials
### About this Tutorial
## Application Development
This is the third part of the Angular tutorial series. See all parts:
- [Part I: Create the project and a book list page](Part-I.md)
- [Part II: Create, Update and Delete books](Part-II.md)
- **Part III: Integration Tests (this tutorial)**
This part covers the **server side** tests. You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
### Test Projects in the Solution
There are multiple test projects in the solution:
![bookstore-test-projects](images/bookstore-test-projects-v3.png)
Each project is used to test the related application project. Test projects use the following libraries for testing:
* [xunit](https://xunit.github.io/) as the main test framework.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as an assertion library.
* [NSubstitute](http://nsubstitute.github.io/) as a mocking library.
### Adding Test Data
Startup template contains the `BookStoreTestDataSeedContributor` class in the `Acme.BookStore.TestBase` project that creates some data to run tests on.
Change the `BookStoreTestDataSeedContributor` class as show below:
````C#
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreTestDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreTestDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
await _bookRepository.InsertAsync(
new Book
{
Id = _guidGenerator.Create(),
Name = "Test book 1",
Type = BookType.Fantastic,
PublishDate = new DateTime(2015, 05, 24),
Price = 21
}
);
await _bookRepository.InsertAsync(
new Book
{
Id = _guidGenerator.Create(),
Name = "Test book 2",
Type = BookType.Science,
PublishDate = new DateTime(2014, 02, 11),
Price = 15
}
);
}
}
}
````
* Injected `IRepository<Book, Guid>` and used it in the `SeedAsync` to create two book entities as the test data.
* Used `IGuidGenerator` service to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases (see the [Guid generation document](../../Guid-Generation.md) for more).
### Testing the BookAppService
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:
````C#
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Xunit;
namespace Acme.BookStore
{
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "Test book 1");
}
}
}
````
* `Should_Get_List_Of_Books` test simply uses `BookAppService.GetListAsync` method to get and check the list of users.
Add a new test that creates a valid new book:
````C#
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
````
Add a new test that tries to create an invalid book and fails:
````C#
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
````
* Since the `Name` is empty, ABP throws an `AbpValidationException`.
Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests:
![bookstore-appservice-tests](images/bookstore-test-explorer.png)
Congratulations, green icons show that tests have been successfully passed!
* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC)
* [With Angular UI](../Part-1?UI=NG)

BIN
docs/en/Tutorials/Angular/images/bookstore-actions-buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-angular-file-tree.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-backend-solution-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-book-list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-creating-book-list-terminal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-creating-books-module-terminal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-edit-modal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-final-actions-dropdown.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-initial-book-list-page.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-initial-books-page-with-layout.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-initial-books-page.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-service-terminal-output.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-swagger-api.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

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

Loading…
Cancel
Save