Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into docs/diaval-angular

pull/3072/head
mehmet-erim 7 years ago
parent
commit
1b9acb5a5f
  1. 1
      .gitignore
  2. 13
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sl.json
  3. 90
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json
  4. 31
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sl.json
  5. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/sl.json
  6. 35
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json
  7. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/sl.json
  8. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/sl.json
  9. 10
      docs/cs/AspNetCore/Widgets.md
  10. 3
      docs/cs/CLI.md
  11. 2
      docs/cs/Getting-Started-Angular-Template.md
  12. 2
      docs/cs/Getting-Started-AspNetCore-MVC-Template.md
  13. 8
      docs/en/API/Dynamic-CSharp-API-Clients.md
  14. 2
      docs/en/AspNetCore/Client-Side-Package-Management.md
  15. 2
      docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md
  16. 2
      docs/en/AspNetCore/Tag-Helpers/Index.md
  17. 2
      docs/en/AspNetCore/Widgets.md
  18. 6
      docs/en/Background-Workers-Quartz.md
  19. 0
      docs/en/Background-Workers.md
  20. 1
      docs/en/CLI.md
  21. 90
      docs/en/Customizing-Application-Modules-Guide.md
  22. 95
      docs/en/Entities.md
  23. 2
      docs/en/Getting-Started-Angular-Template.md
  24. 2
      docs/en/Getting-Started-AspNetCore-MVC-Template.md
  25. 4
      docs/en/Localization.md
  26. 11
      docs/en/Module-Development-Basics.md
  27. 4
      docs/en/Multi-Tenancy.md
  28. 95
      docs/en/Samples/Microservice-Demo.md
  29. 9
      docs/en/UI/AspNetCore/Client-Side-Package-Management.md
  30. 94
      docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md
  31. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  32. BIN
      docs/en/UI/AspNetCore/Tag-Helpers/fa-address-card.png
  33. 16
      docs/en/UI/AspNetCore/Widgets.md
  34. 27
      docs/en/docs-nav.json
  35. BIN
      docs/en/images/microservice-sample-diagram-3.png
  36. BIN
      docs/en/images/microservice-sample-solution-2.png
  37. 4
      docs/pt-BR/Getting-Started-Angular-Template.md
  38. 2
      docs/pt-BR/Getting-Started-AspNetCore-MVC-Template.md
  39. 4
      docs/pt-BR/Localization.md
  40. 4
      docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md
  41. 6
      docs/zh-Hans/AspNetCore/Dynamic-CSharp-API-Clients.md
  42. 94
      docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md
  43. 2
      docs/zh-Hans/AspNetCore/Tag-Helpers/Index.md
  44. BIN
      docs/zh-Hans/AspNetCore/Tag-Helpers/fa-address-card.png
  45. 10
      docs/zh-Hans/AspNetCore/Widgets.md
  46. 3
      docs/zh-Hans/CLI.md
  47. 93
      docs/zh-Hans/Entities.md
  48. 21
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  49. 4
      docs/zh-Hans/Exception-Handling.md
  50. 2
      docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md
  51. 2
      docs/zh-Hans/Localization.md
  52. 2
      docs/zh-Hans/Module-Development-Basics.md
  53. 4
      docs/zh-Hans/Multi-Tenancy.md
  54. 14
      docs/zh-Hans/docs-nav.json
  55. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSize.cs
  56. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSizeExtensions.cs
  57. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/sl.json
  58. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NugetPackageToLocalReferenceConverter.cs
  59. 23
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/sl.json
  60. 7
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/sl.json
  61. 6
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/sl.json
  62. 2
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/EntityChange.cs
  63. 5
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAuthorizationHandler.cs
  64. 5
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAuthorizationHandler.cs
  65. 3
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json
  66. 3
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json
  67. 2
      modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/CommentsController.cs
  68. 22
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml
  69. 3
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs
  70. 32
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/detail.js
  71. 3
      modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj
  72. 6
      modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs
  73. 5
      modules/docs/app/VoloDocs.Web/appsettings.json
  74. 2
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs
  75. 22
      modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs
  76. 7
      modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs
  77. 19
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchInput.cs
  78. 24
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchOutput.cs
  79. 5
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs
  80. 32
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs
  81. 7
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/DocsDomainErrorCodes.cs
  82. 1
      modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj
  83. 16
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs
  84. 27
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DefaultElasticClientProvider.cs
  85. 15
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocsElasticSearchOptions.cs
  86. 49
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocumentChangedEventHandler.cs
  87. 189
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs
  88. 29
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/EsDocument.cs
  89. 20
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IDocumentFullSearch.cs
  90. 9
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IElasticClientProvider.cs
  91. 2
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json
  92. 2
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json
  93. 1
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json
  94. 17
      modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs
  95. 3
      modules/docs/src/Volo.Docs.Web/DocsWebModule.cs
  96. 18
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  97. 3
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs
  98. 10
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js
  99. 67
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml
  100. 73
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs

1
.gitignore

@ -292,3 +292,4 @@ samples/MicroserviceDemo/microservices/BloggingService.Host/Logs/logs.txt
samples/MicroserviceDemo/applications/ConsoleClientDemo/Logs/logs.txt
modules/docs/app/Volo.DocsTestApp/Logs/logs.txt
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Logs/logs.txt
samples/MicroserviceDemo/microservices/TenantManagementService.Host/Logs/logs.txt

13
abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sl.json

@ -0,0 +1,13 @@
{
"culture": "sl",
"texts": {
"Account": "Račun",
"Welcome": "Dobrodošli",
"UseOneOfTheFollowingLinksToContinue": "Za nadaljevanje uporabite eno od naslednjih povezav",
"FrameworkHomePage": "Domača stran razvojnega okolja",
"FrameworkDocumentation": "Dokumentacija razvojnega okolja",
"OfficialBlog": "Uradni blog",
"CommercialHomePage": "Domača stran različice Commercial",
"CommercialSupportWebSite": "Spletna stran za komercialno podporo"
}
}

90
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json

@ -0,0 +1,90 @@
{
"culture": "sl",
"texts": {
"Permission:Organizations": "Organizacije",
"Permission:Manage": "Upravljaj organizacije",
"Permission:NpmPackages": "NPM paketi",
"Permission:NugetPackages": "Nuget paketi",
"Permission:Maintenance": "Vzdrževanje",
"Permission:Maintain": "Vzdrževanje",
"Permission:ClearCaches": "Počisti predpomnilnike",
"Permission:Modules": "Moduli",
"Permission:Packages": "Paketi",
"Permission:Edit": "Urejanje",
"Permission:Delete": "Brisanje",
"Permission:Create": "Ustvarjanje",
"Menu:Organizations": "Organizacije",
"Menu:Packages": "Paketi",
"NpmPackageDeletionWarningMessage": "Ta paket NPM bo izbrisan. Ali to potrjujete?",
"NugetPackageDeletionWarningMessage": "Ta Nuget paket bo izbrisan. Ali to potrjujete?",
"ModuleDeletionWarningMessage": "Ta modul bo izbrisan. Ali to potrjujete?",
"Name": "Naziv",
"DisplayName": "Naziv za prikaz",
"ShortDescription": "Kratek opis",
"NameFilter": "Naziv",
"CreationTime": "Čas nastanka",
"IsPro": "Je pro",
"EfCoreConfigureMethodName": "Konfiguriraj ime metode",
"IsProFilter": "Je pro",
"ApplicationType": "Tip aplikacije",
"Target": "Cilj",
"TargetFilter": "Cilj",
"ModuleClass": "Razred modula",
"NugetPackageTarget.DomainShared": "Domain Shared",
"NugetPackageTarget.Domain": "Domain",
"NugetPackageTarget.Application": "Application",
"NugetPackageTarget.ApplicationContracts": "Application Contracts",
"NugetPackageTarget.HttpApi": "Http Api",
"NugetPackageTarget.HttpApiClient": "Http Api Client",
"NugetPackageTarget.Web": "Web",
"NugetPackageTarget.EntityFrameworkCore": "DeleteAllEntityFramework Core",
"NugetPackageTarget.MongoDB": "MongoDB",
"Edit": "Uredi",
"Delete": "Izbriši",
"Refresh": "Osveži",
"NpmPackages": "NPM paketi",
"NugetPackages": "Nuget paketi",
"NpmPackageCount": "Število NPM paketov",
"NugetPackageCount": "Število Nuget paketov",
"Module": "Moduli",
"ModuleInfo": "Informacije o modulu",
"CreateANpmPackage": "Ustvari NPM paket",
"CreateAModule": "Ustvari modul",
"CreateANugetPackage": "Ustvari Nuget paket",
"AddNew": "Dodaj novega",
"PackageAlreadyExist{0}": "Paket \"{0}\" je že dodan.",
"ModuleAlreadyExist{0}": "Modul \"{0}\" je že dodan.",
"ClearCache": "Počisti predpomnilnik",
"SuccessfullyCleared": "Uspešno izbrisano",
"Menu:NpmPackages": "NPM paketi",
"Menu:Modules": "Moduli",
"Menu:Maintenance": "Vzdrževanje",
"Menu:NugetPackages": "Nuget paketi",
"CreateAnOrganization": "Ustvari organizacijo",
"Organizations": "Organizacije",
"LongName": "Dolg naziv",
"LicenseType": "Tip licence",
"LicenseStartTime": "Čas začetka licence",
"LicenseEndTime": "Čas konca licence",
"AllowedDeveloperCount": "Dovoljeno število razvijalcev",
"UserNameOrEmailAddress": "Uporabniško ime ali e-poštni naslov",
"AddOwner": "Dodaj lastnika",
"UserName": "Uporabniško ime",
"Email": "E-poštni naslov",
"Developers": "Razvijalci",
"AddDeveloper": "Dodaj razvijalca",
"Create": "Ustvari",
"UserNotFound": "Uporabnika ni mogoče najti",
"{0}WillBeRemovedFromMembers": "{0} bo odstranjen iz članov",
"Computers": "Računalniki",
"UniqueComputerId": "Unikatni id računalnika",
"LastSeenDate": "Zadnjič viden",
"{0}Computer{1}WillBeRemovedFromRecords": "Računalnik {0} ({1}) bo odstranjen iz zapisov",
"OrganizationDeletionWarningMessage": "Organizacija bo izbrisana",
"This{0}AlreadyExistInThisOrganization": "{0} že obstaja v tej organizaciji",
"AreYouSureYouWantToDeleteAllComputers": "Ali ste prepričani, da želite izbrisati vse računalnike?",
"DeleteAll": "Izbriši vse",
"DoYouWantToCreateNewUser": "Ali želite ustvariti novega uporabnika?",
"MasterModules": "Glavni moduli"
}
}

31
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sl.json

@ -0,0 +1,31 @@
{
"culture": "sl",
"texts": {
"Volo.AbpIo.Domain:010004": "Doseženo je največje število članov!",
"Volo.AbpIo.Domain:010005": "Doseženo je največje število lastnikov!",
"Volo.AbpIo.Domain:010006": "Ta uporabnik je že lastnik te organizacije!",
"Volo.AbpIo.Domain:010007": "Ta uporabnik je že razvijalec v tej organizaciji!",
"Volo.AbpIo.Domain:010008": "Dovoljeno število razvijalcev ne sme biti manjše od trenutnega števila razvijalcev!",
"Volo.AbpIo.Domain:010009": "Dovoljeno število razvijalcev ne sme biti manjše od 0!",
"Volo.AbpIo.Domain:010010": "Največje število mac naslovov je preseženo!",
"Volo.AbpIo.Domain:010011": "Osebne licence ne sme imeti več kot 1 razvijalec!",
"Volo.AbpIo.Domain:010012": "Licence ni mogoče podaljšati en mesec po poteku licence!",
"Volo.AbpIo.Domain:020001": "Tega paketa NPM ni mogoče izbrisati, ker so \ "{NugetPackages} \" Nuget paketi odvisni od tega paketa.",
"Volo.AbpIo.Domain:020002": "Tega paketa NPM ni mogoče izbrisati, ker \ "{Modules} \" moduli uporabljajo ta paket.",
"Volo.AbpIo.Domain:020003": "Tega paketa NPM ni mogoče izbrisati, ker \ "{Modules} \" moduli uporabljajo ta paket in \ "{NugetPackages} \" Nuget paketi so odvisni od tega paketa.",
"Volo.AbpIo.Domain:020004": "Nuget paketa ni mogoče izbrisati, ker \ "{Modules} \" moduli uporabljajo ta paket.",
"WantToLearn?": "Se želite naučiti?",
"ReadyToGetStarted?": "Pripravljeni, da bi začeli?",
"JoinOurCommunity": "Pridružite se naši skupnosti",
"GetStartedUpper": "ZAČNI",
"ForkMeOnGitHub": "Naredi vejitev na GitHub",
"Features": "Funkcionalnosti",
"GetStarted": "Začni",
"Documents": "Dokumenti",
"Community": "Skupnost",
"ContributionGuide": "Vodič za prispevke",
"Blog": "Blog",
"Commercial": "Commercial",
"SeeDocuments": "Poglej dokumente"
}
}

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

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

35
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json

@ -0,0 +1,35 @@
{
"culture": "sl",
"texts": {
"OrganizationManagement": "Upravljanje organizacije",
"OrganizationList": "Seznam organizacij",
"Volo.AbpIo.Commercial:010003": "Niste lastnik te organizacije!",
"OrganizationNotFoundMessage": "Nobene organizacije ni bilo mogoče najti!",
"DeveloperCount": "Dodeljeni / skupno število razvijalcev",
"QuestionCount": "Preostala / skupno število vprašanj",
"Unlimited": "Neomejeno",
"Owners": "Lastniki",
"AddMember": "Dodaj člana",
"AddOwner": "Dodaj lastnika",
"AddDeveloper": "Dodaj razvijalce",
"UserName": "Uporabniško ime",
"Name": "Ime",
"EmailAddress": "E-poštni naslov",
"Developers": "Razvijalci",
"LicenseType": "Tip licence",
"Manage": "Upravljaj",
"StartDate": "Datum začetka",
"EndDate": "Datum konca",
"Modules": "Moduli",
"LicenseExtendMessage": "Končni datum veljavnosti licence je podaljšan na {0}",
"LicenseUpgradeMessage": "Vaša licenca je nadgrajena na {0}",
"LicenseAddDeveloperMessage": "{0} razvijalcev je bilo dodanih k vaši licenci",
"Volo.AbpIo.Commercial:010004": "Navedenega uporabnika ni mogoče najti! Uporabnik mora biti registriran.",
"MyOrganizations": "Moje organizacije",
"ApiKey": "Ključ API",
"UserNameNotFound": "Ni uporabnika z uporabniškim imenom {0}",
"SuccessfullyAddedToNewsletter": "Hvala, ker ste se naročili na naše novice!",
"ManageProfile": "Upravljaj svoj profil",
"EmailNotValid": "Vnesite veljaven e-poštni naslov."
}
}

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

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

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

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

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

2
docs/cs/Getting-Started-Angular-Template.md

@ -29,7 +29,7 @@ Vytvořené řešení vyžaduje;
* [Visual Studio 2019 (v16.4.0+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://yarnpkg.com/)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
### Struktura řešení

2
docs/cs/Getting-Started-AspNetCore-MVC-Template.md

@ -29,7 +29,7 @@ Vytvořené řešení vyžaduje;
* [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í

8
docs/en/API/Dynamic-CSharp-API-Clients.md

@ -1,5 +1,3 @@
# 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
@ -106,12 +104,12 @@ While you can inject `IBookAppService` like above to use the client proxy, you c
### RemoteServiceOptions
`RemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example:
`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<RemoteServiceOptions>(options =>
context.Services.Configure<AbpRemoteServiceOptions>(options =>
{
options.RemoteServices.Default =
new RemoteServiceConfiguration("http://localhost:53929/");
@ -147,7 +145,7 @@ context.Services.AddHttpClientProxies(
);
````
`remoteServiceName` parameter matches the service endpoint configured via `RemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint.
`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

2
docs/en/AspNetCore/Client-Side-Package-Management.md

@ -1,4 +1,4 @@
This document has moved.
[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md)
[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md)

2
docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md

@ -1,3 +1,3 @@
This document has moved.
[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md)
[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md)

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

@ -1,3 +1,3 @@
This document has moved.
[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md)
[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md)

2
docs/en/AspNetCore/Widgets.md

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

6
docs/en/Background-Workers-Quartz.md

@ -38,7 +38,7 @@ public class YourModule : AbpModule
### Configuration
See [Configuration](Background-Jobs-Quartz.md#Configuration).
See [Configuration](Background-Jobs-Quartz#Configuration).
### Create a Background Worker
@ -61,8 +61,8 @@ public class MyLogWorker : QuartzBackgroundWorkerBase
}
````
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.md#DependencyInterfaces) to register it as another life cycle.
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.
Please see Quartz's [documentation](https://www.quartz-scheduler.net/documentation/index.html) for more information.

0
docs/en/Background-Worker.md → docs/en/Background-Workers.md

1
docs/en/CLI.md

@ -109,6 +109,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

90
docs/en/Customizing-Application-Modules-Guide.md

@ -0,0 +1,90 @@
# Customizing the Existing Modules
ABP Framework provides was designed to support to build fully [modular applications](Module-Development-Basics.md) and systems. It also provides some [pre-built application modules](Modules/Index.md) those are **ready to use** in any kind of application.
For example, you can **re-use** the [Identity Management Module](Modules/Identity.md) to add user, role and permission management to your application. The [application startup template](Startup-Templates/Application.md) already comes with Identity and some other modules **pre-installed**.
## Re-Using an Application Module
You have two options to re-use an application module.
### As Package References
You can add **NuGet** & **NPM** package references of the related module to your application and configure the module (based on its documentation) to integrate to your application.
As mentioned before, the [application startup template](Startup-Templates/Application.md) already comes with some **fundamental modules pre-installed**. It uses the modules as NuGet & NPM package references.
This approach has the following benefits:
* Your solution will be **clean** and only contains your **own application code**.
* You can **easily upgrade** a module when a new version is available. `abp update` [CLI](CLI.md) command makes it even easier. In this way, you can continue to get **new features and bug fixes**.
However, there is a drawback:
* You may not able to **customize** the module source code as it is in your own solution.
This document explains **how to customize or extend** a depended module without need to change its source code. While it is limited compared to a full source code change opportunity, there are still some good ways to make some customizations.
If you don't think to make huge changes on the pre-built modules, re-using them as package reference is the recommended way.
### Including the Source Code
If you want to make **huge changes** or add **major features** on a pre-built module, but the available extension points are not enough, you can consider to directly work the source code of the depended module.
In this case, you typically **add the source code** of the module to your solution and **replace package references** by local project references. **[ABP CLI](CLI.md)** automates this process for you.
#### Separating the Module Solution
You may prefer to not include the module source code **directly into your solution**. Every module consists of 10+ project files and adding **multiple modules** may impact on the **size** of your solution **load & development time.** Also, you may have different development teams working on different modules, so you don't want to make the module code available to the application development team.
In any case, you can create a **separate solution** for the desired module and depend on the module as project references out of the solution. We do it like that for the [abp repository](https://github.com/abpframework/abp/).
> One problem we see is Visual Studio doesn't play nice with this kind of approach (it doesn't support well to have references to local projects out of the solution directory). If you get error while building the application (depends on an external module), run `dotnet restore` in the command line after opening the application's solution in the Visual Studio.
#### Publishing the Customized Module as Packages
One alternative scenario could be re-packaging the module source code (as NuGet/NPM packages) and using as package references. You can use a local private NuGet/NPM server for your company.
## Module Customization / Extending Approaches
This section suggests some approaches if you decided to use pre-built application modules as NuGet/NPM package references.
### Extending Entities
In some cases, you may want to add some additional properties (and database fields) for an entity defined in a depended module. This section will cover some different approaches to make this possible.
#### Extra Properties
[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these entities.
Example:
````csharp
//SET AN EXTRA PROPERTY
var user = await _identityUserRepository.GetAsync(userId);
user.SetProperty("Title", "My custom title value!");
await _identityUserRepository.UpdateAsync(user);
//GET AN EXTRA PROPERTY
var user = await _identityUserRepository.GetAsync(userId);
return user.GetProperty<string>("Title");
````
This approach is very easy to use and available out of the box. No extra code needed. You can store more than one property at the same time by using different property names (like `Title` here).
Extra properties are stored as a single `JSON` formatted string value in the database for the EF Core. For MongoDB, they are stored as separate fields of the document.
See the [entities document](Entities.md) for more about the extra properties system.
> It is possible to perform a **business logic** based on the value of an extra property. You can **override** a service method and get or set the value as shown above. Overriding services will be discussed below.
#### Creating a New Entity Maps to the Same Database Table/Collection
While using the extra properties approach is easy to use and suitable for some scenarios, it has some drawbacks described in the [entities document](Entities.md).
Another approach can be creating your own entity mapped to the same database table (or collection for a MongoDB database).
`AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage 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

95
docs/en/Entities.md

@ -295,6 +295,99 @@ All these base classes also have non-generic versions to take `AuditedEntity` an
All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser<TUser>` and`FullAuditedAggregateRootWithUser<TKey, TUser>`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern).
## Extra Properties
ABP defines the `IHasExtraProperties` interface that can be implemented by an entity to be able to dynamically set and get properties for the entity. `AggregateRoot` base class already implements the `IHasExtraProperties` interface. If you've derived from this class (or one of the related audit class defined above), you can directly use the API.
### GetProperty & SetProperty Extension Methods
These extension methods are the recommended way to get and set data for an entity. Example:
````csharp
public class ExtraPropertiesDemoService : ITransientDependency
{
private readonly IIdentityUserRepository _identityUserRepository;
public ExtraPropertiesDemoService(IIdentityUserRepository identityUserRepository)
{
_identityUserRepository = identityUserRepository;
}
public async Task SetTitle(Guid userId, string title)
{
var user = await _identityUserRepository.GetAsync(userId);
//SET A PROPERTY
user.SetProperty("Title", title);
await _identityUserRepository.UpdateAsync(user);
}
public async Task<string> GetTitle(Guid userId)
{
var user = await _identityUserRepository.GetAsync(userId);
//GET A PROPERTY
return user.GetProperty<string>("Title");
}
}
````
* Property's **value is object** and can be any type of object (string, int, bool... etc).
* `GetProperty` returns `null` if given property was not set before.
* You can store more than one property at the same time by using different **property names** (like `Title` here).
It would be a good practice to **define a constant** for the property name to prevent typo errors. It would be even a better practice to **define extension methods** to take the advantage of the intellisense. Example:
````csharp
public static class IdentityUserExtensions
{
private const string TitlePropertyName = "Title";
public static void SetTitle(this IdentityUser user, string title)
{
user.SetProperty(TitlePropertyName, title);
}
public static string GetTitle(this IdentityUser user)
{
return user.GetProperty<string>(TitlePropertyName);
}
}
````
Then you can directly use `user.SetTitle("...")` and `user.GetTitle()` for an `IdentityUser` object.
### HasProperty & RemoveProperty Extension Methods
* `HasProperty` is used to check if the object has a property set before.
* `RemoveProperty` is used to remove a property from the object. You can use this instead of setting a `null` value.
### How it is Implemented?
`IHasExtraProperties` interface requires to define a `Dictionary<string, object>` property, named `ExtraProperties`, for the implemented class.
So, you can directly use the `ExtraProperties` property to use the dictionary API, if you like. However, `SetProperty` and `GetProperty` methods are the recommended ways since they also check for `null`s.
#### How is it Stored?
The way to store this dictionary in the database depends on the database provider you're using.
* For [Entity Framework Core](Entity-Framework-Core.md), it is stored in a single `ExtraProperties` field as a `JSON` string. Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core.
* For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system.
### Discussion for the Extra Properties
Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way. You normally **don't need** to this system for your own entities, because it has the following drawbacks:
* It is **not fully type safe**.
* It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects.
* It **doesn't create fields** in the database table for EF Core, so it will not be easy to create indexes or search/order by this field in the database side.
### Extra Properties Behind Entities
`IHasExtraProperties` is not restricted to be used with entities. You can implement this interface for any kind of class and use the `GetProperty`, `SetProperty` and other related methods.
## See Also
* [Best practice guide to design the entities](Best-Practices/Entities.md)
* [Best practice guide to design the entities](Best-Practices/Entities.md)

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

@ -29,7 +29,7 @@ The created solution requires;
* [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

2
docs/en/Getting-Started-AspNetCore-MVC-Template.md

@ -29,7 +29,7 @@ The created solution requires;
* [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

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

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 = "......";
});

4
docs/en/Multi-Tenancy.md

@ -168,7 +168,7 @@ namespace MyCompany.MyProject
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<ConfigurationTenantStoreOptions>(options =>
Configure<AbpDefaultTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
@ -214,7 +214,7 @@ namespace MyCompany.MyProject
{
var configuration = BuildConfiguration();
Configure<ConfigurationTenantStoreOptions>(configuration);
Configure<AbpDefaultTenantStoreOptions>(configuration);
}
private static IConfigurationRoot BuildConfiguration()

95
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.
@ -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.

9
docs/en/UI/AspNetCore/Client-Side-Package-Management.md

@ -1,11 +1,8 @@
## ASP.NET Core MVC Client Side Package Management
ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually.
However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn.
Finally, we suggest the [**Yarn**](https://yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM.
Finally, we suggest the [**Yarn**](https://classic.yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM.
### @ABP NPM Packages
@ -41,7 +38,7 @@ After depending on a NPM package, all you should do is to run the **yarn** comma
yarn
````
Alternatively, you can use `npm install` but [Yarn](https://yarnpkg.com/) is suggested as mentioned before.
Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before.
#### Package Contribution
@ -113,4 +110,4 @@ When you run the `gulp`, all packages will copy their own resources into the **w
#### See Also
* [Bundling & Minification](Bundling-Minification.md)
* [Theming](Theming.md)
* [Theming](Theming.md)

94
docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md

@ -0,0 +1,94 @@
# Buttons
ABP framework has a special Tag Helper to create bootstrap button easily.
`<abp-button>`
## Attributes
`<abp-button>` has 7 different attribute.
* [`button-type`](#button-type)
* [`size`](#size)
* [`busy-text`](#busy-text)
* [`text`](#text)
* [`icon`](#icon)
* [`disabled`](#disabled)
* [`icon-type`](#icon-type)
### `button-type`
`button-type` is a selectable parameter. It's default value is `Default`.
`<abp-button button-type="Primary">Button</abp-button>`
You can choose one of the button type listed below.
* `Default`
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
* `Outline_Primary`
* `Outline_Secondary`
* `Outline_Success`
* `Outline_Danger`
* `Outline_Warning`
* `Outline_Info`
* `Outline_Light`
* `Outline_Dark`
* `Link`
### `size`
`size` is a selectable parameter. It's default value is `Default`.
`<abp-button size="Default">Button</abp-button>`
You can choose one of the size type listed below.
* `Default`
* `Small`
* `Medium`
* `Large`
* `Block`
* `Block_Small`
* `Block_Medium`
* `Block_Large`
### `busy-text`
`busy-text` is a string parameter. It shows the text while the button is busy.
### `text`
`text` is a string parameter that displaying on button.
### `icon`
`icon` is a string parameter. It is depending to [`icon-type`](#`icon-type`). For default, we use [Font Awesome](https://fontawesome.com/) for icons. To use it, you need to set `icon` parameter as a icon name.
##### Example
[fa-address-card](https://fontawesome.com/icons/address-card): ![fa-address-card](fa-address-card.png "Address Card")
`<abp-button icon="address-card" text="Address" />`
> Don't forget: You dont need to write prefix! It will add automatically "fa" prefix for [Font Awesome](https://fontawesome.com/) icons while you did not change `icon-type`.
### `disabled`
`disabled` is a boolean parameter. If you set it `true`, your button will be disabled.
### `icon-type`
`icon-type` is a selectable parameter. It's default value is `FontAwesome`. You can create your own icon type provider and change it.
You can choose one of the icon type listed below.
* `FontAwesome`
* `Other`

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

@ -1,3 +1,3 @@
## ABP Tag Helpers
"ABP tag helpers" is not documented yet. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now.
"ABP tag helpers" documentation is creating now. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now.

BIN
docs/en/UI/AspNetCore/Tag-Helpers/fa-address-card.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

16
docs/en/UI/AspNetCore/Widgets.md

@ -1,7 +1,3 @@
# Widgets
ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are especially useful when you want to;
* Have **scripts & styles** dependencies for your widget.
* Create **dashboards** with widgets used inside.
* Define widgets in reusable **[modules](../Module-Development-Basics.md)**.
@ -13,7 +9,7 @@ ABP provides a model and infrastructure to create **reusable widgets**. Widget s
As the first step, create a new regular ASP.NET Core View Component:
![widget-basic-files](../../images/widget-basic-files.png)
![widget-basic-files](../images/widget-basic-files.png)
**MySimpleWidgetViewComponent.cs**:
@ -478,19 +474,19 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
## WidgetOptions
As alternative to the `Widget` attribute, you can use the `WidgetOptions` to configure widgets:
As alternative to the `Widget` attribute, you can use the `AbpWidgetOptions` to configure widgets:
```csharp
Configure<WidgetOptions>(options =>
Configure<AbpWidgetOptions>(options =>
{
options.Widgets.Add<MySimpleWidgetViewComponent>();
});
```
Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `WidgetOptions`. Example configuration that adds a style for the widget:
Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `AbpWidgetOptions`. Example configuration that adds a style for the widget:
````csharp
Configure<WidgetOptions>(options =>
Configure<AbpWidgetOptions>(options =>
{
options.Widgets
.Add<MySimpleWidgetViewComponent>()
@ -498,7 +494,7 @@ Configure<WidgetOptions>(options =>
});
````
> 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`.
> Tip: `AbpWidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`.
## See Also

27
docs/en/docs-nav.json

@ -54,8 +54,17 @@
]
},
{
"text": "ASP.NET Boilerplate Migration Guide",
"path": "AspNet-Boilerplate-Migration-Guide.md"
"text": "Guides",
"items": [
{
"text": "Customizing the Application Modules",
"path": "Customizing-Application-Modules-Guide.md"
},
{
"text": "Migrating from the ASP.NET Boilerplate",
"path": "AspNet-Boilerplate-Migration-Guide.md"
}
]
},
{
"text": "CLI",
@ -219,7 +228,8 @@
"text": "Domain Services"
},
{
"text": "Specifications"
"text": "Specifications",
"path": "Specifications.md"
}
]
},
@ -269,7 +279,16 @@
},
{
"text": "Tag Helpers",
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
"items": [
{
"text": "Live Demo",
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
},
{
"text": "Buttons",
"path": "UI/AspNetCore/Tag-Helpers/Buttons.md"
}
]
},
{
"text": "Widgets",

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

4
docs/pt-BR/Getting-Started-Angular-Template.md

@ -29,7 +29,7 @@ A solução criada requer;
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://yarnpkg.com/)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
### A Estrutura da Solução
@ -103,7 +103,7 @@ A maioria das APIs de aplicativos requer autenticação e autorização. Se voc
#### Execute o aplicativo angular (lado do cliente)
Vá para a `angular`pasta, abra um terminal de linha de comando, digite o `yarn`comando (sugerimos ao gerenciador de pacotes do [yarn](https://yarnpkg.com/) enquanto o npm install também funcionará na maioria dos casos):
Vá para a `angular`pasta, abra um terminal de linha de comando, digite o `yarn`comando (sugerimos ao gerenciador de pacotes do [yarn](https://classic.yarnpkg.com/) enquanto o npm install também funcionará na maioria dos casos):
```bash
yarn

2
docs/pt-BR/Getting-Started-AspNetCore-MVC-Template.md

@ -29,7 +29,7 @@ A solução criada requer;
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://yarnpkg.com/)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
### A Estrutura da Solução

4
docs/pt-BR/Localization.md

@ -46,7 +46,7 @@ public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<VirtualFileSystemOptions>(options =>
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>();
});
@ -66,7 +66,7 @@ Neste exemplo;
- Adicionado um novo recurso de localização com "en" (inglês) como a cultura padrão.
- Arquivos JSON usados para armazenar as sequências de localização.
- Os arquivos JSON são incorporados ao assembly usando `VirtualFileSystemOptions`(consulte [sistema de arquivos virtual](Virtual-File-System.md) ).
- Os arquivos JSON são incorporados ao assembly usando `AbpVirtualFileSystemOptions`(consulte [sistema de arquivos virtual](Virtual-File-System.md) ).
Os arquivos JSON estão localizados na pasta do projeto "/ Localização / Recursos / Teste", como mostrado abaixo:

4
docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md

@ -5,7 +5,7 @@ ABP框架可以与任何类型的客户端包管理系统一起使用. 甚至你
但是, ABP框架最适用于**NPM/Yarn**. 默认情况下,内置模块配置为与NPM/Yarn一起使用.
最后, 我们建议[**Yarn**](https://yarnpkg.com/)而不是NPM,因为它更快,更稳定并且与NPM兼容.
最后, 我们建议[**Yarn**](https://classic.yarnpkg.com/)而不是NPM,因为它更快,更稳定并且与NPM兼容.
### @ABP NPM Packages
@ -41,7 +41,7 @@ ABP是一个模块化平台. 每个开发人员都可以创建模块, 模块应
yarn
````
虽然你可以使用`npm install`,但如前所述,建议使用[Yarn](https://yarnpkg.com/).
虽然你可以使用`npm install`,但如前所述,建议使用[Yarn](https://classic.yarnpkg.com/).
#### 贡献包

6
docs/zh-Hans/AspNetCore/Dynamic-CSharp-API-Clients.md

@ -106,12 +106,12 @@ public class MyService : ITransientDependency
### RemoteServiceOptions
默认情况下`RemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如:
默认情况下`AbpRemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Configure<RemoteServiceOptions>(options =>
context.Services.Configure<AbpRemoteServiceOptions>(options =>
{
options.RemoteServices.Default =
new RemoteServiceConfiguration("http://localhost:53929/");
@ -147,7 +147,7 @@ context.Services.AddHttpClientProxies(
);
````
`remoteServiceName`参数会匹配通过`RemoteServiceOptions`配置的服务端点.如果`BookStore`端点没有定义就会使用默认的`Default`端点.
`remoteServiceName`参数会匹配通过`AbpRemoteServiceOptions`配置的服务端点.如果`BookStore`端点没有定义就会使用默认的`Default`端点.
### 作为默认服务

94
docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md

@ -0,0 +1,94 @@
# 按钮
ABP框架定义了Tag Helper用于简单的创建bootstrap按钮.
`<abp-button>`
## 属性
`<abp-button>` 有7个不同的属性.
* [`button-type`](#button-type)
* [`size`](#size)
* [`busy-text`](#busy-text)
* [`text`](#text)
* [`icon`](#icon)
* [`disabled`](#disabled)
* [`icon-type`](#icon-type)
### `button-type`
`button-type` 是一个可选参数. 它的默认值是 `Default`.
`<abp-button button-type="Primary">Button</abp-button>`
你可以为按钮选择以下按钮类型:
* `Default`
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
* `Outline_Primary`
* `Outline_Secondary`
* `Outline_Success`
* `Outline_Danger`
* `Outline_Warning`
* `Outline_Info`
* `Outline_Light`
* `Outline_Dark`
* `Link`
### `size`
`size` 是一个可选参数. 它的默认值是 `Default`.
`<abp-button size="Default">Button</abp-button>`
你可以为按钮选择以下size类型:
* `Default`
* `Small`
* `Medium`
* `Large`
* `Block`
* `Block_Small`
* `Block_Medium`
* `Block_Large`
### `busy-text`
`busy-text` 是一个字符串类型参数. 当按钮繁忙时设置该文本.
### `text`
`text` 是一个字符串类型参数,显示在按钮上.
### `icon`
`icon` 是一个字符串类型参数. 它的值取决于[`icon-type`](#`icon-type`). 默认情况下,我们对图标使用[Font Awesome](https://fontawesome.com/). 要使用它,你需要将 `icon` 参数设置为图标名称.
##### 示例
[fa-address-card](https://fontawesome.com/icons/address-card): ![fa-address-card](fa-address-card.png "Address Card")
`<abp-button icon="address-card" text="Address" />`
> 不要忘记: 你不需要写前缀,如果你没有更改 `icon-type` ,它会为[Font Awesome](https://fontawesome.com/)图标自动添加 `fa` 前缀.
### `disabled`
`disabled` 是一个布尔类型参数. 如果你设值为 `true`, 按钮会被禁用.
### `icon-type`
`icon-type` 是一个可选参数。它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它.
你可以为按钮选择以下图标类型:
* `FontAwesome`
* `Other`

2
docs/zh-Hans/AspNetCore/Tag-Helpers/Index.md

@ -1,3 +1,3 @@
## ABP Tag Helpers
"ABP tag helpers" 目前还没有文档. 你现在可以看到[组件演示](http://bootstrap-taghelpers.abp.io/).
"ABP tag helpers" 文档还在创建中. 你现在可以参阅[组件演示](http://bootstrap-taghelpers.abp.io/).

BIN
docs/zh-Hans/AspNetCore/Tag-Helpers/fa-address-card.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

10
docs/zh-Hans/AspNetCore/Widgets.md

@ -251,19 +251,19 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
## 部件选项
`WidgetOptions``Widget` attribute 替代, 你可以使用它去配置部件:
`AbpWidgetOptions``Widget` attribute 替代, 你可以使用它去配置部件:
```csharp
Configure<WidgetOptions>(options =>
Configure<AbpWidgetOptions>(options =>
{
options.Widgets.Add<MySimpleWidgetViewComponent>();
});
```
将上面的代码写到[模块](../Module-Development-Basics.md)的 `ConfigureServices` 方法中. `Widgetoptions` 可以完成 `Widget` attribute 的所有功能. 比如为组件添加样式:
将上面的代码写到[模块](../Module-Development-Basics.md)的 `ConfigureServices` 方法中. `AbpWidgetOptions` 可以完成 `Widget` attribute 的所有功能. 比如为组件添加样式:
````csharp
Configure<WidgetOptions>(options =>
Configure<AbpWidgetOptions>(options =>
{
options.Widgets
.Add<MySimpleWidgetViewComponent>()
@ -271,4 +271,4 @@ Configure<WidgetOptions>(options =>
});
````
> 提示: `WidgetOptions` 还可以更改现有的部件配置. 如果要修改应用程序使用的模块内的组件配置,这会很有用. 使用 `options.Widgets.Find` 获取现有的 `WidgetDefinition`.
> 提示: `AbpWidgetOptions` 还可以更改现有的部件配置. 如果要修改应用程序使用的模块内的组件配置,这会很有用. 使用 `options.Widgets.Find` 获取现有的 `WidgetDefinition`.

3
docs/zh-Hans/CLI.md

@ -1,4 +1,4 @@
# ABP CLI
# ABP CLI
ABP CLI (命令行接口) 是一个命令行工具,用来执行基于ABP解决方案的一些常见操作.
@ -109,6 +109,7 @@ abp add-module Volo.Blogging
* `--solution``-s`: 指定解决方案 (.sln) 路径. 如果未指定,CLI会尝试在当前目录中寻找.sln文件.
* `--skip-db-migrations`: 对于EF Core 数据库提供程序,它会自动添加新代码的第一次迁移 (`Add-Migration`) 并且在需要时更新数据库 (`Update-Database`). 指定此选项可跳过此操作.
* `-sp``--startup-project`: 启动项目的项目文件夹的相对路径. 默认值是当前文件夹.
* `--with-source-code`: 添加模块的源代码,而不是NuGet/NPM软件包.
### update

93
docs/zh-Hans/Entities.md

@ -287,6 +287,99 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
所有这些基类也有 `... WithUser`,像 `FullAuditedAggregateRootWithUser<TUser>``FullAuditedAggregateRootWithUser<TKey, TUser>`. 这样就可以将导航属性添加到你的用户实体. 但在聚合根之间添加导航属性不是一个好做法,所以这种用法是不建议的(除非你使用EF Core之类的ORM可以很好地支持这种情况,并且你真的需要它. 请记住这种方法不适用于NoSQL数据库(如MongoDB),你必须真正实现聚合模式).
## 额外的属性
ABP定义了 `IHasExtraProperties` 接口,可以由实体实现,以便能够动态地设置和获取的实体属性. `AggregateRoot` 基类已经实现了 `IHasExtraProperties` 接口. 如果你从这个类(或者上面定义的一个相关审计类)派生,那么你可以直接使用API​.
### GetProperty 和 SetProperty 扩展方法
这些扩展方法是获取和设置实体数据的推荐方法. 例:
````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");
}
}
````
* 属性的**值是object**,可以是任何类型的对象(string,int,bool...等).
* 如果给定的属性未设置值, `GetProperty` 方法会返回 `null`.
* 你可以使用不同的**属性名称**(如这里的`Title`)同时存储多个属性.
最好为属性名**定义一个常量**防止拼写错误. 最佳方式是定义**扩展方法**来利用智能感知. 例:
````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);
}
}
````
然后你可以直接使用 `IdentityUser` 对象的 `user.SetTitle("...")``user.GetTitle()`.
### HasProperty 和 RemoveProperty 扩展方法
* `HasProperty` 用于检查对象是否设置了属性.
* `RemoveProperty` 用于从对象中删除属性. 你可以使用它来替代设置 `null` 值.
### 它是如何实现的?
`IHasExtraProperties` 要求实现类定义一个名称为 `ExtraProperties` 的`Dictionary<string, object>` 属性.
所以,如果你需要你可以直接使用 `ExtraProperties` 属性来使用字典API,但是推荐使用 `SetProperty``GetProperty` 方法,因为它们会检查 `null` 值.
#### 它是如何存储的?
存储字典的方式取决于你使用的数据库提供程序.
* 对于 [Entity Framework Core](Entity-Framework-Core.md), 它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成.
* 对于 [MongoDB](MongoDB.md), 它以 **常规字段** 存储, 因为 MongoDB 天生支持这种 [额外](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) 系统.
### 讨论额外的属性
如果你使用**可重复使用的模块**,其中定义了一个实体,你想使用简单的方式get/set此实体相关的一些数据,那么额外的属性系统是非常有用的. 通常 **不需要** 为自己的实体使用这个系统,是因为它有以下缺点:
* 它不是**完全类型安全的**.
* 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**.
* 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易.
### 额外属性背后的实体
`IHasExtraProperties` 不限于与实体一起使用. 你可以为任何类型的类实现这个接口,使用 `GetProperty`,`SetProperty` 和其他相关方法.
## 另请参阅
* [实体设计最佳实践指南](Best-Practices/Entities.md)

21
docs/zh-Hans/Entity-Framework-Core-Migrations.md

@ -902,26 +902,7 @@ public class BookStoreDbMigratorModule : AbpModule
}
````
#### BookStoreDbMigrationService
最后还需要找到 `Acme.BookStore.Domain` 项目的 `BookStoreDbMigrationService` 类,它当前被设计为使用单个 `IBookStoreDbSchemaMigrator` 实现,但现在我们有了两个.
它注入了 `IBookStoreDbSchemaMigrator`. 使用 `IEnumerable<IBookStoreDbSchemaMigrator>` 注入替换它, ([依赖注入系统](Dependency-Injection.md) 允许像这样注入接口的多个实现).
现在你有 **模式迁移器的集合** ,找到以下行:
````csharp
await _dbSchemaMigrators.MigrateAsync();
````
将这行代码进行以下改变:
````csharp
foreach (var migrator in _dbSchemaMigrators)
{
await migrator.MigrateAsync();
}
````
#### 运行数据库迁移程序
你可以运行 `.DbMigrator` 应用程序应用迁移和初始化种子数据. 你可以删除这两个数据库进行测试,然后再次运行 `.DbMigrator` 应用程序,它会创建两个数据库.

4
docs/zh-Hans/Exception-Handling.md

@ -204,7 +204,7 @@ throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"
首先,在模块配置代码中将 **code-namespace** 映射至 **本地化资源**:
````C#
services.Configure<ExceptionLocalizationOptions>(options =>
services.Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("Volo.Qa", typeof(QaResource));
});
@ -285,7 +285,7 @@ ABP尝试按照以下规则,自动映射常见的异常类型的HTTP状态代码
可以重写HTTP状态代码的自动映射,示例如下:
````C#
services.Configure<ExceptionHttpStatusCodeOptions>(options =>
services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
{
options.Map("Volo.Qa:010002", HttpStatusCode.Conflict);
});

2
docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md

@ -27,7 +27,7 @@ abp new Acme.BookStore
* [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/)
### 解决方案结构

2
docs/zh-Hans/Localization.md

@ -46,7 +46,7 @@ public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<VirtualFileSystemOptions>(options =>
Configure<AbpVirtualFileSystemOptions>(options =>
{
// "YourRootNameSpace" 是项目的根命名空间名字. 如果你的项目的根命名空间名字为空,则无需传递此参数.
options.FileSets.AddEmbedded<MyModule>("YourRootNameSpace");

2
docs/zh-Hans/Module-Development-Basics.md

@ -42,7 +42,7 @@ public class BlogModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
//为应用程序配置默认的连接字符串
Configure<DbConnectionOptions>(options =>
Configure<AbpDbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = "......";
});

4
docs/zh-Hans/Multi-Tenancy.md

@ -168,7 +168,7 @@ namespace MyCompany.MyProject
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<ConfigurationTenantStoreOptions>(options =>
Configure<AbpDefaultTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
@ -214,7 +214,7 @@ namespace MyCompany.MyProject
{
var configuration = BuildConfiguration();
Configure<ConfigurationTenantStoreOptions>(configuration);
Configure<AbpDefaultTenantStoreOptions>(configuration);
}
private static IConfigurationRoot BuildConfiguration()

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

@ -198,7 +198,8 @@
"text": "领域服务"
},
{
"text": "规约"
"text": "规约",
"path": "Specifications.md"
}
]
},
@ -248,7 +249,16 @@
},
{
"text": "Tag Helpers",
"path": "AspNetCore/Tag-Helpers/Index.md"
"items":[
{
"text": "在线演示",
"path": "AspNetCore/Tag-Helpers/Index.md"
},
{
"text": "按钮",
"path": "AspNetCore/Tag-Helpers/Buttons.md"
}
]
},
{
"text": "仪表板和小部件(Widget)系统",

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSize.cs

@ -4,6 +4,7 @@
{
Default,
Small,
Large
Large,
ExtraLarge
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSizeExtensions.cs

@ -10,6 +10,8 @@
return "modal-sm";
case AbpModalSize.Large:
return "modal-lg";
case AbpModalSize.ExtraLarge:
return "modal-xl";
case AbpModalSize.Default:
return "";
default:

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/sl.json

@ -0,0 +1,12 @@
{
"culture": "sl",
"texts": {
"GivenTenantIsNotAvailable": "Najemnik ni na voljo: {0}",
"Tenant": "Najemnik",
"Switch": "Zamenjaj",
"Name": "Naziv",
"SwitchTenantHint": "Pustite polje z imenom prazno, da preklopite na stran za upravnike.",
"SwitchTenant": "Zamenjaj najemnika",
"NotSelected": "Ni izbran"
}
}

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NugetPackageToLocalReferenceConverter.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -52,6 +52,7 @@ namespace Volo.Abp.Cli.ProjectModification
foreach (XmlNode oldNode in nodes)
{
var tempSourceFile = sourceFile;
var oldNodeIncludeValue = oldNode?.Attributes?["Include"]?.Value;
var moduleName = nugetPackageList.FirstOrDefault(n => n.NugetPackage.Name == oldNodeIncludeValue)?.ModuleName;
@ -67,11 +68,11 @@ namespace Volo.Abp.Cli.ProjectModification
if (oldNodeIncludeValue.EndsWith(".test", StringComparison.InvariantCultureIgnoreCase) ||
oldNodeIncludeValue.EndsWith(".tests", StringComparison.InvariantCultureIgnoreCase))
{
sourceFile = "test";
tempSourceFile = "test";
}
else
{
sourceFile = "src";
tempSourceFile = "src";
}
}
else
@ -81,7 +82,7 @@ namespace Volo.Abp.Cli.ProjectModification
}
var referenceProjectPath =
$"{localPathPrefix}{moduleName}\\{sourceFile}\\{oldNodeIncludeValue}\\{oldNodeIncludeValue}.csproj";
$"{localPathPrefix}{moduleName}\\{tempSourceFile}\\{oldNodeIncludeValue}\\{oldNodeIncludeValue}.csproj";
XmlNode newNode = GetNewReferenceNode(doc, referenceProjectPath);

23
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/sl.json

@ -0,0 +1,23 @@
{
"culture": "sl",
"texts": {
"DisplayName:Abp.Mailing.DefaultFromAddress": "Privzeti naslov za pošiljanje",
"DisplayName:Abp.Mailing.DefaultFromDisplayName": "Privzeto ime za prikaz pri pošiljanju",
"DisplayName:Abp.Mailing.Smtp.Host": "Gostitelj",
"DisplayName:Abp.Mailing.Smtp.Port": "Vrata",
"DisplayName:Abp.Mailing.Smtp.UserName": "Uporabniško ime",
"DisplayName:Abp.Mailing.Smtp.Password": "Geslo",
"DisplayName:Abp.Mailing.Smtp.Domain": "Domena",
"DisplayName:Abp.Mailing.Smtp.EnableSsl": "Omogoči SSL",
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Uporabi privzete poverilnice",
"Description:Abp.Mailing.DefaultFromAddress": "Privzeti naslov za pošiljanje",
"Description:Abp.Mailing.DefaultFromDisplayName": "Privzeto ime za prikaz pri pošiljanju",
"Description:Abp.Mailing.Smtp.Host": "Naziv ali IP naslov gostitelja za uporabo SMTP transakcij.",
"Description:Abp.Mailing.Smtp.Port": "Vrata za uporabo SMTP transakcij.",
"Description:Abp.Mailing.Smtp.UserName": "Uporabniško ime, povezano s poverilnicami.",
"Description:Abp.Mailing.Smtp.Password": "The password for the user name associated with the credentials.",
"Description:Abp.Mailing.Smtp.Domain": "Geslo za uporabniško ime, povezano s poverilnicami.",
"Description:Abp.Mailing.Smtp.EnableSsl": "Ali SmtpClient uporablja sloj varnih vtičnic (SSL) za šifriranje povezave.",
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Ali so privzete poverilnice poslane skupaj z zahtevki."
}
}

7
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/sl.json

@ -0,0 +1,7 @@
{
"culture": "sl",
"texts": {
"DisplayName:Abp.Localization.DefaultLanguage": "Privzeti jezik",
"Description:Abp.Localization.DefaultLanguage": "Privzeti jezik aplikacije."
}
}

6
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/sl.json

@ -0,0 +1,6 @@
{
"culture": "sl",
"texts": {
"Menu:Administration": "Administracija"
}
}

2
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/EntityChange.cs

@ -26,7 +26,7 @@ namespace Volo.Abp.AuditLogging
public virtual string EntityTypeFullName { get; protected set; }
public ICollection<EntityPropertyChange> PropertyChanges { get; protected set; }
public virtual ICollection<EntityPropertyChange> PropertyChanges { get; protected set; }
public virtual Dictionary<string, object> ExtraProperties { get; protected set; }

5
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAuthorizationHandler.cs

@ -35,6 +35,11 @@ namespace Volo.Blogging.Comments
private async Task<bool> HasDeletePermission(AuthorizationHandlerContext context, Comment resource)
{
if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
{
return true;
}
if (await _permissionChecker.IsGrantedAsync(context.User, BloggingPermissions.Comments.Delete))
{
return true;

5
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAuthorizationHandler.cs

@ -35,6 +35,11 @@ namespace Volo.Blogging.Posts
private async Task<bool> HasDeletePermission(AuthorizationHandlerContext context, Post resource)
{
if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
{
return true;
}
if (await _permissionChecker.IsGrantedAsync(context.User, BloggingPermissions.Posts.Delete))
{
return true;

3
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json

@ -42,6 +42,7 @@
"CreationTime": "Creation Time",
"Description": "Description",
"Blogs": "Blogs",
"Tags": "Tags"
"Tags": "Tags",
"ShareOn": "Share on"
}
}

3
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json

@ -42,6 +42,7 @@
"CreationTime": "Oluşturulma zamanı",
"Description": "Açıklama",
"Blogs": "Bloglar",
"Tags": "Etiketler"
"Tags": "Etiketler",
"ShareOn": "Paylaş"
}
}

2
modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/CommentsController.cs

@ -45,7 +45,7 @@ namespace Volo.Blogging
[Route("{id}")]
public Task DeleteAsync(Guid id)
{
throw new NotImplementedException();
return _commentAppService.DeleteAsync(id);
}
}
}

22
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml

@ -32,7 +32,7 @@
<div class="hero-articles">
<div class="hero-content">
<h1 class="mb-3">
<a asp-page="./Detail" asp-route-postUrl="@Model.Post.Url" asp-route-blogShortName="@Model.BlogShortName">@Model.Post.Title</a>
<a asp-page="./Detail" asp-route-postUrl="@Model.Post.Url" asp-route-blogShortName="@Model.BlogShortName" id="PostTitle">@Model.Post.Title</a>
</h1>
<div class="article-owner">
@ -84,7 +84,7 @@
</section>
</div>
</div>
<div class="row">
<div class="col-md-8 col-lg-8 mx-auto">
<section class="post-content">
@ -94,9 +94,22 @@
</section>
</div>
</div>
<div class="row">
<div class="col-md-8 col-lg-8 mx-auto">
<hr />
<div class="mb-2 mt-1">
@(L["ShareOn"].Value + " :")
<a href="#" target="_blank" class="mr-2" id="TwitterShareLink" title="Twitter">
Twitter <i class="fa fa-twitter"></i>
</a>
<a href="#" target="_blank" class="mr-2" id="LinkedinShareLink" title="LinkedIn">
Linkedin <i class="fa fa-linkedin"></i>
</a>
<a href="#" target="_blank" class="mr-2" id="EmailShareLink" title="E-mail">
E-mail <i class="fa fa-envelope-square"></i>
</a>
</div>
@if (Model.Post.Tags.Count > 0)
{
<div class="tags">
@ -147,7 +160,7 @@
</a>
}
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Delete))
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Delete) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId))
{
<span class="seperator">|</span>
<a href="#" class="tag deleteLink" data-deleteid="@commentWithRepliesDto.Comment.Id">
@ -317,3 +330,4 @@
</div>
</div>
</div>
<p id="BlogFullName" name="@Model.Blog.Name" hidden></p>

3
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs

@ -62,8 +62,7 @@ namespace Volo.Blogging.Pages.Blog.Posts
var editedPost = await _postAppService.UpdateAsync(Post.Id, post);
var blog = await _blogAppService.GetAsync(editedPost.BlogId);
// return Redirect(Url.Content($"~/blog/{WebUtility.UrlEncode(blog.ShortName)}/{WebUtility.UrlEncode(editedPost.Url)}"));
return RedirectToPage("/Blog/Posts/Detail", new { blogShortName = blog.ShortName, postUrl = editedPost.Url });
return RedirectToPage("/Blogs/Posts/Detail", new { blogShortName = blog.ShortName, postUrl = editedPost.Url });
}
}

32
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/detail.js

@ -2,6 +2,34 @@
var l = abp.localization.getResource('Blogging');
var initSocialShareLinks = function () {
var re = new RegExp(/^.*\//);
var rootUrl = re.exec(window.location.href);
var pageHeader = $("#PostTitle").text();
var blogName = $('#BlogFullName').attr('name');
$('#TwitterShareLink').attr('href',
'https://twitter.com/intent/tweet?text=' + encodeURI(pageHeader + " | " + blogName + " | " + window.location.href)
);
$('#LinkedinShareLink').attr('href',
'https://www.linkedin.com/shareArticle?'
+ 'url=' + encodeURI(window.location.href) + '&'
+ 'mini=true&'
+ "summary=" + encodeURI(blogName) + '&'
+ "title=" + encodeURI(pageHeader) + '&'
+ "source=" + encodeURI(rootUrl)
);
$('#EmailShareLink').attr('href',
'mailto:?'
+ 'body=' + encodeURI('I want you to look at ' + window.location.href) + '&'
+ "subject=" + encodeURI(pageHeader + ' | ' + blogName) + '&'
);
};
$('div .replyForm').hide();
$('div .editForm').hide();
@ -135,4 +163,8 @@
}, 500);
}
$(".post-content a[href^='http']").attr('target', '_blank');
initSocialShareLinks();
})(jQuery);

3
modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\..\configureawait.props" />
@ -8,6 +8,7 @@
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<UserSecretsId>5f11b41f-0025-4fe6-ab97-60ec1bd4e8c2</UserSecretsId>
</PropertyGroup>
<ItemGroup>

6
modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs

@ -33,6 +33,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Volo.Abp.Account;
using Volo.Abp.Validation.Localization;
using Volo.Docs.Documents.FullSearch.Elastic;
namespace VoloDocs.Web
{
@ -71,6 +72,11 @@ namespace VoloDocs.Web
options.RoutePrefix = null;
});
Configure<DocsElasticSearchOptions>(options =>
{
options.Enable = true;
});
Configure<AbpDbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = configuration["ConnectionString"];

5
modules/docs/app/VoloDocs.Web/appsettings.json

@ -1,4 +1,7 @@
{
"ConnectionString": "Server=localhost;Database=VoloDocs;Trusted_Connection=True;MultipleActiveResultSets=true",
"LogoUrl": "/assets/images/Logo.png"
"LogoUrl": "/assets/images/Logo.png",
"ElasticSearch": {
"Url": "http://localhost:9200"
}
}

2
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs

@ -8,5 +8,7 @@ namespace Volo.Docs.Admin.Documents
Task PullAllAsync(PullAllDocumentInput input);
Task PullAsync(PullDocumentInput input);
Task ReindexAsync();
}
}

22
modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs

@ -7,6 +7,7 @@ using Newtonsoft.Json;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Docs.Documents;
using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.Projects;
namespace Volo.Docs.Admin.Documents
@ -18,16 +19,19 @@ namespace Volo.Docs.Admin.Documents
private readonly IDocumentRepository _documentRepository;
private readonly IDocumentSourceFactory _documentStoreFactory;
private readonly IDistributedCache<DocumentUpdateInfo> _documentUpdateCache;
private readonly IDocumentFullSearch _documentFullSearch;
public DocumentAdminAppService(IProjectRepository projectRepository,
IDocumentRepository documentRepository,
IDocumentSourceFactory documentStoreFactory,
IDistributedCache<DocumentUpdateInfo> documentUpdateCache)
IDistributedCache<DocumentUpdateInfo> documentUpdateCache,
IDocumentFullSearch documentFullSearch)
{
_projectRepository = projectRepository;
_documentRepository = documentRepository;
_documentStoreFactory = documentStoreFactory;
_documentUpdateCache = documentUpdateCache;
_documentFullSearch = documentFullSearch;
}
public async Task PullAllAsync(PullAllDocumentInput input)
@ -80,6 +84,22 @@ namespace Volo.Docs.Admin.Documents
await UpdateDocumentUpdateInfoCache(sourceDocument);
}
public async Task ReindexAsync()
{
var docs = await _documentRepository.GetListAsync();
var projects = await _projectRepository.GetListAsync();
foreach (var doc in docs)
{
var project = projects.FirstOrDefault(x => x.Id == doc.ProjectId);
if (project != null && (doc.FileName == project.NavigationDocumentName || doc.FileName == project.ParametersDocumentName))
{
continue;
}
await _documentFullSearch.AddOrUpdateAsync(doc);
}
}
private async Task UpdateDocumentUpdateInfoCache(Document document)
{
var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}";

7
modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs

@ -32,5 +32,12 @@ namespace Volo.Docs.Admin
{
return _documentAdminAppService.PullAsync(input);
}
[HttpPost]
[Route("Reindex")]
public Task ReindexAsync()
{
return _documentAdminAppService.ReindexAsync();
}
}
}

19
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchInput.cs

@ -0,0 +1,19 @@
using System;
namespace Volo.Docs.Documents
{
public class DocumentSearchInput
{
public string Context { get; set; }
public Guid ProjectId { get; set; }
public string LanguageCode { get; set; }
public string Version { get; set; }
public int? SkipCount { get; set; }
public int? MaxResultCount { get; set; }
}
}

24
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchOutput.cs

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Docs.Documents
{
public class DocumentSearchOutput
{
public DocumentSearchOutput()
{
Highlight = new List<string>();
}
public string Name { get; set; }
public string FileName { get; set; }
public string Version { get; set; }
public string LanguageCode { get; set; }
public List<string> Highlight { get; set; }
}
}

5
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
@ -14,5 +15,9 @@ namespace Volo.Docs.Documents
Task<DocumentParametersDto> GetParametersAsync(GetParametersDocumentInput input);
Task<DocumentResourceDto> GetResourceAsync(GetDocumentResourceInput input);
Task<List<DocumentSearchOutput>> SearchAsync(DocumentSearchInput input);
Task<bool> FullSearchEnabledAsync();
}
}

32
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs

@ -5,8 +5,10 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Volo.Abp.Caching;
using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.Projects;
namespace Volo.Docs.Documents
@ -20,6 +22,9 @@ namespace Volo.Docs.Documents
protected IDistributedCache<DocumentResourceDto> ResourceCache { get; }
protected IDistributedCache<DocumentUpdateInfo> DocumentUpdateCache { get; }
protected IHostEnvironment HostEnvironment { get; }
private readonly IDocumentFullSearch _documentFullSearch;
private readonly DocsElasticSearchOptions _docsElasticSearchOptions;
public DocumentAppService(
IProjectRepository projectRepository,
IDocumentRepository documentRepository,
@ -27,7 +32,9 @@ namespace Volo.Docs.Documents
IDistributedCache<LanguageConfig> languageCache,
IDistributedCache<DocumentResourceDto> resourceCache,
IDistributedCache<DocumentUpdateInfo> documentUpdateCache,
IHostEnvironment hostEnvironment)
IHostEnvironment hostEnvironment,
IDocumentFullSearch documentFullSearch,
IOptions<DocsElasticSearchOptions> docsElasticSearchOptions)
{
_projectRepository = projectRepository;
_documentRepository = documentRepository;
@ -36,6 +43,8 @@ namespace Volo.Docs.Documents
ResourceCache = resourceCache;
DocumentUpdateCache = documentUpdateCache;
HostEnvironment = hostEnvironment;
_documentFullSearch = documentFullSearch;
_docsElasticSearchOptions = docsElasticSearchOptions.Value;
}
public virtual async Task<DocumentWithDetailsDto> GetAsync(GetDocumentInput input)
@ -124,6 +133,27 @@ namespace Volo.Docs.Documents
);
}
public async Task<List<DocumentSearchOutput>> SearchAsync(DocumentSearchInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);
var esDocs = await _documentFullSearch.SearchAsync(input.Context, project.Id, input.LanguageCode, input.Version);
return esDocs.Select(esDoc => new DocumentSearchOutput//TODO: auto map
{
Name = esDoc.Name,
FileName = esDoc.FileName,
Version = esDoc.Version,
LanguageCode = esDoc.LanguageCode,
Highlight = esDoc.Highlight
}).ToList();
}
public async Task<bool> FullSearchEnabledAsync()
{
return await Task.FromResult(_docsElasticSearchOptions.Enable);
}
public async Task<DocumentParametersDto> GetParametersAsync(GetParametersDocumentInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);

7
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/DocsDomainErrorCodes.cs

@ -0,0 +1,7 @@
namespace Volo.Docs
{
public static class DocsDomainErrorCodes
{
public const string ElasticSearchNotEnabled = "Volo.Docs.Domain:010001";
}
}

1
modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj

@ -18,6 +18,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.0" />
<PackageReference Include="Octokit" Version="0.36.0" />
<PackageReference Include="NEST" Version="7.5.1" />
</ItemGroup>
<ItemGroup>

16
modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs

@ -1,10 +1,14 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.Domain;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
using Volo.Abp.VirtualFileSystem;
using Volo.Docs.Documents;
using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.FileSystem.Documents;
using Volo.Docs.GitHub.Documents;
using Volo.Docs.Localization;
@ -43,5 +47,17 @@ namespace Volo.Docs
client.Timeout = TimeSpan.FromMilliseconds(15000);
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
using (var scope = context.ServiceProvider.CreateScope())
{
if (scope.ServiceProvider.GetRequiredService<IOptions<DocsElasticSearchOptions>>().Value.Enable)
{
var documentFullSearch = scope.ServiceProvider.GetRequiredService<IDocumentFullSearch>();
AsyncHelper.RunSync(() => documentFullSearch.CreateIndexIfNeededAsync());
}
}
}
}
}

27
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DefaultElasticClientProvider.cs

@ -0,0 +1,27 @@
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Nest;
using Volo.Abp.DependencyInjection;
namespace Volo.Docs.Documents.FullSearch.Elastic
{
public class DefaultElasticClientProvider : IElasticClientProvider, ISingletonDependency
{
protected readonly DocsElasticSearchOptions Options;
protected readonly IConfiguration Configuration;
public DefaultElasticClientProvider(IOptions<DocsElasticSearchOptions> options, IConfiguration configuration)
{
Configuration = configuration;
Options = options.Value;
}
public virtual IElasticClient GetClient()
{
var node = new Uri(Configuration["ElasticSearch:Url"]);
var settings = new ConnectionSettings(node).DefaultIndex(Options.IndexName);
return new ElasticClient(settings);
}
}
}

15
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocsElasticSearchOptions.cs

@ -0,0 +1,15 @@
namespace Volo.Docs.Documents.FullSearch.Elastic
{
public class DocsElasticSearchOptions
{
public DocsElasticSearchOptions()
{
Enable = false;
IndexName = "abp_documents";
}
public bool Enable { get; set; }
public string IndexName { get; set; }
}
}

49
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocumentChangedEventHandler.cs

@ -0,0 +1,49 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace Volo.Docs.Documents.FullSearch.Elastic
{
public class DocumentChangedEventHandler : ILocalEventHandler<EntityCreatedEventData<Document>>,
ILocalEventHandler<EntityUpdatedEventData<Document>>,
ILocalEventHandler<EntityDeletedEventData<Document>>,
ITransientDependency
{
private readonly DocsElasticSearchOptions _options;
private readonly IDocumentFullSearch _documentFullSearch;
public DocumentChangedEventHandler(IDocumentFullSearch documentFullSearch, IOptions<DocsElasticSearchOptions> options)
{
_documentFullSearch = documentFullSearch;
_options = options.Value;
}
public async Task HandleEventAsync(EntityCreatedEventData<Document> eventData)
{
await AddOrUpdate(eventData.Entity);
}
public async Task HandleEventAsync(EntityUpdatedEventData<Document> eventData)
{
await AddOrUpdate(eventData.Entity);
}
private async Task AddOrUpdate(Document document)
{
if (_options.Enable)
{
await _documentFullSearch.AddOrUpdateAsync(document);
}
}
public async Task HandleEventAsync(EntityDeletedEventData<Document> eventData)
{
if (_options.Enable)
{
await _documentFullSearch.DeleteAsync(eventData.Entity.Id);
}
}
}
}

189
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs

@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elasticsearch.Net;
using Microsoft.Extensions.Options;
using Nest;
using Volo.Abp;
using Volo.Abp.Domain.Services;
namespace Volo.Docs.Documents.FullSearch.Elastic
{
public class ElasticDocumentFullSearch : DomainService, IDocumentFullSearch
{
private readonly IElasticClientProvider _clientProvider;
private readonly DocsElasticSearchOptions _options;
public ElasticDocumentFullSearch(IElasticClientProvider clientProvider, IOptions<DocsElasticSearchOptions> options)
{
_clientProvider = clientProvider;
_options = options.Value;
}
public async Task CreateIndexIfNeededAsync(CancellationToken cancellationToken = default)
{
CheckEsEnabled();
var client = _clientProvider.GetClient();
var existsResponse = await client.Indices.ExistsAsync(_options.IndexName, ct: cancellationToken);
HandleError(existsResponse);
if (!existsResponse.Exists)
{
HandleError(await client.Indices.CreateAsync(_options.IndexName, c => c
.Map<EsDocument>(m =>
{
return m
.Properties(p => p
.Keyword(x => x.Name(d => d.Id))
.Keyword(x => x.Name(d => d.ProjectId))
.Keyword(x => x.Name(d => d.Name))
.Keyword(x => x.Name(d => d.FileName))
.Keyword(x => x.Name(d => d.Version))
.Keyword(x => x.Name(d => d.LanguageCode))
.Text(x => x.Name(d => d.Content)));
}), cancellationToken));
}
}
public async Task AddOrUpdateAsync(Document document, CancellationToken cancellationToken = default)
{
CheckEsEnabled();
var client = _clientProvider.GetClient();
var existsResponse = await client.DocumentExistsAsync<EsDocument>(DocumentPath<EsDocument>.Id(document.Id),
x => x.Index(_options.IndexName), cancellationToken);
HandleError(existsResponse);
var esDocument = new EsDocument
{
Id = document.Id,
ProjectId = document.ProjectId,
Name = document.Name,
FileName = document.FileName,
Content = document.Content,
LanguageCode = document.LanguageCode,
Version = document.Version
};
if (!existsResponse.Exists)
{
HandleError(await client.IndexAsync<EsDocument>(esDocument,
x => x.Id(document.Id).Index(_options.IndexName), cancellationToken));
}
else
{
HandleError(await client.UpdateAsync<EsDocument>(DocumentPath<EsDocument>.Id(document.Id),
x => x.Doc(esDocument).Index(_options.IndexName), cancellationToken));
}
}
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
CheckEsEnabled();
HandleError(await _clientProvider.GetClient()
.DeleteAsync(DocumentPath<Document>.Id(id), x => x.Index(_options.IndexName), cancellationToken));
}
public async Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode,
string version, int? skipCount = null, int? maxResultCount = null,
CancellationToken cancellationToken = default)
{
CheckEsEnabled();
var request = new SearchRequest
{
Size = maxResultCount ?? 10,
From = skipCount ?? 0,
Query = new BoolQuery
{
Must = new QueryContainer[]
{
new MatchQuery
{
Field = "content",
Query = context
}
},
Filter = new QueryContainer[]
{
new BoolQuery
{
Must = new QueryContainer[]
{
new TermQuery
{
Field = "projectId",
Value = projectId
},
new TermQuery
{
Field = "version",
Value = version
},
new TermQuery
{
Field = "languageCode",
Value = languageCode
}
}
}
}
},
Highlight = new Highlight
{
PreTags = new[] { "<highlight>" },
PostTags = new[] { "</highlight>" },
Fields = new Dictionary<Field, IHighlightField>
{
{
"content", new HighlightField()
}
}
}
};
//var json = _clientProvider.GetClient().RequestResponseSerializer.SerializeToString(request);
var response = await _clientProvider.GetClient().SearchAsync<EsDocument>(request, cancellationToken);
HandleError(response);
var docs = new List<EsDocument>();
foreach (var hit in response.Hits)
{
var doc = hit.Source;
if (hit.Highlight.ContainsKey("content"))
{
doc.Highlight = new List<string>();
doc.Highlight.AddRange(hit.Highlight["content"]);
}
docs.Add(doc);
}
return docs;
}
protected void HandleError(IElasticsearchResponse response)
{
if (!response.ApiCall.Success)
{
throw response.ApiCall.OriginalException;
}
}
protected void CheckEsEnabled()
{
if (!_options.Enable)
{
throw new BusinessException(DocsDomainErrorCodes.ElasticSearchNotEnabled);
}
}
}
}

29
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/EsDocument.cs

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Volo.Docs.Documents.FullSearch.Elastic
{
public class EsDocument
{
public EsDocument()
{
Highlight = new List<string>();
}
public Guid Id { get; set; }
public Guid ProjectId { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
public string Version { get; set; }
public string LanguageCode { get; set; }
public string Content { get; set; }
public List<string> Highlight { get; set; }
}
}

20
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IDocumentFullSearch.cs

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Docs.Documents.FullSearch.Elastic
{
public interface IDocumentFullSearch
{
Task CreateIndexIfNeededAsync(CancellationToken cancellationToken = default);
Task AddOrUpdateAsync(Document document, CancellationToken cancellationToken = default);
Task DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode,
string version, int? skipCount = null, int? maxResultCount = null,
CancellationToken cancellationToken = default);
}
}

9
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IElasticClientProvider.cs

@ -0,0 +1,9 @@
using Nest;
namespace Volo.Docs.Documents.FullSearch.Elastic
{
public interface IElasticClientProvider
{
IElasticClient GetClient();
}
}

2
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json

@ -17,6 +17,8 @@
"NavigationDocumentNotFound": "This version does not have a navigation document!",
"DocumentNotFoundInSelectedLanguage": "Document in the language you wanted is not found. Document in the default language is shown.",
"FilterTopics": "Filter topics",
"FullSearch": "Search in documents",
"Volo.Docs.Domain:010001": "Elastic search is not enabled.",
"MultipleVersionDocumentInfo": "This document has multiple versions. Select the options best fit for you.",
"New": "New",
"Upd": "Upd",

2
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json

@ -17,6 +17,8 @@
"NavigationDocumentNotFound": "这个版本没有导航文件!",
"DocumentNotFoundInSelectedLanguage": "本文档不适用于所选语言, 将以默认语言显示文档.",
"FilterTopics": "过滤主题",
"FullSearch": "搜索文档",
"Volo.Docs.Domain:010001": "Elastic search未启用.",
"New": "新文档",
"Upd": "更新",
"NewExplanation": "在最近两周内创建.",

1
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json

@ -17,6 +17,7 @@
"NavigationDocumentNotFound": "這個版本沒有導覽文件!",
"DocumentNotFoundInSelectedLanguage": "本文件不適用於所選語系,將以預設語系顯示.",
"FilterTopics": "過濾主題",
"FullSearch": "搜索文件",
"New": "新文檔",
"Upd": "更新",
"NewExplanation": "在最近兩周內創建.",

17
modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
@ -46,6 +47,20 @@ namespace Volo.Docs.Documents
return DocumentAppService.GetResourceAsync(input);
}
[HttpPost]
[Route("search")]
public Task<List<DocumentSearchOutput>> SearchAsync(DocumentSearchInput input)
{
return DocumentAppService.SearchAsync(input);
}
[HttpGet]
[Route("full-search-enabled")]
public Task<bool> FullSearchEnabledAsync()
{
return DocumentAppService.FullSearchEnabledAsync();
}
[HttpGet]
[Route("parameters")]
public Task<DocumentParametersDto> GetParametersAsync(GetParametersDocumentInput input)

3
modules/docs/src/Volo.Docs.Web/DocsWebModule.cs

@ -1,4 +1,4 @@
using System;
using System;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -61,6 +61,7 @@ namespace Volo.Docs
options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{projectName}");
options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{languageCode}/{projectName}");
options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{languageCode}/{projectName}/{version}/{*documentName}");
options.Conventions.AddPageRoute("/Documents/Search", routePrefix + "search/{languageCode}/{projectName}/{version}/{*keyword}");
});
context.Services.AddAutoMapperObjectMapper<DocsWebModule>();

18
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml

@ -135,18 +135,32 @@
}
</div>
<div class="docs-version mb-4">
<div class="docs-version">
<div class="version-select">
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text"><i class="fa fa-filter"></i></label>
</div>
<input class="form-control" type="search" placeholder="@L["FilterTopics"].Value" aria-label="Filter">
<input class="form-control" id="filter" type="search" data-search-url="@Model." placeholder="@L["FilterTopics"].Value" aria-label="Filter">
</div>
</div>
</div>
@if (Model.FullSearchEnabled)
{
<div class="docs-version mb-4">
<div class="version-select">
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text"><i class="fa fa-filter"></i></label>
</div>
<input class="form-control" id="fullsearch" type="search" data-fullsearch-url="/search/@Model.LanguageCode/@Model.ProjectName/@Model.Version/" placeholder="@L["FullSearch"].Value" aria-label="Filter">
</div>
</div>
</div>
}
@if (Model.Navigation == null || !Model.Navigation.HasChildItems)
{

3
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

@ -64,6 +64,8 @@ namespace Volo.Docs.Pages.Documents.Project
public DocumentParametersDto DocumentPreferences { get; set; }
public DocumentRenderParameters UserPreferences { get; set; } = new DocumentRenderParameters();
public bool FullSearchEnabled { get; set; }
private readonly IDocumentAppService _documentAppService;
private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory;
@ -106,6 +108,7 @@ namespace Volo.Docs.Pages.Documents.Project
{
DocumentsUrlPrefix = _uiOptions.RoutePrefix;
ShowProjectsCombobox = _uiOptions.ShowProjectsCombobox;
FullSearchEnabled = await _documentAppService.FullSearchEnabledAsync();
try
{

10
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js

@ -1,4 +1,4 @@
(function ($) {
(function ($) {
$(function () {
var initNavigationFilter = function (navigationContainerId) {
@ -56,13 +56,19 @@
});
};
$(".docs-page .docs-tree-list input[type='search']").keyup(function (e) {
$("#filter").keyup(function (e) {
filterDocumentItems(e.target.value);
if (e.key === "Enter") {
gotoFilteredDocumentIfThereIsOnlyOne();
}
});
$("#fullsearch").keyup(function (e) {
if (e.key === "Enter") {
window.open($(this).data("fullsearch-url") + this.value);
}
});
};
var initAnchorTags = function (container) {

67
modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml

@ -0,0 +1,67 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@using Volo.Docs.Localization
@using Volo.Docs.Pages.Documents
@inject IHtmlLocalizer<DocsResource> L
@inject IThemeManager ThemeManager
@inject IPageLayout PageLayout
@model SearchModel
@{
Layout = ThemeManager.CurrentTheme.GetEmptyLayout();
}
@section styles {
<style>
highlight {
font-weight: bold;
color: red;
font-style: italic;
}
</style>
}
<div class="container">
<form method="get" action="/search/@Model.LanguageCode/@Model.ProjectName/@Model.Version/" class="mt-4">
<input type="text" asp-for="@Model.KeyWord" class="form-control" />
<button type="submit" class="btn-block btn-primary btn-lg mt-3">Search</button>
</form>
<div class="my-3 p-3 bg-white rounded">
<h6 class="border-bottom pb-4 mb-0">Search results</h6>
@foreach (var docs in Model.SearchOutputs)
{
<div class="media text-muted pt-3">
<div class="media-body pb-3 mb-0 small border-bottom">
<div class="list-group">
@functions
{
string RemoveFileExtensionFromPath(string path)
{
if (path == null)
{
return null;
}
return path.EndsWith("." + @Model.Project.Format)
? path.Left(path.Length - Model.Project.Format.Length - 1)
: path;
}
}
<a href="/@Model.LanguageCode/@Model.ProjectName/@Model.Version/@RemoveFileExtensionFromPath(docs.Name)">
<h3>@RemoveFileExtensionFromPath(docs.Name)</h3></a>
@foreach (var highlight in docs.Highlight)
{
<p class="list-group-item list-group-item-action">@Html.Raw(highlight)</p>
}
</div>
</div>
</div>
}
</div>
</div>

73
modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Docs.Documents;
using Volo.Docs.HtmlConverting;
using Volo.Docs.Models;
using Volo.Docs.Projects;
namespace Volo.Docs.Pages.Documents
{
public class SearchModel : PageModel
{
[BindProperty(SupportsGet = true)]
public string ProjectName { get; set; }
[BindProperty(SupportsGet = true)]
public string Version { get; set; }
[BindProperty(SupportsGet = true)]
public string LanguageCode { get; set; }
[BindProperty(SupportsGet = true)]
public string KeyWord { get; set; }
public ProjectDto Project { get; set; }
private readonly IProjectAppService _projectAppService;
private readonly IDocumentAppService _documentAppService;
public SearchModel(IProjectAppService projectAppService,
IDocumentAppService documentAppService)
{
_projectAppService = projectAppService;
_documentAppService = documentAppService;
}
public List<DocumentSearchOutput> SearchOutputs { get; set; } = new List<DocumentSearchOutput>();
public async Task<IActionResult> OnGetAsync(string keyword)
{
if (!await _documentAppService.FullSearchEnabledAsync())
{
return RedirectToPage("Index");
}
KeyWord = keyword;
Project = await _projectAppService.GetAsync(ProjectName);
var output = await _projectAppService.GetVersionsAsync(Project.ShortName);
var versions = output.Items.ToList();
if (versions.Any() && string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase))
{
Version = versions.First().Name;
}
SearchOutputs = await _documentAppService.SearchAsync(new DocumentSearchInput
{
ProjectId = Project.Id,
Context = KeyWord,
LanguageCode = LanguageCode,
Version = Version
});
return Page();
}
}
}

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

Loading…
Cancel
Save