Browse Source

Merge branch 'dev' into maliming/linkuser

pull/5190/head
maliming 6 years ago
parent
commit
6f9636211a
  1. 1
      .github/ISSUE_TEMPLATES/feature.md
  2. 71
      .github/workflows/codeql-analysis.yml
  3. 43
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json
  4. 31
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json
  5. 35
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json
  6. 68
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json
  7. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  8. 161
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ar.json
  9. 15
      docs/en/.vscode/settings.json
  10. 8
      docs/en/AspNet-Boilerplate-Migration-Guide.md
  11. 44
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md
  12. 47
      docs/en/Blog-Posts/2020-10-01 v3_2_Release_Stable/POST.md
  13. 3
      docs/en/CLI.md
  14. 258
      docs/en/Caching.md
  15. 317
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/Using-DevExtremeAngularComponents-With-The-ABP-Framework.md
  16. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/dev-extreme-page.png
  17. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-final.gif
  18. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-first.png
  19. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-second.png
  20. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/localhost-running.png
  21. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/login-screen.png
  22. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/project-setup.png
  23. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users-on-dev-extreme.png
  24. BIN
      docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users.png
  25. 53
      docs/en/Data-Filtering.md
  26. 4
      docs/en/Distributed-Event-Bus-Kafka-Integration.md
  27. 79
      docs/en/Getting-Started-React-Native.md
  28. 32
      docs/en/Getting-Started.md
  29. 5
      docs/en/Localization.md
  30. 2
      docs/en/Modules/Identity.md
  31. 4
      docs/en/MongoDB.md
  32. 492
      docs/en/Multi-Tenancy.md
  33. 43
      docs/en/Redis-Cache.md
  34. 2
      docs/en/SignalR-Integration.md
  35. 4
      docs/en/Tutorials/Part-2.md
  36. 2
      docs/en/Tutorials/Part-5.md
  37. 2
      docs/en/UI/Angular/Config-State.md
  38. 4
      docs/en/UI/Angular/Content-Projection-Service.md
  39. 4
      docs/en/UI/Angular/Custom-Setting-Page.md
  40. 4
      docs/en/UI/Angular/Dom-Insertion-Service.md
  41. 11
      docs/en/UI/Angular/Environment.md
  42. 98
      docs/en/UI/Angular/Feature-Libraries.md
  43. 4
      docs/en/UI/Angular/Lazy-Load-Service.md
  44. 7
      docs/en/UI/Angular/List-Service.md
  45. 6
      docs/en/UI/Angular/Migration-Guide-v3.md
  46. 29
      docs/en/UI/Angular/Multi-Tenancy.md
  47. 346
      docs/en/UI/Angular/PWA-Configuration.md
  48. 212
      docs/en/UI/Angular/Quick-Start.md
  49. 29
      docs/en/UI/Angular/Service-Proxies.md
  50. 4
      docs/en/UI/Angular/Subscription-Service.md
  51. 4
      docs/en/UI/Angular/Toaster-Service.md
  52. 4
      docs/en/UI/Angular/Track-By-Service.md
  53. BIN
      docs/en/UI/Angular/images/pwa-configuration-ng-add.png
  54. BIN
      docs/en/UI/Angular/images/quick-start---angular-live-development-server.gif
  55. BIN
      docs/en/UI/Angular/images/quick-start---new-project-home-page.png
  56. BIN
      docs/en/UI/Angular/images/quick-start---production-build-optimization.png
  57. BIN
      docs/en/UI/Angular/images/quick-start---root-folder-structure.png
  58. BIN
      docs/en/UI/Angular/images/quick-start---self-signed-certificate-error.png
  59. BIN
      docs/en/UI/Angular/images/quick-start---source-folder-structure.png
  60. 206
      docs/en/UI/AspNetCore/Forms-Validation.md
  61. 206
      docs/en/UI/AspNetCore/Navigation-Menu.md
  62. 8
      docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md
  63. 21
      docs/en/UI/AspNetCore/Widgets.md
  64. 6
      docs/en/UI/Blazor/Overall.md
  65. 3
      docs/en/UI/Blazor/Services/Notification.md
  66. 192
      docs/en/docs-nav.json
  67. BIN
      docs/en/images/abp-dynamic-form-result.png
  68. BIN
      docs/en/images/abp-form-input-validation-error-french-name.png
  69. BIN
      docs/en/images/abp-form-input-validation-error-french.png
  70. BIN
      docs/en/images/abp-form-input-validation-error.png
  71. BIN
      docs/en/images/nav-main-menu-administration.png
  72. BIN
      docs/en/images/nav-main-menu.png
  73. BIN
      docs/en/images/react-native-introduction.gif
  74. BIN
      docs/en/images/user-menu.png
  75. 2
      docs/zh-Hans/Startup-Templates/Module.md
  76. 6
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicTheme.cs
  77. 20
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeToolbarContributor.cs
  78. 7
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor
  79. 40
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs
  80. 9
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor
  81. 46
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor
  82. 55
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenuItem.razor
  83. 8
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor
  84. 32
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor.cs
  85. 36
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpUtilsService.cs
  86. 15
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/IAbpUtilsService.cs
  87. 16
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/AbpToolbarOptions.cs
  88. 29
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/IToolbarConfigurationContext.cs
  89. 9
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/IToolbarContributor.cs
  90. 9
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/IToolbarManager.cs
  91. 7
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/StandardToolbars.cs
  92. 18
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/Toolbar.cs
  93. 68
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/ToolbarConfigurationContext.cs
  94. 23
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/ToolbarItem.cs
  95. 39
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/ToolbarManager.cs
  96. 35
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/wwwroot/abp_theming.js
  97. 8
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs
  98. 9
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs
  99. 13
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs
  100. 21
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs

1
.github/ISSUE_TEMPLATES/feature.md

@ -0,0 +1 @@
Please describe the feature need!

71
.github/workflows/codeql-analysis.yml

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [dev, rel-1.0, rel-1.1, rel-2.0, rel-2.1, rel-2.2, rel-2.3, rel-2.4, rel-2.5, rel-2.6, rel-2.7, rel-2.8, rel-2.9, rel-3.0, rel-3.1, rel-3.2]
pull_request:
# The branches below must be a subset of the branches above
branches: [dev]
schedule:
- cron: '0 8 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['csharp', 'javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

43
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json

@ -41,6 +41,16 @@
"ApplicationType": "نوع التطبيق",
"Target": "استهداف",
"TargetFilter": "استهداف",
"ModuleClass": "Module class",
"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": "EntityFramework Core",
"NugetPackageTarget.MongoDB": "قاعدة بيانات مونجو",
"Edit": "تعديل",
"Delete": "حذف",
"Refresh": "تحديث",
@ -54,8 +64,8 @@
"CreateAModule": "إنشاء وحدة",
"CreateANugetPackage": "أنشاء حزمة Nuget",
"AddNew": "اضف جديد",
"PackageAlreadyExist{0}": "",
"ModuleAlreadyExist{0}": "",
"PackageAlreadyExist{0}": "حزمة \"{0}\" تمت إضافتها من قبل.",
"ModuleAlreadyExist{0}": "وحدة \"{0}\" مضافة بالفعل.",
"ClearCache": "مسح ذاكرة التخزين المؤقت",
"SuccessfullyCleared": "تم محوه بنجاح",
"Menu:NpmPackages": "حزم NPM",
@ -93,7 +103,7 @@
"MasterModules": "الوحدات الرئيسية",
"OrganizationName": "اسم المنظمة",
"CreationDate": "تاريخ الإنشاء",
"LicenseStart": "تاريخ بدء الرخصة",
"LicenseStartDate": "تاريخ بدء الترخيص",
"LicenseEndDate": "تاريخ إنتهاء الرخصة",
"OrganizationNamePlaceholder": "اسم المنظمة ...",
"TotalQuestionCountPlaceholder": "إجمالي عدد الأسئلة ...",
@ -156,6 +166,31 @@
"TotalQuestionMustBeGreaterWarningMessage": "يجب أن يكون TotalQuestionCount أكبر من RemainingQuestionCount!",
"QuestionCountsMustBeGreaterThanZero": "يجب أن يكون TotalQuestionCount و RemainingQuestionCount صفرًا أو أكبر من الصفر!",
"UnlimitedQuestionCount": "عدد أسئلة غير محدود",
"Notes": "ملاحظات"
"Notes": "ملاحظات",
"Menu:Community": "المجتمع",
"Menu:Articles": "مقالات",
"Wait": "انتظر",
"Approve": "موافقة",
"Reject": "رفض",
"Details": "تفاصيل",
"Url": "عنوان الموقع",
"Title": "عنوان",
"ContentSource": "مصدر المحتوى",
"Status": "الحالة",
"ReadArticle": "إقرأ المقال",
"ArticleHasBeenWaiting": "تم إيقاف المقال",
"ArticleHasBeenApproved": "تم الموافقة على المقال",
"ArticleHasBeenRejected": "تم رفض المقال",
"Permission:Community": "المجتمع",
"Permission:CommunityArticle": "مقال",
"Link": "رابط",
"Enum:ContentSource:0": "Github",
"Enum:ContentSource:1": "خارجى",
"Enum:Status:0": "انتظار",
"Enum:Status:1": "مرفوض",
"Enum:Status:2": "تمت الموافقة",
"Summary": "مُلخص",
"AuthorName": "اسم المؤلف",
"CoverImage": "صورة الغلاف"
}
}

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

@ -0,0 +1,31 @@
{
"culture": "ar",
"texts": {
"Volo.AbpIo.Domain:010004": "تم الوصول إلى الحد الأقصى لعدد الأعضاء!",
"Volo.AbpIo.Domain:010005": "تم الوصول إلى الحد الأقصى لعدد المالكين!",
"Volo.AbpIo.Domain:010006": "هذا المستخدم هو بالفعل مالك في هذه المنظمة!",
"Volo.AbpIo.Domain:010007": "هذا المستخدم هو بالفعل مطور في هذه المنظمة!",
"Volo.AbpIo.Domain:010008": "لا يمكن أن يكون عدد المطورين المسموح لهم أقل من عدد المطورين الحاليين!",
"Volo.AbpIo.Domain:010009": "لا يمكن أن يكون عدد المطورين المسموح لهم أقل من 0!",
"Volo.AbpIo.Domain:010010": "تم تجاوز الحد الأقصى لعدد عناوين mac!",
"Volo.AbpIo.Domain:010011": "لا يمكن أن تحتوي الرخصة الشخصية على أكثر من مطور واحد!",
"Volo.AbpIo.Domain:010012": "لا يمكن تمديد الترخيص بعد شهر واحد من انتهاء صلاحية الترخيص!",
"Volo.AbpIo.Domain:020001": "تعذر حذف حزمة NPM هذه لأن \"{NugetPackages}\" حزم Nuget تعتمد على هذه الحزمة.",
"Volo.AbpIo.Domain:020002": "تعذر حذف حزمة NPM هذه لأن \"{Modules}\" الوحدات النمطية تستخدم هذه الحزمة.",
"Volo.AbpIo.Domain:020003": "تعذر حذف حزمة NPM هذه لأن \"{Modules}\" الوحدات النمطية تستخدم هذه الحزمة و \"{NugetPackages}\" حزم Nuget تعتمد على هذه الحزمة.",
"Volo.AbpIo.Domain:020004": "تعذر حذف حزمة Nuget هذه لأن \"{Modules}\" الوحدات النمطية تستخدم هذه الحزمة.",
"WantToLearn?": "تريد أن تتعلم؟",
"ReadyToGetStarted?": "على استعداد للبدء؟",
"JoinOurCommunity": "انضم إلى مجتمعنا",
"GetStartedUpper": "دعنا نبدأ",
"ForkMeOnGitHub": "GitHub أعطنى تنبيه على",
"Features": "خصائص",
"GetStarted": "دعنا نبدأ",
"Documents": "وثائق",
"Community": "مجتمع",
"ContributionGuide": "دليل المساهمة",
"Blog": "المدونة",
"Commercial": "تجارى",
"SeeDocuments": "انظر للوثائق"
}
}

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

@ -0,0 +1,35 @@
{
"culture": "ar",
"texts": {
"OrganizationManagement": "إدارة المنظمة",
"OrganizationList": "قائمة المنظمات",
"Volo.AbpIo.Commercial:010003": "لست مالك هذه المنظمة!",
"OrganizationNotFoundMessage": "لا توجد أى منظمة!",
"DeveloperCount": "المحجوز / إجمالى المطورين",
"QuestionCount": "المتبقى / إجمالى الأسئلة",
"Unlimited": "غير محدود",
"Owners": "مُلاّك",
"AddMember": "تسجيل عضو",
"AddOwner": "تسجيل مالك",
"AddDeveloper": "تسجيل مطور",
"UserName": "اسم المستخدم",
"Name": "الاسم",
"EmailAddress": "البريد الإلكترونى",
"Developers": "مطورون",
"LicenseType": "نوع الرخصة",
"Manage": "إدارة",
"StartDate": "تاريخ البداية",
"EndDate": "تاريخ الإنتهاء",
"Modules": "Modules",
"LicenseExtendMessage": "تم تمديد تاريخ انتهاء الترخيص الخاص بك إلى {0}",
"LicenseUpgradeMessage": "تمت ترقية ترخيصك إلى {0}",
"LicenseAddDeveloperMessage": "تمت إضافة {0} من المطورين إلى ترخيصك",
"Volo.AbpIo.Commercial:010004": "لا يمكن العثور على المستخدم المحدد! يجب أن يكون المستخدم قد سجل بالفعل.",
"MyOrganizations": "منظماتى",
"ApiKey": "مفتاح API",
"UserNameNotFound": "لا يوجد مستخدم باسم {0}",
"SuccessfullyAddedToNewsletter": "شكرا لك على الاشتراك في النشرة الإخبارية لدينا!",
"MyProfile": "ملفي",
"EmailNotValid": "من فضلك أدخل بريد أليكترونى صحيح."
}
}

68
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json

@ -0,0 +1,68 @@
{
"culture": "ar",
"texts": {
"Permission:CommunityArticle": "مقالة المجتمع",
"Permission:Edit": "تعديل",
"Waiting": "انتظار",
"Approved": "تمت الموافقة",
"Rejected": "مرفوض",
"Wait": "انتظر",
"Approve": "موافق",
"Reject": "رفض",
"ReadArticle": "إقرأ المقال",
"Status": "الحالة",
"ContentSource": "مصدر المحتوى",
"Details": "التفاصيل",
"Url": "عنوان الموقع",
"Title": "العنوان",
"CreationTime": "وقت الإنشاء",
"Save": "حفظ",
"SameUrlAlreadyExist": "نفس عنوان الموقع موجود بالفعل إذا كنت تريد إضافة هذه المقالة ، يجب عليك تغيير عنوانها!",
"UrlIsNotValid": "عنوان الموقع غير صحيح.",
"UrlNotFound": "عنوان الموقع غير موجود.",
"UrlContentNotFound": "محتوى الموقع غير موجود.",
"Summary": "الخلاصة",
"MostRead": "الأكثر قراءة",
"Latest": "آخر",
"ContributeAbpCommunity": "المساهمة في مجتمع ABP",
"SubmitYourArticle": "إرسال مقالتك",
"ContributionGuide": "دليل المساهمة",
"BugReport": "الإبلاغ عن خطأ",
"SeeAllArticles": "انظر جميع المقالات",
"WelcomeToABPCommunity!": "مرحبًا بك في مجتمع ABP!",
"MyProfile": "ملفى",
"MyOrganizations": "منظماتي",
"EmailNotValid": "من فضلك أدخل بريد أليكترونى صحيح.",
"FeatureRequest": "طلب خاصية جديدة",
"CreateArticleTitleInfo": "عنوان المقال الذي سيظهر في قائمة المقالات.",
"CreateArticleUrlInfo": "الأصل GitHub / عنوان URL خارجي للمقالة.",
"CreateArticleSummaryInfo": "ملخص قصير للمقال ليتم عرضه في قائمة المقالات.",
"CreateArticleCoverInfo": "لإنشاء مقال فعال ، أضف صورة غلاف. لن يتم قبول سوى صور بنسبة عرض إلى ارتفاع تبلغ 9:16!",
"ThisExtensionIsNotAllowed": "امتداد الملف غير مسموح.",
"TheFileIsTooLarge": "مسار الملف طويل جداً",
"GoToTheArticle": "الذهاب للمقال",
"Contribute": "ساهم",
"OverallProgress": "التقدم الكلى",
"Done": "تم",
"Open": "افتح",
"Closed": "أُغلق",
"LatestQuestionOnThe": "آخر سؤال فى",
"Stackoverflow": "Stackoverflow",
"Votes": "تصويتات",
"Answer": "إجابة",
"Views": "مشاهدة",
"Answered": "تمت الإجابة عنه",
"WaitingForYourAnswer": "بانتظار إجابتك",
"Asked": "تم السؤال",
"AllQuestions": "كل الأسئلة",
"NextVersion": "الإصدار القادم",
"MilestoneErrorMessage": "تعذر الحصول على تفاصيل المعالم الحالية من Github.",
"QuestionItemErrorMessage": "تعذر الحصول على أحدث تفاصيل السؤال من Stackoverflow.",
"Oops": "عفواً!",
"CreateArticleSuccessMessage": "تم تقديم المقال بنجاح. سيتم نشره بعد مراجعة من إدارة الموقع.",
"ChooseCoverImage": "اختر صورة الغلاف...",
"CoverImage": "صورة الغلاف",
"ShareYourExperiencesWithTheABPFramework": "شارك خبراتك مع إطار عمل برنامج ABP!",
"Optional": "اختيارى"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -82,6 +82,7 @@
"SeeAllAnnouncements": "See All Announcements",
"LatestBlogPost": "Latest Blog Post",
"Edit": "Edit",
"ProfileImageChange": "Change the profile image"
"ProfileImageChange": "Change the profile image",
"BlogItemErrorMessage": "Could not get the latest blog post details from ABP."
}
}

161
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ar.json

@ -0,0 +1,161 @@
{
"culture": "ar",
"texts": {
"GetStarted": "ابدأ - قوالب بدء التشغيل",
"Create": "إنشاء",
"NewProject": "مشروع جديد",
"DirectDownload": "تحميل مباشر",
"ProjectName": "اسم المشروع",
"ProjectType": "نوع المشروع",
"DatabaseProvider": "مزود قاعدة البيانات",
"NTier": "N- الطبقة",
"IncludeUserInterface": "تشمل واجهة المستخدم",
"CreateNow": "إنشاء جديد",
"TheStartupProject": "مشروع بدء التشغيل",
"Tutorial": "الدورة التعليمية",
"UsingCLI": "باستخدام CLI",
"SeeDetails": "انظر التفاصيل",
"AbpShortDescription": "ABP Framework بنية أساسية كاملة لإنشاء تطبيقات ويب حديثة باتباع أفضل ممارسات واتفاقيات تطوير البرامج.",
"SourceCodeUpper": "شفرة المصدر",
"LatestReleaseLogs": "أحدث سجلات الإصدار",
"Infrastructure": "البنية التحتية",
"Architecture": "الهيكلة",
"Modular": "معياري",
"DontRepeatYourself": "لا تكرر نفسك",
"DeveloperFocused": "تركيز المطور",
"FullStackApplicationInfrastructure": "البنية التحتية الكاملة للتطبيق.",
"DomainDrivenDesign": "Domain Driven Design",
"DomainDrivenDesignExplanation": "تم تصميمه وتطويره بناءً على أنماط ومبادئ DDD. يوفر نموذجًا متعدد الطبقات لتطبيقك.",
"Authorization": "تفويض",
"AuthorizationExplanation": "إذن متقدم مع نظام أذونات مستخدم ودور ودقيق. مبني على مكتبة هوية Microsoft.",
"MultiTenancy": "متعدد الايجار",
"MultiTenancyExplanationShort": "جعلت تطبيقات SaaS سهلة! متعدد الإيجارات المتكاملة من قاعدة البيانات إلى واجهة المستخدم.",
"CrossCuttingConcerns": "اهتمامات مشتركة",
"CrossCuttingConcernsExplanationShort": "بنية تحتية كاملة للتفويض والتحقق من الصحة ومعالجة الاستثناءات والتخزين المؤقت وتسجيل التدقيق وإدارة المعاملات والمزيد.",
"BuiltInBundlingMinification": "تجميع وتصغير مدمج",
"BuiltInBundlingMinificationExplanation": "لا حاجة لاستخدام أدوات خارجية للتجميع والتصغير. تقدم ABP طريقة أبسط وديناميكية وقوية ومتكاملة ومدمجة!",
"VirtualFileSystem": "نظام الملفات الافتراضي",
"VirtualFileSystemExplanation": "قم بتضمين العروض والنصوص والأنماط والصور ... في الحزم / المكتبات وإعادة استخدامها في تطبيقات مختلفة.",
"Theming": "تصميم",
"ThemingExplanationShort": "استخدم وتخصيص سمة واجهة المستخدم القياسية القائمة على التمهيد أو قم بإنشاء سمة خاصة بك.",
"BootstrapTagHelpersDynamicForms": "أدوات مساعدة علامة التمهيد والنماذج الديناميكية",
"BootstrapTagHelpersDynamicFormsExplanation": "بدلاً من كتابة التفاصيل المتكررة لمكونات التمهيد يدويًا ، استخدم مساعدي علامات ABP لتبسيطها والاستفادة من التحسس. أنشئ نماذج واجهة مستخدم بسرعة استنادًا إلى نموذج C # باستخدام مساعد علامة النموذج الديناميكي.",
"HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies",
"HTTPAPIsDynamicProxiesExplanation": "كشف خدمات التطبيقات تلقائيًا على أنها واجهات برمجة تطبيقات HTTP بنمط REST ، واستهلكها باستخدام وكلاء JavaScript و C # الديناميكيين.",
"CompleteArchitectureInfo": "هندسة معمارية حديثة لإنشاء حلول برمجية قابلة للصيانة.",
"DomainDrivenDesignBasedLayeringModelExplanation": "يساعدك على تنفيذ بنية طبقات تستند إلى DDD وبناء قاعدة رمز قابلة للصيانة.",
"DomainDrivenDesignBasedLayeringModelExplanationCont": "يوفر قوالب بدء التشغيل ، والملخصات ، والفئات الأساسية ، والخدمات ، والوثائق والأدلة لمساعدتك على تطوير تطبيقك بناءً على أنماط ومبادئ DDD.",
"MicroserviceCompatibleModelExplanation": "تم تصميم إطار العمل الأساسي ووحدات ما قبل الإنشاء مع مراعاة بنية الخدمات المصغرة.",
"MicroserviceCompatibleModelExplanationCont": "يوفر البنية التحتية وعمليات الدمج والعينات والوثائق لتنفيذ حلول الخدمات المصغرة بشكل أسهل ، في حين أنه لا يجلب تعقيدًا إضافيًا إذا كنت تريد تطبيقًا مترابطًا.",
"ModularInfo": "يوفر ABP نظام وحدة نمطية يسمح لك بتطوير وحدات تطبيق قابلة لإعادة الاستخدام ، والربط بأحداث دورة حياة التطبيق ، والتعبير عن التبعيات بين الأجزاء الأساسية في نظامك.",
"PreBuiltModulesThemes": "الوحدات والسمات المبنية مسبقًا",
"PreBuiltModulesThemesExplanation": "الوحدات والسمات مفتوحة المصدر والتجارية جاهزة للاستخدام في تطبيق عملك.",
"NuGetNPMPackages": "حزم NuGet و NPM",
"NuGetNPMPackagesExplanation": "توزع كحزم NuGet و NPM. سهل التركيب والترقية.",
"ExtensibleReplaceable": "قابل للتوسيع / قابل للاستبدال",
"ExtensibleReplaceableExplanation": "تم تصميم جميع الخدمات والوحدات النمطية في الاعتبار قابلية التوسع. يمكنك استبدال الخدمات والصفحات والأنماط والمكونات.",
"CrossCuttingConcernsExplanation2": "اجعل قاعدة الشفرة أصغر حتى تتمكن من التركيز على الكود الخاص بنشاطك التجاري.",
"CrossCuttingConcernsExplanation3": "لا ترسل وقتًا لتنفيذ متطلبات التطبيق المشتركة في مشاريع متعددة.",
"AuthenticationAuthorization": "المصادقة والتفويض",
"ExceptionHandling": "معالجة الاستثناء",
"Validation": "التحقق من الصحة",
"DatabaseConnection": "اتصال قاعدة البيانات",
"TransactionManagement": "إدارة المعاملات",
"AuditLogging": "تدوين التسجيل",
"Caching": "التخزين المؤقت",
"Multitenancy": "التعددية",
"DataFiltering": "تصفية البيانات",
"ConventionOverConfiguration": "اتفاقية على التكوين",
"ConventionOverConfigurationExplanation": "تنفذ ABP اصطلاحات التطبيق الشائعة افتراضيًا باستخدام الحد الأدنى من التكوين أو صفر.",
"ConventionOverConfigurationExplanationList1": "التسجيل التلقائى للخدمات المعروفة بحقن التبعية.",
"ConventionOverConfigurationExplanationList2": "يعرض خدمات التطبيقات على أنها واجهات برمجة تطبيقات HTTP من خلال اصطلاحات التسمية.",
"ConventionOverConfigurationExplanationList3": "يقوم بإنشاء وكلاء عميل HTTP ديناميكي لـ C # و JavaScript.",
"ConventionOverConfigurationExplanationList4": "يوفر المستودعات الافتراضية للكيانات الخاصة بك.",
"ConventionOverConfigurationExplanationList5": "إدارة وحدة العمل حسب طلب الويب أو طريقة خدمة التطبيق.",
"ConventionOverConfigurationExplanationList6": "ينشر إنشاء وتحديث وحذف الأحداث للكيانات الخاصة بك.",
"BaseClasses": "الفئات الأساسية",
"BaseClassesExplanation": "فئات أساسية مسبقة الصنع لأنماط التطبيق الشائعة.",
"DeveloperFocusedExplanation": "ABP للمطورين.",
"DeveloperFocusedExplanationCont": "يهدف إلى تبسيط عملية تطوير البرامج اليومية مع عدم تقييدك من كتابة رمز منخفض المستوى.",
"SeeAllFeatures": "انظر جميع الميزات",
"CLI_CommandLineInterface": "CLI (واجهة سطر الأوامر)",
"CLI_CommandLineInterfaceExplanation": "يتضمن CLI لمساعدتك على أتمتة إنشاء مشاريع جديدة وإضافة وحدات نمطية جديدة.",
"StartupTemplates": "قوالب بدء التشغيل",
"StartupTemplatesExplanation": "توفر قوالب بدء التشغيل المختلفة حلاً مهيئًا بالكامل لبدء التطوير السريع.",
"BasedOnFamiliarTools": "بناءاً على أدوات معروفة",
"BasedOnFamiliarToolsExplanation": "مصمم ومتكامل مع الأدوات الشائعة التي تعرفها بالفعل. منحنى تعليمي منخفض ، سهل التكيف ، تنمية مريحة.",
"ORMIndependent": "ORM مستقل",
"ORMIndependentExplanation": "الإطار الأساسي هو ORM / قاعدة البيانات مستقل ويمكن أن يعمل مع أي مصدر بيانات. يتوفر مقدمو Entity Framework Core و MongoDB بالفعل.",
"Features": "استكشف ميزات إطار عمل ABP",
"ABPCLI": "ABP CLI",
"Modularity": "النمطية",
"BootstrapTagHelpers": "مساعدي علامة التمهيد",
"DynamicForms": "شاشات ديناميكية",
"BundlingMinification": "التجميع والتصغير",
"BackgroundJobs": "وظائف الخلفية",
"DDDInfrastructure": "البنية التحتية DDD",
"DomainDrivenDesignInfrastructure": "البنية التحتية للتصميم يحركها المجال",
"AutoRESTAPIs": "واجهات برمجة تطبيقات Auto REST",
"DynamicClientProxies": "وكلاء العميل الديناميكي",
"DistributedEventBus": "حافلة الحدث الموزعة",
"DistributedEventBusWithRabbitMQIntegration": "حافلة الحدث الموزعة مع تكامل RabbitMQ",
"TestInfrastructure": "اختبار البنية التحتية",
"AuditLoggingEntityHistories": "سجل التدقيق وتاريخ الكيان",
"ObjectToObjectMapping": "مطابقة كائن إلى كائن",
"EmailSMSAbstractions": "التجريد من البريد الإلكتروني والرسائل النصية القصيرة",
"EmailSMSAbstractionsWithTemplatingSupport": "تجريدات البريد الإلكتروني والرسائل النصية القصيرة مع دعم القوالب",
"Localization": "التعريب",
"SettingManagement": "إدارة الإعدادات",
"ExtensionMethods": "طرق التمديد",
"ExtensionMethodsHelpers": "طرق التمديد والمساعدين",
"AspectOrientedProgramming": "البرمجة الموجهة",
"DependencyInjection": "حقن التبعية",
"DependencyInjectionByConventions": "حقن التبعية بالاتفاقيات",
"ABPCLIExplanation": "ABP CLI (واجهة سطر الأوامر) هي أداة سطر أوامر لإجراء بعض العمليات الشائعة للحلول المستندة إلى ABP.",
"ModularityExplanation": "يوفر ABP بنية أساسية كاملة لبناء وحدات التطبيق الخاصة بك والتي قد تحتوي على كيانات وخدمات وتكامل قواعد البيانات وواجهات برمجة التطبيقات ومكونات واجهة المستخدم وما إلى ذلك ..",
"MultiTenancyExplanation": "لا يدعم إطار عمل ABP تطوير التطبيقات متعددة المستأجرين فحسب ، بل يجعل الكود الخاص بك في الغالب غير مدرك للتعددية المستأجرة.",
"MultiTenancyExplanation2": "يمكن تحديد المستأجر الحالي تلقائيًا ، وعزل بيانات المستأجرين المختلفين عن بعضهم البعض.",
"MultiTenancyExplanation3": "يدعم قاعدة بيانات واحدة وقاعدة بيانات لكل مستأجر ونهج مختلطة.",
"MultiTenancyExplanation4": "أنت تركز على رمز عملك وتدع إطار العمل للتعامل مع عقود الإيجار المتعددة نيابة عنك.",
"BootstrapTagHelpersExplanation": "بدلاً من كتابة التفاصيل المتكررة لمكونات التمهيد يدويًا ، استخدم مساعدي علامات ABP لتبسيطها والاستفادة من التحسس. يمكنك بالتأكيد استخدام Bootstrap متى احتجت إليه.",
"DynamicFormsExplanation": "يمكن لمساعدي النموذج الديناميكي وعلامات الإدخال إنشاء النموذج الكامل من فئة C # كنموذج.",
"AuthenticationAuthorizationExplanation": "خيارات المصادقة والترخيص الغنية المدمجة في ASP.NET Core Identity & IdentityServer4. يوفر نظام إذن موسع ومفصل.",
"CrossCuttingConcernsExplanation": "لا تكرر نفسك لتنفيذ كل هذه الأشياء الشائعة مرارًا وتكرارًا. ركز على رمز عملك ودع برنامج ABP يقوم بأتمتة ذلك من خلال الاتفاقيات.",
"DatabaseConnectionTransactionManagement": "اتصال قاعدة البيانات وإدارة المعاملات",
"CorrelationIdTracking": "تتبع معرف الارتباط",
"BundlingMinificationExplanation": "تقدم ABP نظام تجميع وتقليل بسيط وديناميكي وقوي ونمطي ومدمج.",
"VirtualFileSystemnExplanation": "يتيح نظام الملفات الظاهري إدارة الملفات غير الموجودة فعليًا على نظام الملفات (القرص). يتم استخدامه بشكل أساسي لتضمين ملفات (js ، css ، image ، cshtml ...) في التجميعات واستخدامها مثل الملفات الفعلية في وقت التشغيل.",
"ThemingExplanation": "يسمح نظام Theming بتطوير سمة التطبيق والوحدات النمطية الخاصة بك بشكل مستقل عن طريق تحديد مجموعة من المكتبات الأساسية المشتركة والتخطيطات ، بناءً على أحدث إطار عمل Bootstrap.",
"DomainDrivenDesignInfrastructureExplanation": "بنية تحتية كاملة لإنشاء تطبيقات متعددة الطبقات بناءً على أنماط ومبادئ التصميم المدفوع بالمجال ؛",
"Specification": "تخصيص",
"Repository": "مخزن",
"DomainService": "خدمة المجال",
"ValueObject": "كائن القيمة",
"ApplicationService": "خدمة التطبيق",
"DataTransferObject": "كائن نقل البيانات",
"AggregateRootEntity": "تجميع الجذر والكيان",
"AutoRESTAPIsExplanation": "يمكن لـ ABP تكوين خدمات التطبيقات الخاصة بك تلقائيًا كوحدات تحكم API حسب الاصطلاح.",
"DynamicClientProxiesExplanation": "استهلك واجهات برمجة التطبيقات الخاصة بك بسهولة من عملاء JavaScript و C#.",
"DistributedEventBusWithRabbitMQIntegrationExplanation": "انشر واستهلك الأحداث الموزعة بسهولة باستخدام ناقل الأحداث الموزع المدمج مع توفر تكامل RabbitMQ.",
"TestInfrastructureExplanation": "تم تطوير إطار العمل واختبار الوحدة والتكامل في الاعتبار. يوفر لك الفئات الأساسية لتسهيل الأمر. تأتي قوالب بدء التشغيل مع تكوين مسبق للاختبار.",
"AuditLoggingEntityHistoriesExplanation": "تسجيل تدقيق مدمج للتطبيقات المهمة للأعمال. الطلب والخدمة وتسجيل التدقيق على مستوى الطريقة وتاريخ الكيان مع تفاصيل مستوى الملكية.",
"EmailSMSAbstractionsWithTemplatingSupportExplanation": "تفصل تجريدات IEmailSender و ISmsSender منطق التطبيق الخاص بك عن البنية التحتية. يسمح نظام قوالب البريد الإلكتروني المتقدم بإنشاء قوالب البريد الإلكتروني وتعريبها واستخدامها بسهولة عند الحاجة.",
"LocalizationExplanation": "يسمح نظام التعريب بإنشاء موارد في ملفات JSON عادية واستخدامها لتعريب واجهة المستخدم الخاصة بك. وهو يدعم السيناريوهات المتقدمة مثل الوراثة والإضافات وتكامل JavaScript بينما يتوافق تمامًا مع نظام توطين AspNet Core.",
"SettingManagementExplanation": "حدد إعدادات التطبيق الخاص بك واحصل على القيم في وقت التشغيل بناءً على التكوين الحالي والمستأجر والمستخدم.",
"ExtensionMethodsHelpersExplanation": "لا تكرر نفسك حتى مع أجزاء التعليمات البرمجية البسيطة. الإضافات والمساعدات للأنواع القياسية تجعل التعليمات البرمجية أكثر وضوحًا وسهولة في الكتابة.",
"AspectOrientedProgrammingExplanation": "يوفر بنية تحتية مريحة لإنشاء وكلاء ديناميكيين وتنفيذ البرمجة الموجهة نحو الجانب. اعترض أي فئة وقم بتنفيذ التعليمات البرمجية الخاصة بك قبل وبعد تنفيذ كل طريقة.",
"DependencyInjectionByConventionsExplanation": "لا حاجة لتسجيل فصولك في حقن التبعية يدويًا. يسجل تلقائيًا أنواع الخدمات الشائعة حسب الاصطلاح. بالنسبة لأنواع الخدمات الأخرى ، يمكنك استخدام الواجهات والسمات لتسهيل الأمر ووضعه في مكانه.",
"DataFilteringExplanation": "تحديد واستخدام عوامل تصفية البيانات التي يتم تطبيقها تلقائيًا عند الاستعلام عن الكيانات من قاعدة البيانات. يتم توفير مرشحات Soft Delete & MultiTenant خارج الصندوق عند تنفيذ واجهات بسيطة.",
"PublishEvents": "نشر الأحداث",
"HandleEvents": "معالجة الأحداث",
"AndMore": "وأكثر من ذلك ...",
"Code": "شفرة",
"Result": "النتيجة",
"SeeTheDocumentForMoreInformation": "راجع الـ <a href=\"{1}\">{0} الوثيقة</a> لمزيد من المعلومات",
"IndexPageHeroSection": "<span class=\"first-line shine\"><strong>فتح المصدر</strong></span><span class=\"second-line text-uppercase\">تطبيق الويب<br />إطار </span><span class=\"third-line shine2\"><strong>لـ asp.net core</strong></span>",
"UiFramework": "إطار عمل واجهة المستخدم",
"EmailAddress": "البريد الإلكترونى",
"Mobile": "المحمول",
"ReactNative": "React Native"
}
}

15
docs/en/.vscode/settings.json

@ -0,0 +1,15 @@
{
"grammarly.userWords": [
"api",
"apiName",
"cli",
"defaultProject",
"formatter",
"md",
"monorepo",
"ngsw",
"npx",
"pwa",
"rootNamespace"
]
}

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

@ -364,9 +364,9 @@ ABP Framework doesn't have the same service. Instead, use `ICurrentUser` and `IC
ABP Framework extends the [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing the authorization system to be usable in the [application services](Application-Services.md) too.
#### AbpAutorize vs Autorize
#### AbpAuthorize vs Authorize
Use the standard `[Autorize]` and `[AllowAnonymous]` attributes instead of ASP.NET Boilerplate's custom `[AbpAutorize]` and `[AbpAllowAnonymous]` attributes.
Use the standard `[Authorize]` and `[AllowAnonymous]` attributes instead of ASP.NET Boilerplate's custom `[AbpAuthorize]` and `[AbpAllowAnonymous]` attributes.
#### IPermissionChecker vs IAuthorizationService
@ -672,9 +672,9 @@ It was like `abp.localization.localize(...)` in the ASP.NET Boilerplate.
### Navigation vs Menu
In ASP.NET you create a class deriving from the `NavigationProvider` to define your menu elements. Menu items has `requiredPermissionName` attributes to restrict access to a menu element. Menu items were static and your class is executed only one time.
In ASP.NET Boilerplate you create a class deriving from the `NavigationProvider` to define your menu elements. Menu items has `requiredPermissionName` attributes to restrict access to a menu element. Menu items were static and your class is executed only one time.
Int the ABP Framework you need to create a class implements the `IMenuContributor` interface. Your class is executed whenever the menu needs to be rendered. So, you can conditionally add menu items.
In the ABP Framework you need to create a class implements the `IMenuContributor` interface. Your class is executed whenever the menu needs to be rendered. So, you can conditionally add menu items.
As an example, this is the menu contributor of the tenant management module:

44
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md

@ -1,6 +1,6 @@
# ABP Framework & ABP Commercial 3.2 RC With The New Blazor UI
We are extremely excited today to release the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/), as always) version `3.2.0-rc.1` (Release Candidate). This release includes an early preview version of the **Blazor UI** for the ABP.IO Platform.
We are extremely excited today to release the [ABP Framework](https://abp.io/) Release Candidate (and the [ABP Commercial](https://commercial.abp.io/), as always). This release includes an early preview version of the **Blazor UI** for the ABP.IO Platform.
## The Blazor UI
@ -36,7 +36,7 @@ We also have a good news: **[Mladen Macanović](https://github.com/stsrki)**, th
### The Tutorial
We are currently in progress of updating the [web application development tutorial](https://docs.abp.io/en/abp/3.2/Tutorials/Part-1) for the Blazor UI. Follow the [@abpframework](https://twitter.com/abpframework) Twitter account to get informed once it's ready.
We've **updated** the [web application development tutorial](https://docs.abp.io/en/abp/3.2/Tutorials/Part-1?UI=Blazor) for the **Blazor UI**. You can start to develop applications today! The **source code** of the BookStore application developed with this tutorial is [here](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore).
### Get started with the Blazor UI
@ -44,12 +44,10 @@ If you want to try the Blazor UI today, follow the instructions below.
#### Upgrade the ABP CLI
> **Known issue**: When you upgrade the ABP CLI to `3.2.0-rc.1`, you won't be able to create new solutions with a stable version ([#5453](https://github.com/abpframework/abp/issues/5453)). Downgrade to `3.1.0` back after trying the preview version, if you want to create solutions with a stable version later.
Install the latest [ABP CLI](https://docs.abp.io/en/abp/3.2/CLI) preview version:
````bash
dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.1
dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.2
````
#### Create a new Solution
@ -70,10 +68,6 @@ Open the generated solution using the latest Visual Studio 2019. You will see a
![visual-studio-solution-with-blazor](visual-studio-solution-with-blazor.png)
> **A fix for the 3.2.0-rc.1**
>
> There is a bug in the `3.2.0-rc.1` that prevents `HttpApi.Host` project run properly, when you try to login to the application. **Follow the steps explained in the [#5457](https://github.com/abpframework/abp/issues/5457) for the `HttpApi.Host` project** to fix it for your solution, before running it. It will be resolved with the `3.2.0-rc.2`.
#### Run the Application
* Run the `DbMigrator` project to create the database and seed the initial data.
@ -94,9 +88,37 @@ Beside the Blazor UI, there are a lot of issues have been closed with [the miles
[MongoDB integration](https://docs.abp.io/en/abp/3.2/MongoDB) now supports multi-document transactions that comes with the MongoDB 4.x.
> Transactions are disabled for automated integration tests coming with the application startup template, since the Mongo2Go library (we use in the test projects) has a problem with the transactions. We've sent a [Pull Request](https://github.com/Mongo2Go/Mongo2Go/pull/101) to fix it and will enable the transactions again when they merge & release it.
We've **disabled transactions** for solutions use the MongoDB, inside the `YourProjectMongoDbModule.cs` file in the MongoDB project. If your MongoDB server **supports transactions**, you should manually enable it in this class:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
```
> Or you can delete this code since this is already the default behavior.
#### Upgrade Notes
If you are upgrading an existing solution and your MongoDB server doesn't support transactions, please disable it:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
});
```
See the [Unit Of Work document](https://docs.abp.io/en/abp/3.2/Unit-Of-Work) to learn more about UOW and transactions.
Also, add [this file](https://github.com/abpframework/abp/blob/rel-3.2/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MongoDb/MongoDbMyProjectNameDbSchemaMigrator.cs) into your MongoDB project (remember to change `MongoDbMyProjectNameDbSchemaMigrator` and `IMyProjectNameDbSchemaMigrator` with your own project name).
#### Integration Tests
> Transactions are also **disabled for automated integration tests** coming with the application startup template, since the [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) library (we use in the test projects) has a problem with the transactions. We've sent a [Pull Request](https://github.com/Mongo2Go/Mongo2Go/pull/101) to fix it and will enable the transactions again when they merge & release it.
>
> If you are upgrading an existing solution and using MongoDB, please disable transactions for the test projects by following the [Unit Of Work](https://docs.abp.io/en/abp/3.2/Unit-Of-Work) documentation.
> If you are upgrading an existing solution and using MongoDB, please disable transactions for the test projects just as described above.
### Kafka Integration for the Distributed Event Bus

47
docs/en/Blog-Posts/2020-10-01 v3_2_Release_Stable/POST.md

@ -0,0 +1,47 @@
# ABP Framework 3.2 Final Has Been Released
ABP Framework & ABP Commercial 3.2 have been released today.
Since all the new features are already explained in details with the [3.2 RC Announcement Post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI), I will not repeat all the details again. Please read [the RC post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI) for **new feature and changes** you may need to do for your solution while upgrading to the version 3.2.
## Creating New Solutions
You can create a new solution with the ABP Framework version 3.2 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started).
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details.
## How to Upgrade an Existing Solution
### Install/Update the ABP CLI
First of all, install the ABP CLI or upgrade to the latest version.
If you haven't installed yet:
````bash
dotnet tool install -g Volo.Abp.Cli
````
To update an existing installation:
```bash
dotnet tool update -g Volo.Abp.Cli
```
### ABP UPDATE Command
[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command:
````bash
abp update
````
After the update command, check [the RC blog post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI) to learn if you need to make any changes in your solution.
> You may want to see the new [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading).
## About the Next Versions
The next two versions (3.3 & 4.0) will be mostly related to completing the Blazor UI features and upgrading the ABP Framework & ecosystem to the .NET 5.0.
The ultimate goal is to complete the version 4.0 with a stable Blazor UI with the fundamental features implemented and publish it just after the Microsoft lunches .NET 5 in this November.

3
docs/en/CLI.md

@ -180,7 +180,8 @@ 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.
* `--with-source-code`: Downloads the source code of the module to your solution folder and uses local project references instead of NuGet/NPM packages.
* `--add-to-solution-file`: Adds the downloaded module to your solution file, so you will also see the projects of the module when you open the solution on a IDE. (only available when `--with-source-code` is used.)
### get-source

258
docs/en/Caching.md

@ -1,154 +1,167 @@
# Caching
ABP framework extends ASP.NET Core's distributed caching system.
ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
## Volo.Abp.Caching Package
## Installation
> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually.
> This package is already installed by default with the [application startup template](Startup-Templates/Application.md). So, most of the time, you don't need to install it manually.
Volo.Abp.Caching is the core package of the caching system. Install it to your project using the package manager console (PMC):
[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) is the main package of the caching system. You can install it a project using the add-package command of the [ABP CLI](CLI.md):
```
Install-Package Volo.Abp.Caching
abp add-package Volo.Abp.Caching
```
Then you can add **AbpCachingModule** dependency to your module:
You need to run this command on a command line terminal in a folder containing a `csproj` file (see [other options](https://abp.io/package-detail/Volo.Abp.Caching) to install).
```c#
using Volo.Abp.Modularity;
using Volo.Abp.Caching;
## Usage
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpCachingModule))]
public class MyModule : AbpModule
{
//...
}
}
```
## `IDistributedCache` Interface
### `IDistributedCache` Interface
ASP.NET Core defines the `IDistributedCache` interface to get/set cache values. But it has some difficulties:
ASP.NET Core defines the `IDistributedCache` interface to get/set the cache values. But it has some difficulties:
* It works with **byte arrays** rather than .NET objects. So, you need to **serialize/deserialize** the objects you need to cache.
* It provides a **single key pool** for all cache items, so;
* You need to care about the keys to distinguish **different type of objects**.
* You need to care about the cache items of **different tenants** (see [multi-tenancy](Multi-Tenancy.md)).
* You need to care about the cache items of **different tenants** in a [multi-tenant](Multi-Tenancy.md) system.
> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**.
> Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works **in-memory**. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to **Redis** or another cache provider.
> Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works **in-memory**. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server.
See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information.
## `IDistributedCache<TCacheItem>` Interface
### `IDistributedCache<TCacheItem>` Interface
ABP framework defines the generic `IDistributedCache<TCacheItem>` interface in the [Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching/) package. `TCacheItem` is the type of the object stored in the cache.
`IDistributedCache<TCacheItem>` solves the difficulties explained above;
* It internally **serializes/deserializes** the cached objects. Uses **JSON** serialization by default, but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](Dependency-Injection.md) system.
* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. Default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the `CacheName` attribute on the cache item class to set the cache name.
* It automatically adds the **current tenant id** to the cache key to distinguish cache items for different tenants (only works if your application is [multi-tenant](Multi-Tenancy.md)). Define `IgnoreMultiTenancy` attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application.
* Allows to define a **global cache key prefix** per application, so different applications can use their isolated key pools in a shared distributed cache source.
* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. Default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the **`CacheName` attribute** on the cache item class to set the cache name.
* It automatically adds the **current tenant id** to the cache key to distinguish cache items for different tenants (if your application is [multi-tenant](Multi-Tenancy.md)). Define `IgnoreMultiTenancy` attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application.
* Allows to define a **global cache key prefix** per application, so different applications can use their isolated key pools in a shared distributed cache server.
* It **can tolerate errors** wherever possible and bypasses the cache. This is useful when you have temporary problems on the cache server.
* It has methods like `GetManyAsync` and `SetManyAsync` which significantly improve the performance on **batch operations**.
### Usage
An example class to store an item in the cache:
**Example: Store Book names and prices in the cache**
````csharp
public class BookCacheItem
namespace MyProject
{
public string Name { get; set; }
public class BookCacheItem
{
public string Name { get; set; }
public float Price { get; set; }
public float Price { get; set; }
}
}
````
You can inject and use the `IDistributedCache<BookCacheItem>` service to get/set `BookCacheItem` objects.
Example usage:
You can inject and use the `IDistributedCache<BookCacheItem>` service to get/set `BookCacheItem` objects:
````csharp
public class BookService : ITransientDependency
{
private readonly IDistributedCache<BookCacheItem> _cache;
public BookService(IDistributedCache<BookCacheItem> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId.ToString(), //Cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
namespace MyProject
{
public class BookService : ITransientDependency
{
//TODO: get from database
private readonly IDistributedCache<BookCacheItem> _cache;
public BookService(IDistributedCache<BookCacheItem> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId.ToString(), //Cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
{
//TODO: get from database
}
}
}
````
* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache.
* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache. `GetOrAddAsync` is an additional method that was added by the ABP Framework to the standard ASP.NET Core distributed cache methods.
* If the book was not found in the cache, it calls the factory method (`GetBookFromDatabaseAsync` in this case) to retrieve the book item from the original source.
* `GetOrAddAsync` optionally gets a `DistributedCacheEntryOptions` which can be used to set the lifetime of the cached item.
Other methods of the `IDistributedCache<BookCacheItem>` are same as ASP.NET Core's `IDistributedCache` interface, so you can refer [it's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
`IDistributedCache<BookCacheItem>` supports the same methods of the ASP.NET Core's standard `IDistributedCache` interface, so you can refer [it's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
## `IDistributedCache<TCacheItem, TCacheKey>` Interface
### `IDistributedCache<TCacheItem, TCacheKey>` Interface
`IDistributedCache<TCacheItem>` interface assumes that the type of your cache key is `string` (so, you need to manually convert your key to string if you need to use a different kind of cache key). `IDistributedCache<TCacheItem, TCacheKey>` can be used when your cache key type is not `string`.
`IDistributedCache<TCacheItem>` interface assumes that the type of your **cache key** is `string` (so, you need to manually convert your key to string if you need to use a different kind of cache key). While this is not a big deal, `IDistributedCache<TCacheItem, TCacheKey>` can be used when your cache key type is not `string`.
### Usage
An example class to store an item in the cache:
**Example: Store Book names and prices in the cache**
````csharp
public class BookCacheItem
using Volo.Abp.Caching;
namespace MyProject
{
public string Name { get; set; }
[CacheName("Books")]
public class BookCacheItem
{
public string Name { get; set; }
public float Price { get; set; }
public float Price { get; set; }
}
}
````
Example usage (assumes that your cache key type is `Guid`):
* This example uses the `CacheName` attribute for the `BookCacheItem` class to set the cache name.
````csharp
public class BookService : ITransientDependency
{
private readonly IDistributedCache<BookCacheItem, Guid> _cache;
You can inject and use the `IDistributedCache<BookCacheItem, Guid>` service to get/set `BookCacheItem` objects:
public BookService(IDistributedCache<BookCacheItem, Guid> cache)
{
_cache = cache;
}
````csharp
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId, //Guid type used as the cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
namespace MyProject
{
public class BookService : ITransientDependency
{
//TODO: get from database
private readonly IDistributedCache<BookCacheItem, Guid> _cache;
public BookService(IDistributedCache<BookCacheItem, Guid> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId, //Guid type used as the cache key
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
{
//TODO: get from database
}
}
}
````
@ -156,6 +169,8 @@ public class BookService : ITransientDependency
* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache.
* Since cache explicitly implemented as using `Guid` as cache key, `Guid` value passed to `_cache_GetOrAddAsync()` method.
#### Complex Types as the Cache Key
`IDistributedCache<TCacheItem, TCacheKey>` internally uses `ToString()` method of the key object to convert it to a string. If you need to use a complex object as the cache key, you need to override `ToString` method of your class.
An example class that is used as a cache key:
@ -192,7 +207,66 @@ public class BookService : ITransientDependency
}
````
### DistributedCacheOptions
## Configuration
### AbpDistributedCacheOptions
`AbpDistributedCacheOptions` is the main [options class](Options.md) to configure the caching.
**Example: Set the cache key prefix for the application**
```csharp
Configure<AbpDistributedCacheOptions>(options =>
{
options.KeyPrefix = "MyApp1";
});
```
> Write that code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md).
#### Available Options
* `HideErrors` (`bool`, default: `true`): Enables/disables hiding the errors on writing/reading values from the cache server.
* `KeyPrefix` (`string`, default: `null`): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items.
* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. Default value uses the `SlidingExpiration` as 20 minutes.
## Error Handling
When you design a cache for your objects, you typically try to get the value from cache first. If not found in the cache, you query the object from the **original source**. It may be located in a **database** or may require to perform an HTTP call to a remote server.
In most cases, you want to **tolerate the cache errors**; If you get error from the cache server you don't want to cancel the operation. Instead, you silently hide (and log) the error and **query from the original source**. This is what the ABP Framework does by default.
ABP's Distributed Cache [handle](Exception-Handling.md), log and hide errors by default. There is an option to change this globally (see the options below).
In addition, all of the `IDistributedCache<TCacheItem>` (and `IDistributedCache<TCacheItem, TCacheKey>`) methods have an optional `hideErrors` parameter, which is `null` by default. The global value is used if this parameter left as `null`, otherwise you can decide to hide or throw the exceptions for individual method calls.
## Batch Operations
ABP's distributed cache interfaces provide methods to perform batch get/set methods those improves the performance when you want to get or set multiple cache items in a single method call.
* `SetManyAsync` and `SetMany` methods can be used to set multiple values to the cache.
* `GetManyAsync` and `GetMany` methods can be used to retrieve multiple values from the cache.
> These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by the [ABP Redis Cache integration package](Redis-Cache.md). If the provider doesn't support, it fallbacks to `SetAsync` and `GetAsync` methods (called once for each item).
## Advanced Topics
### Unit Of Work Level Cache
Distributed cache service provides an interesting feature. Assume that you've updated the price of a book in the database, then set the new price to the cache, so you can use the cached value later. What if you have an exception after setting the cache and you **rollback the transaction** that updates the price of the book? In this case, cache value will be incorrect.
`IDistributedCache<..>` methods gets an optional parameter, named `considerOuw`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](Unit-Of-Work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeed**.
### IDistributedCacheSerializer
`IDistributedCacheSerializer` service is used to serialize and deserialize the cache items. Default implementation is the `Utf8JsonDistributedCacheSerializer` class that uses `IJsonSerializer` service to convert objects to [JSON](Json.md) and vice verse. Then it uses UTC8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache.
You can [replace](Dependency-Injection.md) this service by your own implementation if you want to implement your own serialization logic.
### IDistributedCacheKeyNormalizer
`IDistributedCacheKeyNormalizer` is implemented by the `DistributedCacheKeyNormalizer` class by default. It adds cache name, application cache prefix and current tenant id to the cache key. If you need a more advanced key normalization, you can [replace](Dependency-Injection.md) this service by your own implementation.
TODO
## See Also
* [Redis Cache](Redis-Cache.md)

317
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/Using-DevExtremeAngularComponents-With-The-ABP-Framework.md

@ -0,0 +1,317 @@
# Using DevExtreme Angular Components With the ABP Framework
In this article, I will show you how to integrate the [DevExpress Angular components](https://js.devexpress.com/Documentation/Guide/Angular_Components/DevExtreme_Angular_Components/) to a project created using the ABP Framework startup templates. Then I will use the [DataGrid](https://js.devexpress.com/Documentation/Guide/Widgets/DataGrid/Overview/) component to show a list of users on the UI.
## Create the Project
Let's create a new web application with the Angular UI using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI#new):
```shell
abp new DevExtremeAngular -u angular
```
> For detail information about how to generate and start up a project, please refer to the [official docs](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No). For the scope of this post, we will not go into details of the backend applications.
## Running the Solution
### The Server Side
Server side contains multiple projects in the solution:
![A screenshot showing DevExtremeAngular.DbMigrator and DevExtremeAngular.HttpApi.Host](project-setup.png)
Run following projects in order;
* Run `DevExtremeAngular.DbMigrator` to create the database and seed the initial data.
* Run `DevExtremeAngular.HttpApi.Host` project to make the backend API up & running.
### The Angular Application
Open a command line terminal and navigate to `angular` folder then run `yarn` or `npm install` based on which package you are using.
After installation process is done, you can start your angular project by running `yarn start` or `npm start`. This command should serve the application and open the application in your default browser. If it doesn't open, you can navigate to http://localhost:4200 in your browser:
![A screenshot showing localhost is running](localhost-running.png)
You can login to the application by using following credentials:
> _Default admin username is **admin** and password is **1q2w3E\***_
![A screenshot showing login page](login-screen.png)
After successful login, you should be redirected to home page.
## Install DevExtreme
You can follow [the guide](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Add_DevExtreme_to_an_Angular_CLI_Application/) provided by **DevExtreme** team or apply the following steps.
* `npm install devextreme devextreme-angular` or `yarn add devextreme devextreme-angular`
* Import given two following styles in `angular.json` file:
```javascript
// ...
"styles": [
// ...
"src/styles.scss",
"node_modules/devextreme/dist/css/dx.common.css",
"node_modules/devextreme/dist/css/dx.light.css"
]
```
* Add `dx-viewport` to classes of `body` in `index.html`
```html
<body class="bg-light dx-viewport">
<app-root>
<div class="donut centered"></div>
</app-root>
</body>
```
After completing these steps, you need to restart the angular application.
## Create a lazy Angular Module for DevExtreme Demo
Let's create a module which will be loaded lazily.
Open up a terminal and navigate to `angular` to run following command.
```shell
ng g m dev-extreme --route dev-extreme --module app
```
...or with `npx`, if you do not have `angular-cli` installed...
```shell
npx ng g m dev-extreme --route dev-extreme --module app
```
Your terminal should log the following output:
```shell
CREATE src/app/dev-extreme/dev-extreme-routing.module.ts (361 bytes)
CREATE src/app/dev-extreme/dev-extreme.module.ts (379 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.scss (0 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.html (26 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.spec.ts (655 bytes)
CREATE src/app/dev-extreme/dev-extreme.component.ts (295 bytes)
UPDATE src/app/app-routing.module.ts (362 bytes)
```
The Angular CLI has created a module and configured it to lazy-load at `/dev-extreme` path.
The last step to be able to see our newly created module in the browser, open `route.provider.ts` and edit the array being added into the routes.
```typescript
// ...
routes.add([
{
path: '/',
name: '::Menu:Home',
iconClass: 'fas fa-home',
order: 1,
layout: eLayoutType.application,
},
{
path: '/dev-extreme',
name: 'Dev Extreme',
order: 2,
layout: eLayoutType.application,
},
]);
// ...
```
After completing the steps above, you should be able to see `Dev Extreme` on the header and when you click on it, you should be redirected to `/dev-extreme` page and see the following message on the screen.
![A screenshot showing a page that says dev-extreme works!](dev-extreme-page.png)
## Display users on the dev-extreme page
For this demo, we will list users on the screen. We already have `admin` as our first user.
Let's add couple of more to the list in `Administration -> Identity Management -> Users` page.
![A screenshot showing users page after adding couple of users](users.png)
Now we are ready to fetch our users and display them on `/dev-extreme` page.
Firstly, let's create a service for our component.
Navigate to the `dev-extreme` folder and run following command. If you run this command at the root, the service will be generated next to `app.module.ts`
```shell
ng g s dev-extreme
```
Following files should be created
```shell
CREATE src/app/dev-extreme/dev-extreme.service.spec.ts (378 bytes)
CREATE src/app/dev-extreme/dev-extreme.service.ts (139 bytes)
```
Let's import and inject `IdentityService` as dependency in `dev-extreme.service.ts`. After then, let's create a stream called `users$` to retrieve the users.
`identityService.getUsers` returns `ABP.PagedResponse` which contains two fields, `items` and `totalCount`. We are only interested in `items` for now.
When we apply the steps described above, the final version of `dev-extreme.service` should be as follows
```typescript
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';
import { IdentityService } from '@abp/ng.identity';
@Injectable({
providedIn: 'root',
})
export class DevExtremeService {
users$ = this.service.getUsers().pipe(map((result) => result.items));
constructor(private service: IdentityService) {}
}
```
Now we can simply inject `DevExtremeService` as public and utilize `users$` stream in `dev-extreme.component.ts` as follows:
```typescript
import { Component } from '@angular/core';
import { DevExtremeService } from './dev-extreme.service';
@Component({
selector: 'app-dev-extreme',
templateUrl: './dev-extreme.component.html',
styleUrls: ['./dev-extreme.component.scss'],
})
export class DevExtremeComponent {
constructor(public service: DevExtremeService) {}
}
```
And use it within `dev-extreme.component.html`
```html
<ng-container *ngIf="service.users$ | async as users">
<ul>
<li *ngFor="let user of users">
{{ user.name }}
</li>
</ul>
</ng-container>
```
This should list names of the users on the screen
![A screenshot showing list users on the dev extreme page](users-on-dev-extreme.png)
## Use DxDataGrid to list the users
You can take a look at [demo](https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/ColumnCustomization/Angular/Light/) provided by **DevExtreme** team or apply the following steps.
Now, our application is ready to use `dx-data-grid` in `dev-extreme.component.ts`
Firstly, we need to import `DxDataGridModule` in our module as follows.
```typescript
// ...
import { DxDataGridModule } from 'devextreme-angular';
@NgModule({
// ...
imports: [
// ...
DxDataGridModule
],
})
export class DevExtremeModule {}
```
At this point `dx-data-grid` is avaliable within our module and we can use it in our template.
Change `dev-extreme.component.html` to the following
```html
<ng-container *ngIf="service.users$ | async as users">
<dx-data-grid [dataSource]="users"></dx-data-grid>
</ng-container>
```
It should display a table on the screen
![A screenshot displaying dev extreme data grid with lots of columns](devextreme-first.png)
Since, we did not specify any columns, `dx-data-grid` displayed every column avaliable. Let's pick some columns to make it more readable.
Change `dev-extreme.component.html` to the following:
```html
<ng-container *ngIf="service.users$ | async as users">
<dx-data-grid [dataSource]="users">
<dxi-column dataField="userName"></dxi-column>
<dxi-column dataField="name"></dxi-column>
<dxi-column dataField="surname"></dxi-column>
<dxi-column dataField="email"></dxi-column>
<dxi-column dataField="phoneNumber"></dxi-column>
</dx-data-grid>
</ng-container>
```
which will display following table on the screen
![A screenshot displaying dev extreme data grid with these columns: username, name, surname, email and phone number](devextreme-second.png)
We can also utilize `abpLocalization` pipe to translate the headers of the table. To use `abpLocalization` pipe in our templates, we need to import `CoreModule` from `@abp/ng.core` into our module.
```typescript
import { CoreModule } from '@abp/ng.core';
@NgModule({
// ...
imports: [
// ...
CoreModule
],
})
export class DevExtremeModule {}
```
And change the template to the following:
```html
<ng-container *ngIf="service.users$ | async as users">
<dx-data-grid [dataSource]="users">
<dxi-column
dataField="userName"
[caption]="'AbpIdentity::DisplayName:UserName' | abpLocalization"
></dxi-column>
<dxi-column
dataField="name"
[caption]="'AbpIdentity::DisplayName:Name' | abpLocalization"
></dxi-column>
<dxi-column
dataField="surname"
[caption]="'AbpIdentity::DisplayName:Surname' | abpLocalization"
></dxi-column>
<dxi-column
dataField="email"
[caption]="'AbpIdentity::DisplayName:Email' | abpLocalization"
></dxi-column>
<dxi-column
dataField="phoneNumber"
[caption]="'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization"
></dxi-column>
</dx-data-grid>
</ng-container>
```
The headers should change when a new language is selected;
![A gif showing the headers of the table getting translated into the chosen language](devextreme-final.gif)
## Conclusion
In this article, we have seen how to integrate `DevExtreme` angular components into a project generated by `ABP CLI`.
You can download source code of [the demo here](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Angular).

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/dev-extreme-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-final.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-first.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-second.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/localhost-running.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/login-screen.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/project-setup.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users-on-dev-extreme.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

53
docs/en/Data-Filtering.md

@ -102,9 +102,58 @@ namespace Acme.BookStore
````
* [Inject](Dependency-Injection.md) the `IDataFilter` service to your class.
* Use the `Disable` method within a `using` statement to create a code block where the `ISoftDelete` filter is disabled inside it (Always use it inside a `using` block to guarantee that the filter is reset to its previous state).
* Use the `Disable` method within a `using` statement to create a code block where the `ISoftDelete` filter is disabled inside it.
`IDataFilter.Enable` method can be used to enable a filter. `Enable` and `Disable` methods can be used in a nested way to define inner scopes.
In addition to the `Disable<T>()` method;
* `IDataFilter.Enable<T>()` method can be used to enable a filter. `Enable` and `Disable` methods can be used in a **nested** way to define inner scopes.
* `IDataFilter.IsEnabled<T>()` can be used to check whether a filter is currently enabled or not.
> Always use the `Disable` and `Enable` methods it inside a `using` block to guarantee that the filter is reset to its previous state.
### The Generic IDataFilter Service
`IDataFilter` service has a generic version, `IDataFilter<TFilter>` that injects a more restricted and explicit data filter based on the filter type.
````csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class MyBookService : ITransientDependency
{
private readonly IDataFilter<ISoftDelete> _softDeleteFilter;
private readonly IRepository<Book, Guid> _bookRepository;
public MyBookService(
IDataFilter<ISoftDelete> softDeleteFilter,
IRepository<Book, Guid> bookRepository)
{
_softDeleteFilter = softDeleteFilter;
_bookRepository = bookRepository;
}
public async Task<List<Book>> GetAllBooksIncludingDeletedAsync()
{
//Temporary disable the ISoftDelete filter
using (_softDeleteFilter.Disable())
{
return await _bookRepository.GetListAsync();
}
}
}
}
````
* This usage determines the filter type while injecting the `IDataFilter<T>` service.
* In this case you can use the `Disable()` and `Enable()` methods without specifying the filter type.
## AbpDataFilterOptions

4
docs/en/Distributed-Event-Bus-Kafka-Integration.md

@ -89,7 +89,7 @@ Defining multiple connections is allowed. In this case, you can specify the conn
}
````
This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus.
This allows you to use multiple Kafka cluster in your application, but select one of them for the event bus.
You can use any of the [ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html) properties as the connection properties.
@ -164,4 +164,4 @@ Configure<AbpKafkaOptions>(options =>
});
````
Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file.
Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file.

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

@ -1,10 +1,57 @@
# Getting Started with the React Native
````json
//[doc-params]
{
"Tiered": ["No", "Yes"]
}
````
ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to your ABP based backends**.
When you **create a new application** as described in the [getting started document](Getting-Started.md), you have to add `-m react-native` option to include `react-native` project in your solution.
![React Native gif](./images/react-native-introduction.gif)
## How to Prepare Development Environment
Please follow the steps below to prepare your development environment for React Native.
1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v12 or v14 installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system.
2. **[Optional] Install Yarn:** You may install Yarn v1 (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v6 and below. You may skip this step and work with npm, which is built-in in Node.js, instead.
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to React Native projects.
4. **Install an Emulator:** React Native applications need an Android emulator or an iOS simulator to run on your OS. See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/) or [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) on expo.io documentation to learn how to set up an emulator.
## How to Start a New React Native Project
You have multiple options to initiate a new React Native project that works with ABP:
### 1. Using ABP CLI
ABP CLI is probably the most convenient and flexible way to initiate an ABP solution with a React Native application. Simply [install the ABP CLI](../../CLI.md) and run the following command in your terminal:
```shell
abp new MyCompanyName.MyProjectName -csf -u <angular or mvc> -m react-native
```
> To see further options in the CLI, please visit the [CLI manual](../../CLI.md).
This command will prepare a solution with an **Angular** or an **MVC** (depends on your choice), a **.NET Core**, and a **React Native** project in it.
### 2. Direct Download
You may [download a solution scaffold directly on ABP.io](https://abp.io/get-started) if you are more comfortable with GUI or simply want to try ABP without installing the CLI.
## Configure Your Local IP Address
Please do the following:
1. Click on the "DIRECT DOWNLOAD" tab.
2. Fill out the short form about your project.
3. Click on the "Create now" button.
...and a customized download will start in a few seconds.
## How to Configure & Run the Backend
> React Native application does not trust the auto-generated .NET HTTPS certificate. You should use **HTTP** during the development.
A React Native application running on an Android emulator or a physical phone **can not connect to the backend** on `localhost`. To fix this problem, it is necessary to run the backend application on your **local IP address**.
@ -25,19 +72,14 @@ A React Native application running on an Android emulator or a physical phone **
{{ end }}
## Run the Server Application
Run the backend application as described in the [getting started document](Getting-Started.md).
> React Native application does not trust the auto-generated .NET HTTPS certificate. You should use **HTTP** during the development.
Go to the `react-native` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com/) package manager while `npm install` will also work):
## How to Configure & Run the React Native Application
```bash
yarn
```
* Open the `Environment.js` in the `react-native` folder and replace the `localhost` address on the `apiUrl` and `issuer` properties with your local IP address as shown below:
1. Make sure the [database migration is complete](./Getting-Started?UI=NG&DB=EF&Tiered=No#create-the-database) and the [API is up and running](./Getting-Started?UI=NG&DB=EF&Tiered=No#run-the-application).
2. Open `react-native` folder and run `yarn` or `npm install` if you have not already.
3. Open the `Environment.js` in the `react-native` folder and replace the `localhost` address on the `apiUrl` and `issuer` properties with your local IP address as shown below:
![react native environment local IP](images/rn-environment-local-ip.png)
@ -51,22 +93,21 @@ yarn
{{ end }}
Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
```bash
yarn start
```
4. Run `yarn start` or `npm start`. Wait Expo CLI to start. Expo CLI opens the management interface on the `http://localhost:19002/` address.
Wait Expo CLI to start. Expo CLI opens the management interface on the `http://localhost:19002/` address.
> The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features.
![expo-interface](images/rn-expo-interface.png)
In the above management interface, you can start the application with an Android emulator, an iOS simulator or a physical phone by the scan the QR code with the [Expo Client](https://expo.io/tools#client).
> See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/), [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) documents on expo.io.
![React Native login screen on iPhone 11](images/rn-login-iphone.png)
Enter **admin** as the username and **1q2w3E*** as the password to login to the application.
The application is up and running. You can continue to develop your application based on this startup template.
## See Also
* [React Native project structure](./Startup-Templates/Application#react-native)

32
docs/en/Getting-Started.md

@ -43,24 +43,6 @@ The following tools should be installed on your development machine:
> ABP CLI is a free & open source tool for the ABP framework.
{{if UI == "Blazor"}}
BLAZOR UI IS ONLY AVAILABLE WITH THE PREVIEW VERSION. SO, YOU NEED TO INSTALL THE PREVIEW VERSION OF THE ABP CLI
First, you need to install the ABP CLI **RC.2** using the following command:
````shell
dotnet tool install Volo.Abp.Cli -g --version 3.2.0-rc.2
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.2
````
{{else}}
First, you need to install the ABP CLI using the following command:
````shell
@ -73,22 +55,14 @@ If you've already installed, you can update it using the following command:
dotnet tool update -g Volo.Abp.Cli
````
{{end}}
## Create a New Project
Use the `new` command of the ABP CLI to create a new project:
````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor --preview{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
````
{{if UI == "Blazor"}}
> BLAZOR UI IS ONLY AVAILABLE WITH THE PREVIEW VERSION. THIS IS WHY IT IS REQUIRED TO SET THE `--PREVIEW` OPTION.
{{end}}
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
> Alternatively, you can select the "Direct Download" tab from the [ABP Framework web site](https://abp.io/get-started) to create a new solution.
@ -124,10 +98,12 @@ The [startup template](Startup-templates/Index.md) **disables** transactions in
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
```
> Or you can delete this code since this is already the default behavior.
{{ end }}
## Create the Database

5
docs/en/Localization.md

@ -2,7 +2,7 @@
ABP's localization system is seamlessly integrated to the `Microsoft.Extensions.Localization` package and compatible with the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization). It adds some useful features and enhancements to make it easier to use in real life application scenarios.
## Volo.Abp.Localization Package
## Installation
> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually.
@ -242,4 +242,5 @@ Both of the samples above produce the output `Hello John, welcome!`.
## See Also
* [Localization in Angular UI](UI/Angular/Localization.md)
* [Localization in Angular UI](UI/Angular/Localization.md)
* [Forms & Validation](UI/AspNetCore/Forms-Validation.md) for the ASP.NET Core MVC / Razor Pages UI

2
docs/en/Modules/Identity.md

@ -2,7 +2,7 @@
Identity module is used to manage [organization units](Organization-Units.md), roles, users and their permissions, based on the Microsoft Identity library.
See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...
**See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...**
## Identity Security Log

4
docs/en/MongoDB.md

@ -282,10 +282,12 @@ MongoDB supports multi-document transactions starting from the version 4.0 and t
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
```
> Or you can delete this code since this is already the default behavior.
### Advanced Topics
#### Set Default Repository Classes

492
docs/en/Multi-Tenancy.md

@ -1,53 +1,67 @@
## Multi-Tenancy
# Multi-Tenancy
ABP Multi-tenancy module provides base functionality to create multi tenant applications.
Multi-Tenancy is a widely used architecture to create **SaaS applications** where the hardware and software **resources are shared by the customers** (tenants). ABP Framework provides all the base functionalities to create **multi tenant applications**.
Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) multi-tenancy as like that:
Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenancy as like that:
> Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants.
### Volo.Abp.MultiTenancy Package
## Terminology: Host vs Tenant
Volo.Abp.MultiTenancy package defines fundamental interfaces to make your code "multi-tenancy ready". So, install it to your project using the package manager console (PMC):
There are two main side of a typical SaaS / Multi-tenant application:
````
Install-Package Volo.Abp.MultiTenancy
````
* A **Tenant** is a customer of the SaaS application that pays money to use the service.
* **Host** is the company that owns the SaaS application and manages the system.
> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually.
The Host and the Tenant terms will be used for that purpose in the rest of the document.
Then you can add **AbpMultiTenancyModule** dependency to your module:
## Configuration
````C#
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
### AbpMultiTenancyOptions: Enable/Disable Multi-Tenancy
`AbpMultiTenancyOptions` is the main options class to **enable/disable the multi-tenancy** for your application.
**Example: Enable multi-tenancy**
namespace MyCompany.MyProject
```csharp
Configure<AbpMultiTenancyOptions>(options =>
{
[DependsOn(typeof(AbpMultiTenancyModule))]
public class MyModule : AbpModule
{
//...
}
}
````
options.IsEnabled = true;
});
```
> Multi-Tenancy is disabled in the ABP Framework by default. However, it is **enabled by default** when you create a new solution using the [startup template](Startup-Templates/Application.md). `MultiTenancyConsts` class in the solution has a constant to control it in a single place.
### Database Architecture
ABP Framework supports all the following approaches to store the tenant data in the database;
* **Single Database**: All tenants are stored in a single database.
* **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant.
* **Hybrid**: Some tenants share a single databases while some tenants may have their own databases.
> With the "Multi-tenancy ready" concept, we intent to develop our code to be compatible with multi-tenancy approach. Then it can be used in a multi-tenant application or not, depending on the requirements of the final application.
[Tenant management module](Modules/Tenant-Management.md) (which comes pre-installed with the startup projects) allows you to set a connection string for any tenant (as optional), so you can achieve any of the approaches.
#### Define Entities
## Usage
You can implement **IMultiTenant** interface for your entities to make them multi-tenancy ready. Example:
Multi-tenancy system is designed to **work seamlessly** and make your application code **multi-tenancy unaware** as much as possible.
````C#
### IMultiTenant
You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**.
**Example: A multi-tenant *Product* entity**
````csharp
using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace MyCompany.MyProject
namespace MultiTenancyDemo.Products
{
public class Product : AggregateRoot, IMultiTenant
public class Product : AggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; } //IMultiTenant defines TenantId property
public Guid? TenantId { get; set; } //Defined by the IMultiTenant interface
public string Name { get; set; }
@ -56,320 +70,340 @@ namespace MyCompany.MyProject
}
````
IMultiTenant requires to define a **TenantId** property in the implementing entity (See [entity documentation](Entities.md) for more about entities).
* `IMultiTenant` interface just defines a `TenantId` property.
When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries. A tenant can not access to data of another tenant by default.
#### Obtain Current Tenant's Id
#### Why the TenantId Property is Nullable?
Your code may require to get current tenant's id (regardless of how it's retrieved actually). You can [inject](Dependency-Injection.md) and use **ICurrentTenant** interface for such cases. Example:
`IMultiTenant.TenantId` is **nullable**. When it is null that means the entity is owned by the **Host** side and not owned by a tenant. It is useful when you create a functionality in your system that is both used by the tenant and the host sides.
````C#
using Volo.Abp.DependencyInjection;
For example, `IdentityUser` is an entity defined by the [Identity Module](Modules/Identity.md). The host and all the tenants have their own users. So, for the host side, users will have a `null` `TenantId` while tenant users will have their related `TenantId`.
> **Tip**: If your entity is tenant-specific and has no meaning in the host side, you can force to not set `null` for the `TenantId` in the constructor of your entity.
#### When to set the TenantId?
ABP Framework doesn't set the `TenantId` for you (because of the cross tenant operations, ABP can not know the proper `TenantId` in some cases). So, you need to set it yourself **when you create a new multi-tenant entity**.
##### Best Practice
We suggest to set the `TenantId` in the constructor and never allow to change it again. So, the `Product` class can be re-written as below:
````csharp
using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace MyCompany.MyProject
namespace MultiTenancyDemo.Products
{
public class MyService : ITransientDependency
public class Product : AggregateRoot<Guid>, IMultiTenant
{
private readonly ICurrentTenant _currentTenant;
//Private setter prevents changing it later
public Guid? TenantId { get; private set; }
public MyService(ICurrentTenant currentTenant)
public string Name { get; set; }
public float Price { get; set; }
protected Product()
{
_currentTenant = currentTenant;
//This parameterless constructor is needed for ORMs
}
public void DoIt()
public Product(string name, float price, Guid? tenantId)
{
var tenantId = _currentTenant.Id;
//use tenantId in your code...
Name = name;
Price = price;
TenantId = tenantId; //Set in the constructor
}
}
}
````
#### Change Current Tenant
TODO: ...
#### Determining Current Tenant
The first thing for a multi-tenant application is to determine the current tenant on the runtime. Volo.Abp.MultiTenancy package only provides abstractions (named as tenant resolver) for determining the current tenant, however it does not have any implementation out of the box.
**Volo.Abp.AspNetCore.MultiTenancy** package has implementation to determine the current tenant from current web request (from subdomain, header, cookie, route... etc.). See Volo.Abp.AspNetCore.MultiTenancy Package section later in this document.
> You can see the [entities document](Entities.md) for a more about entities and aggregate roots.
##### Custom Tenant Resolvers
You typically use the `ICurrentTenant` to set the `TenantId` while creating a new `Product`.
You can add your custom tenant resolver to **AbpTenantResolveOptions** in your module's ConfigureServices method as like below:
**Example: Creating a new product in a [Domain Service](Domain-Services.md)**
````C#
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
namespace MyCompany.MyProject
namespace MultiTenancyDemo.Products
{
[DependsOn(typeof(AbpMultiTenancyModule))]
public class MyModule : AbpModule
public class ProductManager : DomainService
{
public override void ConfigureServices(ServiceConfigurationContext context)
private readonly IRepository<Product, Guid> _productRepository;
public ProductManager(IRepository<Product, Guid> productRepository)
{
Configure<AbpTenantResolveOptions>(options =>
{
options.TenantResolvers.Add(new MyCustomTenantResolveContributor());
});
_productRepository = productRepository;
}
//...
public async Task<Product> CreateAsync(string name, float price)
{
var product = new Product(name, price, CurrentTenant.Id);
return await _productRepository.InsertAsync(product);
}
}
}
````
`MyCustomTenantResolveContributor` must implement **ITenantResolveContributor** as shown below:
* `DomainService` base class (and some common base classes in the ABP Framework) provides the `CurrentTenant`, so you directly use it. Otherwise, you need to [inject](Dependency-Injection.md) the `ICurrentTenant` service.
````C#
using Volo.Abp.MultiTenancy;
### ICurrentTenant
namespace MyCompany.MyProject
{
public class MyCustomTenantResolveContributor : ITenantResolveContributor
{
public void Resolve(ITenantResolveContext context)
{
context.TenantIdOrName = ... //find tenant id or tenant name from somewhere...
}
}
}
````
`ICurrentTenant` is the main service to interact with the multi-tenancy infrastructure.
`ApplicationService`, `DomainService`, `AbpController` and some other base classes already has pre-injected `CurrentTenant` properties. For other type of classes, you can inject the `ICurrentTenant` into your service.
A tenant resolver can set **TenantIdOrName** if it can determine it. If not, just leave it as is to allow next resolver to determine it.
#### Tenant Properties
#### Tenant Store
`ICurrentTenant` defines the following properties;
Volo.Abp.MultiTenancy package defines **ITenantStore** to abstract data source from the framework. You can implement ITenantStore to work with any data source (like a relational database) that stores information of your tenants.
* `Id` (`Guid`): Id of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
* `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request.
* `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`.
...
#### Change the Current Tenant
##### Configuration Data Store
ABP Framework automatically filters the resources (database, cache...) based on the `ICurrentTenant.Id`. However, in some cases you may want to perform an operation on behalf of a specific tenant, generally when you are in the host context.
There is a built in (and default) tenant store, named ConfigurationTenantStore, that can be used to store tenants using standard [configuration system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) (with [Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration) package). Thus, you can define tenants as hard coded or get from your appsettings.json file.
`ICurrentTenant.Change` method changes the current tenant for a limited scope, so you can safely perform operations for the tenant.
###### Example: Define tenants as hard-coded
**Example: Get product count of a specific tenant**
````C#
````csharp
using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
namespace MyCompany.MyProject
namespace MultiTenancyDemo.Products
{
[DependsOn(typeof(AbpMultiTenancyModule))]
public class MyModule : AbpModule
public class ProductManager : DomainService
{
public override void ConfigureServices(ServiceConfigurationContext context)
private readonly IRepository<Product, Guid> _productRepository;
public ProductManager(IRepository<Product, Guid> productRepository)
{
Configure<AbpDefaultTenantStoreOptions>(options =>
_productRepository = productRepository;
}
public async Task<long> GetProductCountAsync(Guid? tenantId)
{
using (CurrentTenant.Change(tenantId))
{
options.Tenants = new[]
{
new TenantConfiguration(
Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"), //Id
"tenant1" //Name
),
new TenantConfiguration(
Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"), //Id
"tenant2" //Name
)
{
//tenant2 has a seperated database
ConnectionStrings =
{
{ConnectionStrings.DefaultConnectionStringName, "..."}
}
}
};
});
return await _productRepository.GetCountAsync();
}
}
}
}
````
###### Example: Define tenants in appsettings.json
* `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement.
* When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly.
* Use `CurrentTenant.Change(null)` to change scope to the host context.
> Always use the `Change` method with a `using` statement like done in this example.
First create your configuration from your appsettings.json file as you always do.
### Data Filtering: Disable the Multi-Tenancy Filter
````C#
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
As mentioned before, ABP Framework handles data isolation between tenants using the [Data Filtering](Data-Filtering.md) system. In some cases, you may want to disable it and perform a query on all the data, without filtering for the current tenant.
**Example: Get count of products in the database, including all the products of all the tenants.**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
namespace MyCompany.MyProject
namespace MultiTenancyDemo.Products
{
[DependsOn(typeof(AbpMultiTenancyModule))]
public class MyModule : AbpModule
public class ProductManager : DomainService
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = BuildConfiguration();
private readonly IRepository<Product, Guid> _productRepository;
private readonly IDataFilter _dataFilter;
Configure<AbpDefaultTenantStoreOptions>(configuration);
public ProductManager(
IRepository<Product, Guid> productRepository,
IDataFilter dataFilter)
{
_productRepository = productRepository;
_dataFilter = dataFilter;
}
private static IConfigurationRoot BuildConfiguration()
public async Task<long> GetProductCountAsync()
{
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
using (_dataFilter.Disable<IMultiTenant>())
{
return await _productRepository.GetCountAsync();
}
}
}
}
````
Then add a "**Tenants**" section to your appsettings.json:
See the [Data Filtering document](Data-Filtering.md) for more.
````json
"Tenants": [
{
"Id": "446a5211-3d72-4339-9adc-845151f8ada0",
"Name": "tenant1"
},
{
"Id": "25388015-ef1c-4355-9c18-f6b6ddbaf89d",
"Name": "tenant2",
"ConnectionStrings": {
"Default": "...write tenant2's db connection string here..."
}
}
]
````
> Note that this approach won't work if your tenants have **separate databases** since there is no built-in way to query from multiple database in a single database query. You should handle it yourself if you need it.
##### Volo.Abp... Package (TODO)
## Infrastructure
TODO: This package implements ITenantStore using a real database...
### Determining the Current Tenant
#### Tenant Information
The first thing for a multi-tenant application is to determine the current tenant on the runtime.
ITenantStore works with **TenantConfiguration** class that has several properties for a tenant:
ABP Framework provides an extensible **Tenant Resolving** system for that purpose. Tenant Resolving system then used in the **Multi-Tenancy Middleware** to determine the current tenant for the current HTTP request.
* **Id**: Unique Id of the tenant.
* **Name**: Unique name of the tenant.
* **ConnectionStrings**: If this tenant has dedicated database(s) to store it's data, then connection strings can provide database connection strings (it may have a default connection string and connection strings per modules - TODO: Add link to Abp.Data package document).
#### Tenant Resolvers
A multi-tenant application may require additional tenant properties, but these are the minimal requirements for the framework to work with multiple tenants.
##### Default Tenant Resolvers
#### Change Tenant By Code
The following resolvers are provided and configured by default;
TODO...
* `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**.
* `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default.
* `FormTenantResolveContributor`:Tries to find current tenant id from form parameters. The parameter name is `__tenant` by default.
* `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route.
* `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default.
* `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default.
### Volo.Abp.AspNetCore.MultiTenancy Package
###### Problems with the NGINX
Volo.Abp.AspNetCore.MultiTenancy package integrate multi-tenancy to ASP.NET Core applications. To install it to your project, run the following command on PMC:
You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please:
http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
````
Install-Package Volo.Abp.AspNetCore.MultiTenancy
````
###### AbpAspNetCoreMultiTenancyOptions
Then you can add **AbpAspNetCoreMultiTenancyModule** dependency to your module:
`__tenant` parameter name can be changed using `AbpAspNetCoreMultiTenancyOptions`.
````C#
using Volo.Abp.Modularity;
using Volo.Abp.AspNetCore.MultiTenancy;
**Example:**
namespace MyCompany.MyProject
````csharp
services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
{
[DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))]
public class MyModule : AbpModule
{
//...
}
}
options.TenantKey = "MyTenantKey";
});
````
#### Multi-Tenancy Middleware
> However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then.
Volo.Abp.AspNetCore.MultiTenancy package includes the multi-tenancy middleware...
##### Domain/Subdomain Tenant Resolver
````C#
app.UseMultiTenancy();
````
In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure the `AbpTenantResolveOptions` to add the domain tenant resolver.
TODO:...
**Example: Add a subdomain resolver**
#### Determining Current Tenant From Web Request
Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box:
* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**.
* **QueryStringTenantResolveContributor**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default.
* **FormTenantResolveContributor**: Tries to find current tenant id from form parameter. Parameter name is "__tenant" by default.
* **RouteTenantResolveContributor**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route.
* **HeaderTenantResolveContributor**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default.
* **CookieTenantResolveContributor**: Tries to find current tenant id from cookie values. Cookie name is "__tenant" by default.
````csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.mydomain.com");
});
````
> If you use nginx as a reverse proxy server, please note that if `TenantKey` contains an underscore or other special characters, there may be a problem, please refer to:
http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
* `{0}` is the placeholder to determine current tenant's unique name.
* Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md).
* This should be done in the *Web/API Layer* since the URL is a web related stuff.
##### Custom Tenant Resolvers
"__tenant" parameter name can be changed using AbpAspNetCoreMultiTenancyOptions. Example:
You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below:
````C#
services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
````csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.TenantKey = "MyTenantKey";
options.TenantResolvers.Add(new MyCustomTenantResolveContributor());
});
````
##### Domain Tenant Resolver
In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure AbpTenantResolveOptions to add a domain tenant resolver.
###### Example: Add a subdomain resolver
`MyCustomTenantResolveContributor` must inherit from the `TenantResolveContributorBase` (or implement the `ITenantResolveContributor`) as shown below:
````C#
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.Modularity;
````csharp
using Volo.Abp.MultiTenancy;
namespace MyCompany.MyProject
namespace MultiTenancyDemo.Web
{
[DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))]
public class MyModule : AbpModule
public class MyCustomTenantResolveContributor : TenantResolveContributorBase
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpTenantResolveOptions>(options =>
{
//Subdomain format: {0}.mydomain.com
//Adding as the second highest priority resolver after 'CurrentUserTenantResolveContributor' to
//ensure the user cannot impersonate a different tenant.
options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));
});
public override string Name => "Custom";
//...
public override void Resolve(ITenantResolveContext context)
{
//TODO...
}
}
}
````
{0} is the placeholder to determine current tenant's unique name.
* A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it.
* `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system.
#### Multi-Tenancy Middleware
Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));`` you can use this shortcut:
Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that determines the current tenant from the HTTP request and sets the `ICurrentTenant` properties.
````C#
options.AddDomainTenantResolver("{0}.mydomain.com");
Multi-Tenancy middleware is typically placed just under the [authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication) middleware (`app.UseAuthentication()`):
````csharp
app.UseMultiTenancy();
````
###### Example: Add a domain resolver
> This middleware is already configured in the startup templates, so you normally don't need to manually add it.
### Tenant Store
`ITenantStore` is used to get the tenant configuration from a data source.
````C#
options.AddDomainTenantResolver("{0}.com");
#### Tenant Management Module
The [tenant management module](Modules/Tenant-Management) is **included in the startup templates** and implements the `ITenantStore` interface to get the tenants and their configuration from a database. It also provides the necessary functionality and UI to manage the tenants and their connection strings.
#### Configuration Data Store
**If you don't want to use the tenant management module**, the `DefaultTenantStore` is used as the `ITenantStore` implementation. It gets the tenant configurations from the [configuration system](Configuration.md) (`IConfiguration`). You can either configure the `AbpDefaultTenantStoreOptions` [options](Options.md) or set it in your `appsettings.json` file:
**Example: Define tenants in appsettings.json**
````json
"Tenants": [
{
"Id": "446a5211-3d72-4339-9adc-845151f8ada0",
"Name": "tenant1"
},
{
"Id": "25388015-ef1c-4355-9c18-f6b6ddbaf89d",
"Name": "tenant2",
"ConnectionStrings": {
"Default": "...tenant2's db connection string here..."
}
}
]
````
> It is recommended to **use the Tenant Management module**, which is already pre-configured when you create a new application with the ABP startup templates.
### Other Multi-Tenancy Infrastructure
ABP Framework was designed to respect to the multi-tenancy in every aspect and most of the times everything will work as expected.
BLOB Storing, Caching, Data Filtering, Data Seeding, Authorization and all the other services are designed to properly work in a multi-tenant system.
## The Tenant Management Module
ABP Framework provides all the the infrastructure to create a multi-tenant application, but doesn't make any assumption about how you manage (create, delete...) your tenants.
The [Tenant Management module](Modules/Tenant-Management.md) provides a basic UI to manage your tenants and set their connection strings. It is pre-configured for the [application startup template](Startup-Templates/Application.md).
## See Also
* [Features](Features.md)

43
docs/en/Redis-Cache.md

@ -0,0 +1,43 @@
# Redis Cache
ABP Framework [Caching System](Caching.md) extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). So, **any provider** supported by the standard ASP.NET Core distributed cache can be usable in your application and can be configured just like **documented by Microsoft**.
However, ABP provides an **integration package** for Redis Cache: [Volo.Abp.Caching.StackExchangeRedis](https://www.nuget.org/packages/Volo.Abp.Caching.StackExchangeRedis). There are two reasons for using this package, instead of the standard [Microsoft.Extensions.Caching.StackExchangeRedis](https://www.nuget.org/packages/Microsoft.Extensions.Caching.StackExchangeRedis/) package.
1. It implements `SetManyAsync` and `GetManyAsync` methods. These are not standard methods of the Microsoft Caching library, but added by the ABP Framework [Caching](Caching.md) system. They **significiantly increases the performance** when you need to set/get multiple cache items with a single method call.
2. It **simplifies** the Redis cache **configuration** (will be explained below).
> Volo.Abp.Caching.StackExchangeRedis is already uses the Microsoft.Extensions.Caching.StackExchangeRedis package, but extends and improves it.
## Installation
> This package is already installed in the application startup template if it is using Redis.
Open a command line in the folder of your `.csproj` file and type the following ABP CLI command:
````bash
abp add-package Volo.Abp.Caching.StackExchangeRedis
````
## Configuration
Volo.Abp.Caching.StackExchangeRedis package automatically gets the redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`:
````js
"Redis": {
"Configuration": "127.0.0.1"
}
````
Alternatively you can configure the standard [RedisCacheOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.stackexchangeredis.rediscacheoptions) [options](Options.md) class in the `ConfigureServices` method of your [module](Module-Development-Basics.md):
````csharp
Configure<RedisCacheOptions>(options =>
{
//...
});
````
## See Also
* [Caching](Caching.md)

2
docs/en/SignalR-Integration.md

@ -1,6 +1,6 @@
# SignalR Integration
> It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides a SignalR integration packages those simplify the integration and usage.
> It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides SignalR integration packages those simplify the integration and usage.
## Installation

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

@ -443,7 +443,7 @@ function configureRoutes(routes: RoutesService) {
* `order` is the order of the menu item.
* `layout` is the layout of the BooksModule's routes (there are three types of pre-defined layouts: `eLayoutType.application`, `eLayoutType.account` or `eLayoutType.empty`).
For more information, see the [RoutesService document](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice).
For more information, see the [RoutesService document](../UI/Angular/Modifying-the-Menu.md#via-routesservice).
### Service Proxy Generation
@ -643,7 +643,7 @@ Open the `Books.razor` and replace the content as the following:
</Card>
````
> If you see some syntax errors, you can ignore them if your application property built and run. Visual Studio still has some bugs with Blazor.
> If you see some syntax errors, you can ignore them if your application properly built and run. Visual Studio still has some bugs with Blazor.
* Inherited from the `BlazoriseCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>` which implements all the CRUD details for us.
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base blass.

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

@ -456,8 +456,6 @@ Open the `/src/app/book/book.component.html` file and replace the edit and delet
* Added `abpPermission="BookStore.Books.Edit"` that hides the edit action if the current user has no editing permission.
* Added `abpPermission="BookStore.Books.Delete"` that hides the delete action if the current user has no delete permission.
{{end}}
{{else if UI == "Blazor"}}
### Authorize the Razor Component

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

@ -233,4 +233,4 @@ Please refer to `Config.Environment` type for all the properties you can pass to
## What's Next?
- [Modifying the Menu](./Modifying-the-Menu.md)
- [HTTP Requests](./Http-Requests)

4
docs/en/UI/Angular/Content-Projection-Service.md

@ -1,4 +1,4 @@
# Content Projection
# Projecting Angular Content
You can use the `ContentProjectionService` in @abp/ng.core package in order to project content in an easy and explicit way.
@ -75,4 +75,4 @@ projectContent<T extends Type<any> | TemplateRef<any>>(
## What's Next?
- [TrackByService](./Track-By-Service.md)
- [Confirmation Popup](./Confirmation-Service.md)

4
docs/en/UI/Angular/Custom-Setting-Page.md

@ -35,7 +35,3 @@ export class AppComponent {
Navigate to `/setting-management` route to see the changes:
![Custom Settings Tab](./images/custom-settings.png)
## What's Next?
- [Lazy Loading Scripts & Styles](./Lazy-Load-Service.md)

4
docs/en/UI/Angular/Dom-Insertion-Service.md

@ -1,4 +1,4 @@
# Dom Insertion (of Scripts and Styles)
# Inserting Scripts & Styles to DOM
You can use the `DomInsertionService` in @abp/ng.core package in order to insert scripts and styles in an easy and explicit way.
@ -137,4 +137,4 @@ The `has` method returns a boolean value that indicates the given content has al
## What's Next?
- [ContentProjectionService](./Content-Projection-Service.md)
- [Lazy Loading Scripts & Styles](./Lazy-Load-Service.md)

11
docs/en/UI/Angular/Environment.md

@ -4,7 +4,7 @@ Every application needs some **environment** variables. In Angular world, this i
Current `Environment` configuration holds sub config classes as follows:
```typescript
```js
export interface Environment {
apis: Apis;
application: Application;
@ -16,7 +16,7 @@ export interface Environment {
## Apis
```typescript
```js
export interface Apis {
[key: string]: ApiConfig;
default: ApiConfig;
@ -56,7 +56,7 @@ Everything else will be sent to `"https://localhost:8080"`
## Application
```typescript
```js
export interface Application {
name: string;
baseUrl?: string;
@ -80,7 +80,7 @@ Abp Framework supports this out of box.
To integrate an existing config json into the `environment`, you need to set `remoteEnv`
```typescript
```js
export type customMergeFn = (
localEnv: Partial<Config.Environment>,
remoteEnv: any,
@ -105,5 +105,4 @@ export interface RemoteEnv {
## What's Next?
* [Service Proxies](./Service-Proxies.md)
- [About Feature Libraries](./Feature-Libraries.md)

98
docs/en/UI/Angular/Feature-Libraries.md

@ -0,0 +1,98 @@
# About Feature Libraries
ABP has an ever-growing number of feature modules and [introducing a new one](../../Module-Development-Basics.md) is always possible. When the UI is Angular, these features have modular Angular libraries accompanying them.
## Feature Library Content
Each library has at least two modules:
1. The main module contains all components, services, types, enums, etc. to deliver the required UI when the feature is loaded. From here on, we will refer to these modules as **"feature module"**.
2. There is also a **"config module"** per library which helps us configure applications to run these modules or make them accessible.
## How to Add a Feature Library to Your Project
<!-- TODO: Insert info on CLI `add-module` command here when the schematic is ready. -->
The manual setup of a feature library has three steps:
### 1. Install the Library
Feature libraries are usually published as an npm package. If a library you want to use does not exist in your project, you may install it via the following command:
```shell
yarn add @my-company-name/my-project-name
```
...or...
```shell
npm install @my-company-name/my-project-name
```
The `my-company-name` and `my-project-name` parts are going to change according to the package you want to use. For example, if we want to install the ABP Identity module, the package installation will be as seen below:
```shell
yarn add @abp/ng.identity
```
> Identity is used just as an example. If you have initiated your project with ABP CLI or ABP Suite, the identity library will already be installed and configured in your project.
### 2. Import the Config Module
As of ABP v3.0, every lazy-loaded module has a config module available via a secondary entry point on the same package. Importing them in your root module looks like this:
```js
import { IdentityConfigModule } from "@abp/ng.identity/config";
@NgModule({
imports: [
// other imports
IdentityConfigModule.forRoot(),
],
// providers, declarations, and bootstrap
})
export class AppModule {}
```
We need the config modules for actions required before feature modules are loaded (lazily). For example, the above import configures the menu to display links to identity pages.
Furthermore, depending on the library, the `.forRoot` static method may receive some options that configure how the feature works.
### 3. Import the Feature Module
Finally, the feature module should be [loaded lazily via Angular router](https://angular.io/guide/lazy-loading-ngmodules). If you open the `/src/app/app-routing.module.ts` file, you should see `IdentityModule` is loaded exactly as follows:
```js
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
const routes: Routes = [
// other routes
{
path: "identity",
loadChildren: () =>
import("@abp/ng.identity").then((m) => m.IdentityModule.forLazy()),
},
// other routes
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
```
When you load the identity feature like this, the "Users" page, for example, will have a route path of `/identity/users`. <sup id="a-modify-route">[1](#f-modify-route)</sup>
Depending on the library, the `.forLazy` static method may also receive some options that configure how the feature works.
---
<sup id="f-modify-route"><b>1</b></sup> _Libraries expect to work at a predefined path. Please check [how to patch a navigation element](./Modifying-the-Menu.md#how-to-patch-or-remove-a-navigation-element), if you want to use a different path from the default one (e.g. '/identity')._ <sup>[↩](#a-modify-route)</sup>
---
## What's Next?
- [Service Proxies](./Service-Proxies.md)

4
docs/en/UI/Angular/Lazy-Load-Service.md

@ -1,4 +1,4 @@
# How to Lazy Load Scripts and Styles
# Lazy Loading Scripts & Styles
You can use the `LazyLoadService` in @abp/ng.core package in order to lazy load scripts and styles in an easy and explicit way.
@ -210,4 +210,4 @@ load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Obser
## What's Next?
- [DomInsertionService](./Dom-Insertion-Service.md)
- [Projecting Angular Content](./Content-Projection-Service.md)

7
docs/en/UI/Angular/List-Service.md

@ -1,4 +1,4 @@
# Querying Lists Easily with ListService
# Working with Lists
`ListService` is a utility service to provide an easy pagination, sorting, and search implementation.
@ -177,3 +177,8 @@ As of v3.0, with ngx-datatable, the `page` property has to be set as `0` for ini
```
**Important Note:** The `abp-table` is not removed, but is deprecated and will be removed in the future. Please consider switching to ngx-datatable.
## What's Next?
- [Easy *ngFor trackBy](./Track-By-Service.md)

6
docs/en/UI/Angular/Migration-Guide-v3.md

@ -10,7 +10,7 @@ The new ABP Angular UI is based on Angular 10 and TypeScript 3.9, and we have dr
Open a terminal at your root folder and run the following command:
```sh
```shell
yarn ng update @angular/cli @angular/core --force
```
@ -26,7 +26,7 @@ On the other hand, it would be better if you check which packages to update firs
When Angular reports the packages above, your command would look like this:
```sh
```shell
yarn ng update @angular/cli @angular/core ng-zorro-antd --force
```
@ -473,5 +473,5 @@ Some interfaces have long been marked as deprecated and now they are removed.
## What's Next?
* [Environment](./Environment.md)
* [Quick Start](./Quick-Start.md)

29
docs/en/UI/Angular/Multi-Tenancy.md

@ -2,8 +2,8 @@
ABP Angular UI supports the multi-tenancy. The following features related to multi-tenancy are available in the startup templates.
![Tenants Page](./images/tenants-page.png)
<p style="font-size:small;text-align:center;">Tenants page</p>
On the page above, you can;
@ -13,13 +13,12 @@ On the page above, you can;
- Edit an existing tenant.
- Delete a tenant.
![Tenant Switching Component](./images/tenant-switching-box.png)
<p style="font-size:small;text-align:center;">Tenant Switching Component</p>
You can switch between existing tenants by using the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page). Angular UI sends the selected tenant id to the backend as `__tenant` header on each request.
## Domain Tenant Resolver
Angular UI can get the tenant name from the app running URL. You can determine the current tenant by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). To do this, you need to set the `application.baseUrl` property in the environment:
@ -32,10 +31,10 @@ Subdomain resolver:
export const environment = {
//...
application: {
baseUrl: 'https://{0}.mydomain.com/'
baseUrl: "https://{0}.mydomain.com/",
},
//...
}
};
```
**{0}** is the placeholder to determine current tenant's unique name.
@ -44,7 +43,6 @@ After the configuration above, if your app runs on the `mytenant1.mydomain.com`,
> **Important Note:** If you define the `baseUrl` with the placeholder (**{0}**), the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page) will be hidden.
Domain resolver:
```js
@ -53,10 +51,10 @@ Domain resolver:
export const environment = {
//...
application: {
baseUrl: 'https://{0}.com/'
baseUrl: "https://{0}.com/",
},
//...
}
};
```
After the configuration above, if your app runs on the `mytenant.com`, the app will get the tenant name as **mytenant**.
@ -71,29 +69,28 @@ The **{0}** placeholder can be put to the API URLs in the environment to determi
export const environment = {
//...
application: {
baseUrl: 'https://{0}.mydomain.com/',
baseUrl: "https://{0}.mydomain.com/",
//...
},
oAuthConfig: {
issuer: 'https://{0}.ids.mydomain.com',
issuer: "https://{0}.ids.mydomain.com",
//...
},
apis: {
default: {
url: 'https://{0}.api.mydomain.com',
url: "https://{0}.api.mydomain.com",
},
AbpIdentity: {
url: 'https://{0}.identity.mydomain.com',
url: "https://{0}.identity.mydomain.com",
},
},
}
};
```
> **Important Note:** The `application.baseUrl` and the `{0}` placeholder in the value of the `baseUrl` property are required to be able to get tenant from running URL. Other placeholders in API URLs are optional.
After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get tenant name as **mytenant1** and replace the environment object in `ConfigState` on app initialization as follows:
```js
// environment object in ConfigState
@ -128,8 +125,8 @@ The app sends the `__tenant` header that contains the current tenant id on each
## See Also
* [Multi Tenancy in ABP](../../Multi-Tenancy.md)
- [Multi Tenancy in ABP](../../Multi-Tenancy.md)
## What's Next?
- [Confirmation Popup](./Confirmation-Service.md)
- [Managing RxJS Subscriptions](./Subscription-Service.md)

346
docs/en/UI/Angular/PWA-Configuration.md

@ -0,0 +1,346 @@
# PWA Configuration
[Progressive Web Apps](https://web.dev/progressive-web-apps/) are web applications which, although not as integrated to the OS as a native app, can take advantage of native features. They can be discovered via search engines, installed on devices with a single tap or click, and shared via a regular link. They also can work offline and get updates when new content is available.
Converting your Angular application to a PWA is easy.
## 1. Install Angular PWA
Run the following command in the root folder of your Angular application:
```shell
yarn ng add @angular/pwa
```
...or...
```shell
npm run ng add @angular/pwa
```
This will install the `@angular/service-worker` package and make your default app a PWA. Alternatively, you may add `project` parameter to target a specific app in your workspace:
```shell
yarn ng add @angular/pwa --project MyProjectName
```
Here is the output of the command:
<img alt="Angular PWA updates and creates files" src="./images/pwa-configuration-ng-add.png" width="400px" style="max-width:100%">
So, Angular CLI updates some files and add a few others:
- **ngsw-config.json** is where the [service worker configuration](https://angular.io/guide/service-worker-config) is placed. Not all PWAs have this file. It is specific to Angular.
- **manifest.webmanifest** is a [web app manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) and provides information about your app in JSON format.
- **icons** are placeholder icons that are referred to in your web app manifest. We will replace these in a minute.
- **angular.json** has following modifications:
- `assets` include _manifest.webmanifest_.
- `serviceWorker` is `true` in production build.
- `ngswConfigPath` refers to _ngsw-config.json_.
- **package.json** has _@angular/service-worker_ as a new dependency.
- **app.module.ts** imports `ServiceWorkerModule` and registers a service worker filename.
- **index.html** has following modifications:
- A `<link>` element that refers to _manifest.webmanifest_.
- A `<meta>` tag that sets a theme color.
## 2. Update the Web App Manifest
### 2.1. Set the Name of Your App
The `name` and the `short_name` properties in the generated manifest are derived from your project name. Let's change them.
Open the _manifest.webmanifest_ file and update `name` and `short_name` props:
```json
{
/* rest of the manifest meta data */
"short_name": "My Project",
"name": "My Project: My Catch-Phrase"
}
```
The short name must be really short because it will be displayed on anywhere with limited space, like the launcher and the home screen.
### 2.2. Add a Description
The `@angular/pwa` schematic we just added does not insert a description to your manifest file, but, according to [web app manifest standards](https://www.w3.org/TR/appmanifest/#description-member), you should.
So, open the _manifest.webmanifest_ file and place the description as seen below:
```json
{
/* rest of the manifest meta data */
"description": "My short project description giving a slightly better idea about my app"
}
```
As a bonus, providing a description [along with other criteria](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-edgehtml/microsoft-store#criteria-for-automatic-submission) helps Bing web crawler to index your app and automatically submit your app to Microsoft Store in `.appx` format.
### 2.3. Set App Colors
Angular generates the manifest file with a default `theme_color` and `background_color`. Change these according to your brand identity:
Open the _manifest.webmanifest_ file and update `theme_color` and `background_color` properties:
```json
{
/* rest of the manifest meta data */
"theme_color": "#000000",
"background_color": "#ffffff"
}
```
Then open _index.html_ and change the theme color meta tag as below:
```html
<meta name="theme-color" content="#000000" />
```
### 2.4. Replace App Icons & Add Splash Screens
We need to update the icons and add some splash screens. This normally is time-consuming, but we will use the marvelous [pwa-asset-generator](https://github.com/onderceylan/pwa-asset-generator#readme) library.
First, open the _manifest.webmanifest_ file and remove all elements in the `icons` property:
```json
{
/* rest of the manifest meta data */
"icons": []
}
```
Then, run the following command in your terminal (changing the path of course):
```shell
npx pwa-asset-generator /path/to/your/logo.png ./src/assets/pwa -i ./src/index.html -m ./src/manifest.webmanifest
```
Open the _manifest.webmanifest_ file again. You will see this:
```json
{
/* rest of the manifest meta data */
"icons": [
{
"src": "../manifest-icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "../manifest-icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
```
In addition to updated icons, the library will generate splash screens. However, Apple requires all splash screens to be added in your _index.html_ and displays a blank screen at startup otherwise. So, the following tags will be inserted into the _index.html_ file:
```html
<link
rel="apple-touch-icon"
sizes="180x180"
href="assets/pwa/apple-icon-180.jpg"
/>
<link
rel="apple-touch-icon"
sizes="167x167"
href="assets/pwa/apple-icon-167.jpg"
/>
<link
rel="apple-touch-icon"
sizes="152x152"
href="assets/pwa/apple-icon-152.jpg"
/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="assets/pwa/apple-icon-120.jpg"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2048-2732.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2732-2048.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1668-2388.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2388-1668.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1536-2048.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2048-1536.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1668-2224.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2224-1668.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1620-2160.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2160-1620.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1242-2688.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2688-1242.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1125-2436.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-2436-1125.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-828-1792.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1792-828.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1080-1920.jpg"
media="(device-width: 360px) and (device-height: 640px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1920-1080.jpg"
media="(device-width: 360px) and (device-height: 640px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-750-1334.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1334-750.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-640-1136.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="assets/pwa/apple-splash-1136-640.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
```
## 3. Configure Service Worker
### 3.1 Modify Asset Groups
Angular has defined some static files to be cached by the service worker, but they are not 100% accurate. Let's change it.
Open _ngsw-config.json_ file and replace its content with this:
```json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/common-es2015.*.js",
"/main-es2015.*.js",
"/polyfills-es2015.*.js",
"/runtime-es2015.*.js",
"/vendor-es2015.*.js"
]
}
},
{
"name": "modules",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/*-es2015.*.js",
"!/common-es2015.*.js",
"!/main-es2015.*.js",
"!/polyfills-es2015.*.js",
"!/runtime-es2015.*.js",
"!/vendor-es2015.*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}
```
In case you want to cache other static files, please refer to the [service worker configuration document](https://angular.io/guide/service-worker-config#assetgroups) on Angular.io.
### 3.2 Set Data Groups
This part is unique to your project. We recommend being very careful about which endpoints to cache. Please refer to [service worker configuration document](https://angular.io/guide/service-worker-config#datagroups) on Angular.io for details.
## What's Next?
- [Config State](./Config-State.md)

212
docs/en/UI/Angular/Quick-Start.md

@ -0,0 +1,212 @@
# ABP Angular Quick Start
## How to Prepare Development Environment
Please follow the steps below to prepare your development environment for Angular.
1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v12 or v14 installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system.
2. **[Optional] Install Yarn:** You may install Yarn v1 (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v6 and below. You may skip this step and work with npm, which is built-in in Node.js, instead.
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to Angular projects. ABP project templates even contain plugin recommendations for VS Code users, which VS Code will ask you to install when you open the Angular project folder. Here is a list of recommended extensions:
- [Angular Language Service](https://marketplace.visualstudio.com/items?itemName=angular.ng-template)
- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- [TSLint](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin)
- [Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=visualstudioexptteam.vscodeintellicode)
- [Path Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense)
- [npm Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.npm-intellisense)
- [Angular 10 Snippets - TypeScript, Html, Angular Material, ngRx, RxJS & Flex Layout](https://marketplace.visualstudio.com/items?itemName=Mikael.Angular-BeastCode)
- [JavaScript (ES6) code snippets](https://marketplace.visualstudio.com/items?itemName=xabikos.JavaScriptSnippets)
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)
- [Git History](https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory)
- [indent-rainbow](https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow)
## How to Start a New Angular Project
You have multiple options to initiate a new Angular project that works with ABP:
### 1. Using ABP CLI
ABP CLI is probably the most convenient and flexible way to initiate an ABP solution with an Angular frontend. Simply [install the ABP CLI](../../CLI.md) and run the following command in your terminal:
```shell
abp new MyCompanyName.MyProjectName -csf -u angular
```
> To see further options in the CLI, please visit the [CLI manual](../../CLI.md).
This command will prepare a solution with an Angular and a .NET Core project in it. Please visit [Getting Started section](../../Getting-Started.md?UI=NG&DB=EF&Tiered=No#abp-cli-commands-options) for further instructions on how to set up the backend of your solution.
To continue reading without checking other methods, visit [Angular project structure section](#angular-project-structure).
### 2. Direct Download
You may [download a solution scaffold directly on ABP.io](https://abp.io/get-started) if you are more comfortable with GUI or simply want to try ABP without installing the CLI.
Please do the following:
1. Click on the "DIRECT DOWNLOAD" tab.
2. Fill out the short form about your project.
3. Click on the "Create now" button.
...and a customized download will start in a few seconds.
To avoid IDE warnings, run `yarn` or `npm install` in the Angular root folder before you continue to the next section. It is not imperative but recommended.
## Angular Project Structure
After creating a solution, open its "angular" directory in your IDE. This is how the contents of the root folder looks like:
<img alt="Angular project root folder structure" src="./images/quick-start---root-folder-structure.png" width="300px" style="max-width:100%">
Here is what these folders and files are for:
- **.vscode** has extension recommendations in it.
- **e2e** is a separate app for possible end-to-end tests.
- **src** is where the source files for your application are placed. We will have a closer look in a minute.
- **.browserlistrc** helps [configuring browser compatibility of your Angular app](https://angular.io/guide/build#configuring-browser-compatibility).
- **.editorconfig** helps you have a shared coding style for separate editors and IDEs. Check [EditorConfig.org](https://editorconfig.org/) for details.
- **.gitignore** defined which files and folders should not be tracked by git. Check [git documentation](https://git-scm.com/docs/gitignore) for details.
- **.prettierrc** includes simple coding style choices for [Prettier](https://prettier.io/), an auto-formatter for TypeScript, HTML, CSS, and more. If you install recommended extensions to VS Code, you will never have to format your code anymore.
- **angular.json** is where Angular workspace is defined. It holds project configurations and workspace preferences. Please refer to [Angular workspace configuration](https://angular.io/guide/workspace-config) for details.
- **karma.conf.js** holds [Karma test runner](https://karma-runner.github.io/) configurations.
- **package.json** is where your [package dependencies](https://angular.io/guide/npm-packages) are listed. It also includes some useful scripts for developing, testing, and building your application.
- **README.md** includes some of Angular CLI command examples. You either have to install Angular CLI globally or run these commands starting with `yarn` or `npx` to make them work.
- **start.ps1** is a simple PowerShell script to install dependencies and start a [development server via Angular CLI](https://angular.io/cli/serve), but you probably will not need that after reading this document.
- **tsconfig.json** and all other [tsconfig files](https://angular.io/guide/typescript-configuration) in general, include some TypeScript and Angular compile options.
- **yarn.lock** enables installing consistent package versions across different devices so that working application build will not break because of a package update. Please read [Yarn documentation](https://classic.yarnpkg.com/en/docs/yarn-lock/) if you are interested in more information on the topic. If you have decided to use npm, please remove this file and keep the [package-lock.json](https://docs.npmjs.com/files/package-lock.json) instead.
Now let us take a look at the contents of the source folder.
<img alt="Angular project source folder structure" src="./images/quick-start---source-folder-structure.png" width="300px" style="max-width:100%">
- **app** is the main directory you put your application files in. Any module, component, directive, service, pipe, guard, interceptor, etc. should be placed here. You are free to choose any folder structure, but [organizing Angular applications based on modules](https://angular.io/guide/module-types) is generally a fine practice.
- **home** is a predefined module and acts as a welcome page. It also demonstrates how a feature-based folder structure may look like. More complex features will probably have sub-features, thus inner folders. You may change the home folder however you like.
- **shared** is spared for reusable code that works for several modules. Some, including yours truly, may disagree with using a single module for all shared code, so consider adding standalone sub-modules inside this folder instead of adding everything into **shared.module.ts**.
- **app-routing.module.ts** is where your top-level routes are defined. Angular is capable of [lazy loading feature modules](https://angular.io/guide/lazy-loading-ngmodules), so not all routes will be here. You may think of Angular routing as a tree and this file is the top of the tree.
- **app.component.ts** is essentially the top component that holds the dynamic application layout.
- **app.module.ts** is the [root module](https://angular.io/guide/bootstrapping) that includes information about how parts of your application are related and what to run at the initiation of your application.
- **route.provider.ts** is used for [modifying the menu](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu).
- **assets** is for static files. A file (e.g. an image) placed in this folder will be available as is when the application is served.
- **environments** includes one file per environment configuration. There are two configurations by default, but you may always introduce another one. These files are directly referred to in _angular.json_ and help you have different builds and application variables. Please refer to [configuring Angular application environments](https://angular.io/guide/build#configuring-application-environments) for details.
- **index.html** is the HTML page served to visitors and will contain everything required to run your application. Servers should be configured to redirect every request to this page so that the Angular router can take over. Do not worry about how to add JavaScript and CSS files to it, because Angular CLI will do it automatically.
- **main.ts** bootstraps and configures Angular application to run in the browser. It is production-ready, so forget about it.
- **polyfill.ts** is where you can add polyfills if you want to [support legacy browsers](https://angular.io/guide/browser-support).
- **style.scss** is the default entry point for application styles. You can change this or add new entry points in _angular.json_.
- **test.ts** helps the unit test runner discover and bootstrap spec files.
Phew! So many files, right? Yet, **most of them are typically not subject to change** or, even when they are so, the CLI tooling will do the job for you. The main focus should be on the app folder and its content.
Next, we will take a look at the commands used to prepare, build, and serve our application.
## How to Run the Angular Application
Now that you know about the files and folders, we can get the application up and running.
1. Make sure the [database migration is complete](../../Getting-Started?UI=NG&DB=EF&Tiered=No#create-the-database) and the [API is up and running](../../Getting-Started?UI=NG&DB=EF&Tiered=No#run-the-application).
2. Run `yarn` or `npm install` if you have not already.
3. Run `yarn start` or `npm start`. The first compilation may take a while. This will start a [live development server](#angular-live-development-server) and launch your default browser in the end.
4. Visit the browser page that opens after the compilation <sup id="a-certificate-error">[1](#f-certificate-error)</sup>.
<img alt="New ABP Angular project home page" src="./images/quick-start---new-project-home-page.png" width="744px" style="max-width:100%">
You may modify the behavior of the **start script** (in the package.json file) by changing the parameters passed to the `ng serve` command. For instance, if you do not want a browser window to open next time you run the script, remove `--open` from the end of it. Please check [ng serve documentation](https://angular.io/cli/serve) for all available options.
### Angular Live Development Server
The development server of Angular is based on [Webpack DevServer](https://webpack.js.org/configuration/dev-server/). It tracks changes to source files and syncs the browser window after an incremental re-compilation every time <sup id="a-dev-server">[2](#f-dev-server)</sup> you make one. Your experience will be like this:
<img alt="Angular Live Development Server compiles again on template change and removes a button from the page displayed by the browser." src="./images/quick-start---angular-live-development-server.gif" width="818px" style="max-width:100%">
Please keep in mind that you should not use this server in production. To provide the fastest experience, the compiler skips some heavy optimizations and the development server is simply not built for multiple clients. The next section will describe what to do.
---
<img alt="Error caused by browser blocking access to backend" src="./images/quick-start---self-signed-certificate-error.png" width="400px" style="max-width:100%">
<sup id="f-certificate-error"><b>1</b></sup> _If you see the error above when you run the Angular app, your browser might be blocking access to the API because of the self-signed certificate. Visit that address and allow access to it (once). When you see the Swagger interface, you are good to go._ <sup>[↩](#a-certificate-error)</sup>
<sup id="f-dev-server"><b>2</b></sup> _Sometimes, depending on the file changed, Webpack may miss the change and cannot reflect it in the browser. For example, tsconfig files are not being tracked. In such a case, please restart the development server._ <sup>[↩](#a-dev-server)</sup>
---
## How to Build the Angular Application
An Angular application can have multiple [build targets](https://angular.io/guide/glossary#target), i.e. **configurations in angular.json** which define how [Architect](https://angular.io/guide/glossary#architect) will build applications and libraries. Usually, each build configuration has a separate environment variable file. Currently, the project has two: One for development and one for production.
```js
// this is what environment variables look like
// can be found at /src/environments/environment.ts
import { Config } from '@abp/ng.core';
const baseUrl = 'http://localhost:4200';
export const environment = {
production: false,
application: {
baseUrl,
name: 'MyProjectName',
logoUrl: '',
},
oAuthConfig: {
issuer: 'https://localhost:44381',
redirectUri: baseUrl,
clientId: 'MyProjectName_App',
responseType: 'code',
scope: 'offline_access MyProjectName',
},
apis: {
default: {
url: 'https://localhost:44381',
rootNamespace: 'MyCompanyName.MyProjectName',
},
},
} as Config.Environment;
```
When you run the development server, variables defined in _environment.ts_ take effect. Similarly, in production mode, the default environment is replaced by _environment.prod.ts_ and completely different variables become effective. You may even [create a new build configuration](https://angular.io/guide/workspace-config#build-configs) and set [file replacements](https://angular.io/guide/build#configure-target-specific-file-replacements) to use a completely new environment. For now, we will start a production build:
1. Open your terminal and navigate to the root Angular folder.
2. Run `yarn` or `npm install` if you have not installed dependencies already.
3. Run `yarn build:prod` or `npm run build:prod`.
<img alt="Angular compiler optimizing the build using Terser" src="./images/quick-start---self-signed-certificate-error.png" width="400px" style="max-width:100%">
Depending on project size, the compilation may take a few minutes. When it is finished, the compiled output will be placed inside the _/dist_ folder. Voila! You have deployment-ready build artifacts.
> The amount of optimization performed on the source is the main difference between a regular build and a production one. Production builds have a much smaller size and are more performant.
## How to Deploy the Angular Application
Angular web applications run on the browser and require no server except for a [static web server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_web_server) to deliver files to the client. To see that it works, please make sure the backend application is up and then run the following command in your terminal:
```shell
# please replace MyProjectName with your project name
npx servor dist/MyProjectName index.html 4200 --browse
```
This command will download and start a simple static server, a browser window at `http://localhost:4200` will open, and the compiled output of your project will be served.
Of course, you need your application to run on an optimized web server and become available to everyone. This is quite straight-forward:
1. Create a new static web server instance. You can use a service like [Azure App Service](https://azure.microsoft.com/tr-tr/services/app-service/web/), [Firebase](https://firebase.google.com/docs/hosting), [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), or even [GitHub Pages](https://angular.io/guide/deployment#deploy-to-github-pages). Another option is maintaining own web server with [NGINX](https://www.nginx.com/), [IIS](https://www.iis.net/), [Apache HTTP Server](https://httpd.apache.org/), or equivalent.
2. Copy the files from `dist/MyProjectName` <sup id="a-dist-folder-name">[1](#f-dist-folder-name)</sup> to a publicly served destination on the server via CLI of the service provider, SSH, or FTP (whichever is available). This step would be defined as a job if you have a CI/CD flow.
3. [Configure the server](https://angular.io/guide/deployment#server-configuration) to redirect all requests to the _index.html_ file. Some services do that automatically. Others require you [to add a file to the bundle via assets](https://angular.io/guide/workspace-config#assets-configuration) which describes the server how to do the redirections. Occasionally, you may need to do manual configuration.
In addition, you can [deploy your application to certain targets using the Angular CLI](https://angular.io/guide/deployment#automatic-deployment-with-the-cli). Here are some deploy targets:
- [Azure](https://github.com/Azure/ng-deploy-azure#readme)
- [Firebase](https://github.com/angular/angularfire#readme)
- [Netlify](https://github.com/ngx-builders/netlify-builder#readme)
- [Vercel](https://github.com/vercel/ng-deploy-vercel#readme)
- [GitHub Pages](https://github.com/angular-schule/angular-cli-ghpages/#readme)
---
<sup id="f-dist-folder-name"><b>1</b></sup> _The compiled output will be placed under `/dist` in a folder by the project name._ <sup>[↩](#a-dist-folder-name)</sup>
---
## What's Next?
- [Environment Variables](./Environment.md)

29
docs/en/UI/Angular/Service-Proxies.md

@ -1,16 +1,15 @@
## Service Proxies
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on the server side) and **model objects** (matches to [DTOs](../../Data-Transfer-Objects) in the server side).
Calling a REST endpoint from Angular applications is common. We usually create **services** matching server-side controllers and **interfaces** matching [DTOs](../../Data-Transfer-Objects) to interact with the server. This often results in manually transforming C# code into TypeScript equivalents and that is unfortunate, if not intolerable.
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced:
To avoid manual effort, we might use a tool like [NSWAG](https://github.com/RicoSuter/NSwag) that generates service proxies. However, NSWAG has some disadvantages:
- It generates a **big, single** .ts file which has some problems;
- It get **too large** when your application grows.
- It doesn't fit into the **[modular](../../Module-Development-Basics) approach** of the ABP framework.
- It creates a bit **ugly code**. We want to have a clean code (just like if we write manually).
- It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies.
- It generates **a single .ts file** which gets **too large** as your application grows. Also, this single file does not fit the **[modular](../../Module-Development-Basics) approach** of ABP.
- To be honest, the generated code is a bit **ugly**. We would like to produce code that looks as if someone wrote it.
- Since swagger.json **does not reflect the exact method signature** of backend services, NSWAG cannot reflect them on the client-side as well.
ABP introduces an endpoint that exposes server-side method contracts. When the `generate-proxy` command is run, ABP CLI makes an HTTP request to this endpoint and generates better-aligned client proxies in TypeScript. It organizes folders according to namespaces, adds barrel exports, and reflects method signatures in Angular services.
ABP CLI changes that via the `generate-proxy` command. It automatically generates the client proxies in TypeScript. by creating folders which separated by module names in the `src/app` folder.
Run the following command in the **root folder** of the angular application:
```bash
@ -19,13 +18,13 @@ abp generate-proxy
The command without any parameters creates proxies only for your own application's services and places them in your default Angular application. There are several parameters you may use to modify this behavior. See the [CLI documentation](../../CLI) for details.
The generated files will be placed in a folder called `proxy` at the root of target project.
The generated files will be placed in a folder called `proxy` at the root of the target project.
![generated-files-via-generate-proxy](./images/generated-files-via-generate-proxy.png)
Each folder will have models, enums, and services defined at related namespace accompanied by a barrel export, i.e. an `index.ts` file for easier imports.
> The cammand is able to find application/library roots by reading `angular.json` file. Make sure you have either defined your target project as the `defaultProject` or pass the `--target` parameter to the command. This also means that you may have a monorepo workspace.
> The command can find application/library roots by reading the `angular.json` file. Make sure you have either defined your target project as the `defaultProject` or pass the `--target` parameter to the command. This also means that you may have a monorepo workspace.
### Angular Project Configuration
@ -74,9 +73,9 @@ export const environment: Config.Environment = {
The `generate-proxy` command generates one service per back-end controller and a method (property with a function value actually) for each action in the controller. These methods call backend APIs via [RestService](./Http-Requests#restservice).
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config)
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's `RemoteServiceName`. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config)
The `providedIn` property of the services is defined as `'root'`. Therefore there is no need to provide them in a module. You can use them directly by injecting them into constructor as shown below:
The `providedIn` property of the services is defined as `'root'`. Therefore there is no need to provide them in a module. You can use them directly by injecting them into the constructor as shown below:
```js
import { BookService } from '@proxy/books';
@ -114,7 +113,7 @@ export class BookComponent implements OnInit {
### Enums
Enums have always been difficult to populate in the frontend. The `generate-proxy` command genarates enums in a separate file and exports a ready-to-use options constant from the same file. So you can import them as follows:
Enums have always been difficult to populate in the frontend. The `generate-proxy` command generates enums in a separate file and exports a ready-to-use "options constant" from the same file. So you can import them as follows:
```js
import { bookGenreOptions } from "@proxy/books";
@ -132,7 +131,7 @@ export class BookComponent implements OnInit {
<select formControlName="genre">
<option [ngValue]="null">Select a genre</option>
<option *ngFor="let genre of genres" [ngValue]="genre.value">
{%{{{ genre.key }}}%}
{%{{{ genre.key }}}%}
</option>
</select>
```
@ -141,4 +140,4 @@ export class BookComponent implements OnInit {
## What's Next?
- [HTTP Requests](./Http-Requests)
- [PWA Configuration](./PWA-Configuration.md)

4
docs/en/UI/Angular/Subscription-Service.md

@ -1,4 +1,4 @@
# Easy Unsubscription for Your Observables
# Managing RxJS Subscriptions
`SubscriptionService` is a utility service to provide an easy unsubscription from RxJS observables in Angular components and directives. Please see [why you should unsubscribe from observables on instance destruction](https://angular.io/guide/lifecycle-hooks#cleaning-up-on-instance-destruction).
@ -200,4 +200,4 @@ class DemoComponent implements OnInit {
## What's Next?
- [ListService](./List-Service.md)
- [Working with Lists](./List-Service.md)

4
docs/en/UI/Angular/Toaster-Service.md

@ -67,7 +67,7 @@ With the options above, the toast overlay looks like this:
![toast](./images/toast.png)
### How to Remove a Toast Overlay
### How to Remove a Toast Overlay
The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast:
@ -156,4 +156,4 @@ Removes all open toasts.
## What's Next?
- [Config State](./Config-State.md)
- [Modifying the Menu](./Modifying-the-Menu.md)

4
docs/en/UI/Angular/Track-By-Service.md

@ -1,4 +1,4 @@
# Easy TrackByFunction Implementation
# Easy *ngFor trackBy
`TrackByService` is a utility service to provide an easy implementation for one of the most frequent needs in Angular templates: `TrackByFunction`. Please see [this page in Angular docs](https://angular.io/guide/template-syntax#ngfor-with-trackby) for its purpose.
@ -116,4 +116,4 @@ class DemoComponent {
## What's Next?
- [SubscriptionService](./Subscription-Service.md)
- [Inserting Scripts & Styles to DOM](./Dom-Insertion-Service.md)

BIN
docs/en/UI/Angular/images/pwa-configuration-ng-add.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

BIN
docs/en/UI/Angular/images/quick-start---angular-live-development-server.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
docs/en/UI/Angular/images/quick-start---new-project-home-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/en/UI/Angular/images/quick-start---production-build-optimization.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/UI/Angular/images/quick-start---root-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
docs/en/UI/Angular/images/quick-start---self-signed-certificate-error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/en/UI/Angular/images/quick-start---source-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

206
docs/en/UI/AspNetCore/Forms-Validation.md

@ -0,0 +1,206 @@
# ASP.NET Core MVC / Razor Pages: Forms & Validation
ABP Framework provides infrastructure and conventions to make easier to create forms, localize display names for the form elements and handle server & client side validation;
* [abp-dynamic-form](Tag-Helpers/Dynamic-Forms.md) tag helper automates **creating a complete form** from a C# model class: Creates the input elements, handles localization and client side validation.
* [ABP Form tag helpers](Tag-Helpers/Form-elements.md) (`abp-input`, `abp-select`, `abp-radio`...) render **a single form element** with handling localization and client side validation.
* ABP Framework automatically **localizes the display name** of a form element without needing to add a `[DisplayName]` attribute.
* **Validation errors** are automatically localized based on the user culture.
> This document is for the **client side validation** and it doesn't cover the server side validation. Check the [validation document](../../Validation.md) for server side validation infrastructure.
## The Classic Way
In a typical Bootstrap based ASP.NET Core MVC / Razor Pages UI, you [need to write](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation#client-side-validation) such a boilerplate code to create a simple form element:
````html
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
````
You can continue to use this approach if you need or prefer it. However, ABP Form tag helpers can produce the same output with a minimal code.
## ABP Dynamic Forms
[abp-dynamic-form](Tag-Helpers/Dynamic-Forms.md) tag helper completely automates the form creation. Take this model class as an example:
```csharp
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace MyProject.Web.Pages
{
public class MovieViewModel
{
[Required]
[StringLength(256)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
[TextArea]
[StringLength(1000)]
public string Description { get; set; }
public Genre Genre { get; set; }
public float? Price { get; set; }
public bool PreOrder { get; set; }
}
}
```
It uses the data annotation attributes to define validation rules and UI styles for the properties. `Genre`, is an `enum` in this example:
````csharp
namespace MyProject.Web.Pages
{
public enum Genre
{
Classic,
Action,
Fiction,
Fantasy,
Animation
}
}
````
In order to create the form in a razor page, create a property in your `PageModel` class:
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MyProject.Web.Pages
{
public class CreateMovieModel : PageModel
{
[BindProperty]
public MovieViewModel Movie { get; set; }
public void OnGet()
{
Movie = new MovieViewModel();
}
public async Task OnPostAsync()
{
if (ModelState.IsValid)
{
//TODO: Save the Movie
}
}
}
}
```
Then you can render the form in the `.cshtml` file:
```html
@page
@model MyProject.Web.Pages.CreateMovieModel
<h2>Create a new Movie</h2>
<abp-dynamic-form abp-model="Movie" submit-button="true" />
```
The result is shown below:
![abp-dynamic-form-result](../../images/abp-dynamic-form-result.png)
See the *Localization & Validation* section below to localize the field display names and see how the validation works.
> See [its own document](Tag-Helpers/Dynamic-Forms.md) for all options of the `abp-dynamic-form` tag helper.
## ABP Form Tag Helpers
`abp-dynamic-form` covers most of the scenarios and allows you to control and customize the form using the attributes.
However, if you want to **render the form body yourself** (for example, you may want to fully control the **form layout**), you can directly use the [ABP Form Tag Helpers](Tag-Helpers/Form-elements.md). The same auto-generated form above can be created using the ABP Form Tag Helpers as shown below:
```html
@page
@model MyProject.Web.Pages.CreateMovieModel
<h2>Create a new Movie</h2>
<form method="post">
<abp-input asp-for="Movie.Name"/>
<abp-input asp-for="Movie.ReleaseDate"/>
<abp-input asp-for="Movie.Description"/>
<abp-select asp-for="Movie.Genre"/>
<abp-input asp-for="Movie.Price"/>
<abp-input asp-for="Movie.PreOrder"/>
<abp-button button-type="Primary" type="submit">Save</abp-button>
</form>
```
> See the [ABP Form Tag Helpers](Tag-Helpers/Form-elements.md) document for details of these tag helpers and their options.
## Validation & Localization
Both of the Dynamic Form and the Form Tag Helpers **automatically validate** the input based on the data annotation attributes and shows validation error messages on the user interface. Error messages are **automatically localized** based on the current culture.
**Example: User leaves empty a required string property**
![abp-form-input-validation-error](../../images/abp-form-input-validation-error.png)
The error message below is shown if the language is French:
![abp-form-input-validation-error](../../images/abp-form-input-validation-error-french.png)
Validation errors are already [translated](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization) a lot of languages. You can [contribute](../../Contribution/Index.md) to the translation for your own language or override the texts for your own application by following the [localization](../../Localization.md) documentation.
## Display Name Localization
ABP Framework uses the property name as the field name on the user interface. You typically want to [localize](../../Localization.md) this name based on the current culture.
ABP Framework can conventionally localize the fields on the UI when you add the localization keys to the localization JSON files.
Example: French localization for the *Name* property (add into the `fr.json` in the application):
````js
"Name": "Nom"
````
Then the UI will use the given name for French language:
![abp-form-input-validation-error](../../images/abp-form-input-validation-error-french-name.png)
### Using the `DisplayName:` Prefix
Directly using the property name as the localization key may be a problem if you need to use the property name for other purpose, which a different translation value. In this case, use the `DisplayName:` prefix for the localization key:
````js
"DisplayName:Name": "Nom"
````
ABP prefers to use the `DisplayName:Name` key over the `Name` key if it does exists.
### Using a Custom Localization Key
If you need, you can use the `[DisplayName]` attribute to specify the localization key for a specific property:
````csharp
[DisplayName("MyNameKey")]
public string Name { get; set; }
````
In this case, you can add an entry to the localization file using the key `MyNameKey`.
> If you use the `[DisplayName]` but not add a corresponding entity to the localization file, then ABP Framework shows the given key as the field name, `MyNameKey` for this case. So, it provides a way to specify a hard coded display name even if you don't need to use the localization system.
## See Also
* [Server Side Validation](../../Validation.md)

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

@ -1,3 +1,205 @@
# Navigation Menu
# ASP.NET Core MVC / Razor Pages UI: Navigation Menu
Every application has a main menu to allow users to navigate to pages/screens of the application. Some applications may contain more than one menu in different sections of the UI.
ABP Framework is a [modular](../../Module-Development-Basics.md) application development framework. **Every module may need to add items to the menu**.
So, ABP Framework **provides a menu infrastructure** where;
* The application or the modules can add items to a menu, without knowing how the menu is rendered.
* The [theme](Theming.md) properly renders the menu.
## Adding Menu Items
In order to add menu items (or manipulate the existing items) you need to create a class implementing the `IMenuContributor` interface.
**Example: Add a *CRM* menu item with *Customers* and *Orders* sub menu items**
```csharp
using System.Threading.Tasks;
using MyProject.Localization;
using Volo.Abp.UI.Navigation;
namespace MyProject.Web.Menus
{
public class MyProjectMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name == StandardMenus.Main)
{
await ConfigureMainMenuAsync(context);
}
}
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
var l = context.GetLocalizer<MyProjectResource>();
context.Menu.AddItem(
new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"])
.AddItem(new ApplicationMenuItem(
name: "MyProject.Crm.Customers",
displayName: l["Menu:Customers"],
url: "/crm/customers")
).AddItem(new ApplicationMenuItem(
name: "MyProject.Crm.Orders",
displayName: l["Menu:Orders"],
url: "/crm/orders")
)
);
}
}
}
```
* This example adds items only to the main menu (`StandardMenus.Main`: see the *Standard Menus* section below).
* It gets a `IStringLocalizer` from `context` to [localize](../../Localization.md) the display names of the menu items.
* Adds the Customers and Orders as children of the CRM menu.
Once you create a menu contributor, you need to add it to the `AbpNavigationOptions` in the `ConfigureServices` method of your module:
````csharp
Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.Add(new MyProjectMenuContributor());
});
````
When you run the application, you will see the menu items added to the main menu:
![nav-main-menu](../../images/nav-main-menu.png)
> The menu is rendered by the current UI theme. So, the look of the main menu can be completely different based on your theme.
Here, a few notes on the menu contributors;
* ABP Framework calls the `ConfigureMenuAsync` method **whenever need to render** the menu.
* Every menu item can have **children**. So, you can add menu items with **unlimited depth** (however, your UI theme may not support unlimited depth).
* Only leaf menu items have `url`s normally. When you click to a parent menu, its sub menu is opened or closed, you don't navigate the `url` of a parent menu item.
* If a menu item has no children and has no `url` defined, then it is not rendered on the UI. This simplifies to authorize the menu items: You only authorize the child items (see the next section). If none of the children are authorized, then the parent automatically disappears.
### Menu Item Properties
There are more options of a menu item (the constructor of the `ApplicationMenuItem` class). Here, the list of all available options;
* `name` (`string`, required): The unique name of the menu item.
* `displayName` (`string`, required): Display name/text of the menu item. You can [localize](../../Localization.md) this as shown before.
* `url` (`string`): The URL of the menu item.
* `icon` (`string`): An icon name. Free [Font Awesome](https://fontawesome.com/) icon classes are supported out of the box. Example: `fa fa-book`. You can use any CSS font icon class as long as you include the necessary CSS files to your application.
* `order` (`int`): The order of the menu item. Default value is `1000`. Items are sorted by the adding order unless you specify an order value.
* `customData` (`object`): A custom object that you can associate to the menu item and use it while rendering the menu item.
* `target` (`string`): Target of the menu item. Can be `null` (default), "_blank", "_*self*", "_parent", "_*top*" or a frame name for web applications.
* `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute.
* `cssClass` (`string`): Additional string classes for the menu item.
### Authorization
As seen above, a menu contributor contributes to the menu dynamically. So, you can perform any custom logic or get menu items from any source.
One use case is the [authorization](Authorization.md). You typically want to add menu items by checking a permission.
**Example: Check if the current user has a permission**
````csharp
if (await context.IsGrantedAsync("MyPermissionName"))
{
//...add menu items
}
````
> You can use `context.AuthorizationService` to directly access to the `IAuthorizationService`.
### Resolving Dependencies
`context.ServiceProvider` can be used to resolve any service dependency.
**Example: Get a service**
````csharp
var myService = context.ServiceProvider.GetRequiredService<IMyService>();
//...use the service
````
> You don't need to care about releasing/disposing services. ABP Framework handles it.
### The Administration Menu
There is a special menu item in the menu menu that is added by the ABP Framework: The *Administration* menu. It is typically used by the pre-built admin [application modules](../../Modules/Index.md):
![nav-main-menu-administration](../../images/nav-main-menu-administration.png)
If you want to add menu items under the *Administration* menu item, you can use the `context.Menu.GetAdministration()` extension method:
````csharp
context.Menu.GetAdministration().AddItem(...)
````
### Manipulating the Existing Menu Items
ABP Framework executes the menu contributors by the [module dependency order](../../Module-Development-Basics.md). So, you can manipulate the menu items that your application or module (directly or indirectly) depends on.
**Example: Set an icon for the `Users` menu item added by the [Identity Module](../../Modules/Identity.md)**
````csharp
var userMenu = context.Menu.FindMenuItem(IdentityMenuNames.Users);
userMenu.Icon = "fa fa-users";
````
> `context.Menu` gives you ability to access to all the menu items those have been added by the previous menu contributors.
## Standard Menus
A menu is a **named** component. An application may contain more than one menus with different, unique names. There are two pre-defined standard menus:
* `Main`: The main menu of the application. Contains links to the page of the application. Defined as a constant: `Volo.Abp.UI.Navigation.StandardMenus.Main`.
* `User`: User profile menu. Defined as a constant: `Volo.Abp.UI.Navigation.StandardMenus.User`.
The `Main` menu already covered above. The `User` menu is available when a user has logged in:
![user-menu](../../images/user-menu.png)
You can add items to the `User` menu by checking the `context.Menu.Name` as shown below:
```csharp
if (context.Menu.Name == StandardMenus.User)
{
//...add items
}
```
## IMenuManager
`IMenuManager` is generally used by the UI [theme](Theming.md) to render the menu items on the UI. So, **you generally don't need to directly use** the `IMenuManager`.
**Example: Getting the `Main` menu items**
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.UI.Navigation;
namespace MyProject.Web.Pages
{
public class IndexModel : PageModel
{
private readonly IMenuManager _menuManager;
public IndexModel(IMenuManager menuManager)
{
_menuManager = menuManager;
}
public async Task OnGetAsync()
{
var mainMenu = await _menuManager.GetAsync(StandardMenus.Main);
foreach (var menuItem in mainMenu.Items)
{
//...
}
}
}
}
```
TODO

8
docs/en/UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md

@ -1,7 +1,5 @@
# Dynamic Forms
`Warning:` Before getting into this document, be sure that you have clearly understood [abp form elements](Form-elements.md) document.
## Introduction
`abp-dynamic-form` creates a bootstrap form for a given c# model.
@ -272,4 +270,8 @@ You can set it yourself by using `[Display()]` attribute of Asp.Net Core. You ca
````csharp
[Display(Name = "Name")]
public string Name { get; set; }
````
````
## See Also
* [Form Elements](Form-elements.md)

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

@ -305,6 +305,27 @@ public class CountersWidgetController : AbpController
> A widget supposed to be refreshed in two ways: In the first way, when you use a `RefreshUrl`, it re-rendered on the server and replaced by the HTML returned from server. In the second way the widget gets data (generally a JSON object) from server and refreshes itself in the client side (see the refresh method in the Widget JavaScript API section).
## AutoInitialize
`WidgetAttribute` has an `AutoInitialize` property (`bool`) that can be set to automatically initialize a widget on page ready & whenever the widget is added to the DOM. The default value is `false`.
If a widget is configured to be auto initialized, then a `WidgetManager` (see below) automatically created and initialized for instances of this widget. This is useful when the widget instances are not grouped and separately works (they don't require to init or refresh together).
Setting the `AutoInitialize` to `true` is equivalent to write such a code yourself:
````js
$('.abp-widget-wrapper[data-widget-name="MySimpleWidget"]')
.each(function () {
var widgetManager = new abp.WidgetManager({
wrapper: $(this),
});
widgetManager.init($(this));
});
````
> `AutoInitialize` also supports widgets loaded/refreshed via AJAX (added to the DOM later) and/or used in a nested way (a widget inside another widget). If you don't need to group multiple widgets and control with a single `WidgetManager`, `AutoInitialize` is the recommended approach.
## JavaScript API
A widget may need to be rendered and refreshed in the client side. In such cases, you can use ABP's `WidgetManager` and define APIs for your widgets.

6
docs/en/UI/Blazor/Overall.md

@ -0,0 +1,6 @@
# Blazor UI for the ABP Framework
The detailed documentation for the Blazor UI is in progress. However, you can follow the documents below to start with the Blazor UI today.
* [Get started](https://docs.abp.io/en/abp/latest/Getting-Started?UI=Blazor) with the Blazor UI for the ABP Framework.
* [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=Blazor) with the Blazor UI.

3
docs/en/UI/Blazor/Services/Notification.md

@ -0,0 +1,3 @@
# Blazor UI Notification
`UiNotificationService` is used to show toastr style notifications on the user interface. The documentation is in progress...

192
docs/en/docs-nav.json

@ -156,7 +156,13 @@
},
{
"text": "Caching",
"path": "Caching.md"
"path": "Caching.md",
"items": [
{
"text": "Redis Cache",
"path": "Redis-Cache.md"
}
]
},
{
"text": "Logging",
@ -386,8 +392,16 @@
"text": "User Interface",
"items": [
{
"text": "ASP.NET Core",
"text": "ASP.NET Core MVC / Razor Pages",
"items": [
{
"text": "Forms & Validation",
"path": "UI/AspNetCore/Forms-Validation.md"
},
{
"text": "Navigation / Menus",
"path": "UI/AspNetCore/Navigation-Menu.md"
},
{
"text": "Client Side Package Management",
"path": "UI/AspNetCore/Client-Side-Package-Management.md"
@ -425,83 +439,133 @@
]
},
{
"text": "Angular",
"text": "Blazor",
"items": [
{
"text": "Migration Guide v2.x to v3",
"path": "UI/Angular/Migration-Guide-v3.md"
},
{
"text": "Environment",
"path": "UI/Angular/Environment.md"
},
{
"text": "Service Proxies",
"path": "UI/Angular/Service-Proxies.md"
},
{
"text": "HTTP Requests",
"path": "UI/Angular/HTTP-Requests.md"
},
{
"text": "Localization",
"path": "UI/Angular/Localization.md"
},
{
"text": "Permission Management",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "Multi Tenancy",
"path": "UI/Angular/Multi-Tenancy.md"
},
{
"text": "Confirmation Popup",
"path": "UI/Angular/Confirmation-Service.md"
},
{
"text": "Toast Overlay",
"path": "UI/Angular/Toaster-Service.md"
},
{
"text": "Config State",
"path": "UI/Angular/Config-State.md"
},
{
"text": "Modifying the Menu",
"path": "UI/Angular/Modifying-the-Menu.md"
},
{
"text": "Component Replacement",
"path": "UI/Angular/Component-Replacement.md"
"text": "Overall",
"path": "UI/Blazor/Overall.md"
},
{
"text": "Custom Setting Page",
"path": "UI/Angular/Custom-Setting-Page.md"
},
"text": "Services",
"items": [
{
"text": "Overall",
"path": "UI/Blazor/Services/Notification.md"
}
]
}
]
},
{
"text": "Angular",
"items": [
{
"text": "Lazy Loading Scripts & Styles",
"path": "UI/Angular/Lazy-Load-Service.md"
"text": "Migration Guide v2.x to v3",
"path": "UI/Angular/Migration-Guide-v3.md"
},
{
"text": "DomInsertionService",
"path": "UI/Angular/Dom-Insertion-Service.md"
"text": "Quick Start",
"path": "UI/Angular/Quick-Start.md"
},
{
"text": "ContentProjectionService",
"path": "UI/Angular/Content-Projection-Service.md"
"text": "Development",
"items": [
{
"text": "Environment Variables",
"path": "UI/Angular/Environment.md"
},
{
"text": "About Feature Libraries",
"path": "UI/Angular/Feature-Libraries.md"
},
{
"text": "Service Proxies",
"path": "UI/Angular/Service-Proxies.md"
},
{
"text": "PWA Configuration",
"path": "UI/Angular/PWA-Configuration.md"
}
]
},
{
"text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md"
"text": "Core Functionality",
"items": [
{
"text": "Config State",
"path": "UI/Angular/Config-State.md"
},
{
"text": "HTTP Requests",
"path": "UI/Angular/HTTP-Requests.md"
},
{
"text": "Localization",
"path": "UI/Angular/Localization.md"
},
{
"text": "Permission Management",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "Multi Tenancy",
"path": "UI/Angular/Multi-Tenancy.md"
}
]
},
{
"text": "SubscriptionService",
"path": "UI/Angular/Subscription-Service.md"
"text": "Utilities",
"items": [
{
"text": "Managing RxJS Subscriptions",
"path": "UI/Angular/Subscription-Service.md"
},
{
"text": "Working with Lists",
"path": "UI/Angular/List-Service.md"
},
{
"text": "Easy *ngFor trackBy",
"path": "UI/Angular/Track-By-Service.md"
},
{
"text": "Inserting Scripts & Styles to DOM",
"path": "UI/Angular/Dom-Insertion-Service.md"
},
{
"text": "Lazy Loading Scripts & Styles",
"path": "UI/Angular/Lazy-Load-Service.md"
},
{
"text": "Projecting Angular Content",
"path": "UI/Angular/Content-Projection-Service.md"
},
{
"text": "Confirmation Popup",
"path": "UI/Angular/Confirmation-Service.md"
},
{
"text": "Toast Overlay",
"path": "UI/Angular/Toaster-Service.md"
}
]
},
{
"text": "ListService",
"path": "UI/Angular/List-Service.md"
"text": "Customization",
"items": [
{
"text": "Modifying the Menu",
"path": "UI/Angular/Modifying-the-Menu.md"
},
{
"text": "Component Replacement",
"path": "UI/Angular/Component-Replacement.md"
},
{
"text": "Custom Setting Page",
"path": "UI/Angular/Custom-Setting-Page.md"
}
]
}
]
},

BIN
docs/en/images/abp-dynamic-form-result.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/images/abp-form-input-validation-error-french-name.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/en/images/abp-form-input-validation-error-french.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
docs/en/images/abp-form-input-validation-error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/en/images/nav-main-menu-administration.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/images/nav-main-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/images/react-native-introduction.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 KiB

BIN
docs/en/images/user-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

2
docs/zh-Hans/Startup-Templates/Module.md

@ -133,7 +133,7 @@ abp new Acme.IssueManagement -t module --no-ui
##### 如何运行?
设置成启动项, 使用包管理控制台运行 `Update-Database` 命令迁移数据库,然后运行应用程序. 默认用户名: `admin` 密码: `1q2w3E*`.
`host/YourProjectName.Web.Unified`设置成启动项, 使用包管理控制台运行 `Update-Database` 命令迁移数据库,然后运行应用程序. 默认用户名: `admin` 密码: `1q2w3E*`.
#### 分离部署&数据库 场景

6
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicTheme.cs

@ -1,5 +1,6 @@
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme
@ -15,6 +16,11 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme
{
options.AdditionalAssemblies.Add(typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule).Assembly);
});
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new BasicThemeToolbarContributor());
});
}
}
}

20
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeToolbarContributor.cs

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme
{
public class BasicThemeToolbarContributor : IToolbarContributor
{
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name == StandardToolbars.Main)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(LanguageSwitch)));
context.Toolbar.Items.Add(new ToolbarItem(typeof(LoginDisplay)));
}
return Task.CompletedTask;
}
}
}

7
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor

@ -17,7 +17,10 @@
<DropdownItem Clicked="@(() => NavigateTo(menuItem.Url))">@menuItem.DisplayName</DropdownItem>
}
}
<Divider />
<a class="dropdown-item" href="@ServerAccountUrl">
@UiLocalizer["ManageYourAccount"]
</a>
<DropdownDivider />
<DropdownItem Clicked="BeginSignOut">Logout</DropdownItem>
</DropdownMenu>
</Dropdown>
@ -32,7 +35,7 @@
{
Navigation.NavigateTo(uri);
}
private async Task BeginSignOut()
{
await SignOutManager.SetSignOutState();

40
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs

@ -1,18 +1,50 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.Http.Client;
using Volo.Abp.UI.Navigation;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic
{
public partial class LoginDisplay
public partial class LoginDisplay : IDisposable
{
[Inject] protected IMenuManager MenuManager { get; set; }
[Inject]
protected IMenuManager MenuManager { get; set; }
[Inject]
protected IStringLocalizer<AbpUiResource> UiLocalizer { get; set; }
[Inject]
protected IOptions<AbpRemoteServiceOptions> RemoteServiceOptions { get; set; }
protected ApplicationMenu Menu { get; set; }
protected string ServerUrl { get; set; }
protected string ServerAccountUrl { get; set; }
protected override async Task OnInitializedAsync()
{
Menu = await MenuManager.GetAsync(StandardMenus.User);
ServerUrl = RemoteServiceOptions.Value.RemoteServices.Default?.BaseUrl?.TrimEnd('/');
ServerAccountUrl = ServerUrl + "/Account/Manage?returnUrl=" + Navigation.Uri;
Navigation.LocationChanged += OnLocationChanged;
}
protected virtual void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
ServerAccountUrl = ServerUrl + "/Account/Manage?returnUrl=" + Navigation.Uri;
StateHasChanged();
}
public void Dispose()
{
Navigation.LocationChanged -= OnLocationChanged;
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor

@ -13,14 +13,7 @@
<ul class="navbar-nav mx-auto">
<NavMenu/>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<LanguageSwitch/>
</li>
<li class="nav-item">
<LoginDisplay/>
</li>
</ul>
<NavToolbar/>
</div>
</div>
</nav>

46
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor

@ -2,6 +2,50 @@
{
foreach (var menuItem in Menu.Items)
{
<NavMenuItem MenuItem="@menuItem" />
var elementId = menuItem.ElementId ?? "MenuItem_" + menuItem.Name.Replace(".", "_");
var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass;
var disabled = menuItem.IsDisabled ? "disabled" : string.Empty;
var url = string.IsNullOrEmpty(menuItem.Url) ? "#" : menuItem.Url;
if (menuItem.IsLeaf)
{
if (menuItem.Url != null)
{
<li class="nav-item @cssClass @disabled" id="@elementId">
<a class="nav-link" href="@url">
@if (menuItem.Icon != null)
{
if (menuItem.Icon.StartsWith("fa"))
{
<i class="@menuItem.Icon"></i>
}
}
@menuItem.DisplayName
</a>
</li>
}
}
else
{
<li class="nav-item">
<div class="dropdown">
<a class="nav-link dropdown-toggle" href="#" id="Menu_@(menuItem.Name)" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@if (menuItem.Icon != null)
{
if (menuItem.Icon.StartsWith("fa"))
{
<i class="@menuItem.Icon"></i>
}
}
@menuItem.DisplayName
</a>
<div class="dropdown-menu border-0 shadow-sm" aria-labelledby="Menu_@(menuItem.Name)">
@foreach (var childMenuItem in menuItem.Items)
{
<NavMenuItem MenuItem="childMenuItem"/>
}
</div>
</div>
</li>
}
}
}

55
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenuItem.razor

@ -2,51 +2,46 @@
@{
var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_");
var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass;
var disabled = MenuItem.IsDisabled ? "disabled" : string.Empty;
var url = string.IsNullOrEmpty(MenuItem.Url) ? "#" : MenuItem.Url;
}
@if (MenuItem.IsLeaf)
{
if (MenuItem.Url != null)
{
<li class="nav-item @cssClass" disabled="@MenuItem.IsDisabled">
<NavLink class="nav-link" href="@url" id="@elementId">
@if (MenuItem.Icon != null)
<a class="dropdown-item @cssClass @disabled" href="@url" id="@elementId">
@if (MenuItem.Icon != null)
{
if (MenuItem.Icon.StartsWith("fa"))
{
if (MenuItem.Icon.StartsWith("fa"))
{
<i class="@MenuItem.Icon"></i>
}
<i class="@MenuItem.Icon"></i>
}
@MenuItem.DisplayName
</NavLink>
</li>
}
@MenuItem.DisplayName
</a>
}
}
else
{
<li class="nav-item">
<div class="dropdown">
<a class="nav-link dropdown-toggle" href="#" id="@elementId" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@if (MenuItem.Icon != null)
{
if (MenuItem.Icon.StartsWith("fa"))
{
<i class="@MenuItem.Icon"></i>
}
}
<div class="dropdown-submenu">
<a role="button" class="btn dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span class="lp-icon">
<i class="@(MenuItem.Icon ?? "")"></i>
</span>
<span class="lp-text">
@MenuItem.DisplayName
</a>
<div class="dropdown-menu border-0 shadow-sm" aria-labelledby="@elementId">
@foreach (var childMenuItem in MenuItem.Items)
{
<NavMenuItem MenuItem="@childMenuItem" />
}
</div>
</span>
</a>
<div class="dropdown-menu border-0 shadow-sm">
@foreach (var childMenuItem in MenuItem.Items)
{
<NavMenuItem MenuItem="@childMenuItem"/>
}
</div>
</li>
</div>
}
@code {
[Parameter]
public ApplicationMenuItem MenuItem { get; set; }
}
}

8
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor

@ -0,0 +1,8 @@
<ul class="navbar-nav">
@foreach (var render in ToolbarItemRenders)
{
<li class="nav-item">
@render
</li>
}
</ul>

32
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavToolbar.razor.cs

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic
{
public partial class NavToolbar
{
[Inject]
private IToolbarManager ToolbarManager { get; set; }
private List<RenderFragment> ToolbarItemRenders { get; set; } = new List<RenderFragment>();
protected override async Task OnInitializedAsync()
{
var toolbar = await ToolbarManager.GetAsync(StandardToolbars.Main);
ToolbarItemRenders.Clear();
foreach (var item in toolbar.Items)
{
ToolbarItemRenders.Add(builder =>
{
builder.OpenComponent(0, item.ComponentType);
builder.CloseComponent();
});
}
}
}
}

36
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpUtilsService.cs

@ -0,0 +1,36 @@
using System.Threading.Tasks;
using Microsoft.JSInterop;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming
{
public class AbpUtilsService : IAbpUtilsService, ITransientDependency
{
protected IJSRuntime JsRuntime { get; }
public AbpUtilsService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public ValueTask AddClassToTagAsync(string tagName, string className)
{
return JsRuntime.InvokeVoidAsync("abp.utils.addClassToTag", tagName, className);
}
public ValueTask RemoveClassFromTagAsync(string tagName, string className)
{
return JsRuntime.InvokeVoidAsync("abp.utils.removeClassFromTag", tagName, className);
}
public ValueTask<bool> HasClassOnTagAsync(string tagName, string className)
{
return JsRuntime.InvokeAsync<bool>("abp.utils.hasClassOnTag", tagName, className);
}
public ValueTask ReplaceLinkHrefByIdAsync(string linkId, string hrefValue)
{
return JsRuntime.InvokeVoidAsync("abp.utils.replaceLinkHrefById", linkId, hrefValue);
}
}
}

15
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/IAbpUtilsService.cs

@ -0,0 +1,15 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming
{
public interface IAbpUtilsService
{
ValueTask AddClassToTagAsync(string tagName, string className);
ValueTask RemoveClassFromTagAsync(string tagName, string className);
ValueTask<bool> HasClassOnTagAsync(string tagName, string className);
ValueTask ReplaceLinkHrefByIdAsync(string linkId, string hrefValue);
}
}

16
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/AbpToolbarOptions.cs

@ -0,0 +1,16 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public class AbpToolbarOptions
{
[NotNull]
public List<IToolbarContributor> Contributors { get; }
public AbpToolbarOptions()
{
Contributors = new List<IToolbarContributor>();
}
}
}

29
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/IToolbarConfigurationContext.cs

@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Localization;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public interface IToolbarConfigurationContext : IServiceProviderAccessor
{
Toolbar Toolbar { get; }
IAuthorizationService AuthorizationService { get; }
IStringLocalizerFactory StringLocalizerFactory { get; }
Task<bool> IsGrantedAsync(string policyName);
[CanBeNull]
IStringLocalizer GetDefaultLocalizer();
[NotNull]
public IStringLocalizer GetLocalizer<T>();
[NotNull]
public IStringLocalizer GetLocalizer(Type resourceType);
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/IToolbarContributor.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public interface IToolbarContributor
{
Task ConfigureToolbarAsync(IToolbarConfigurationContext context);
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/IToolbarManager.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public interface IToolbarManager
{
Task<Toolbar> GetAsync(string name);
}
}

7
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/StandardToolbars.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public static class StandardToolbars
{
public const string Main = "Main";
}
}

18
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/Toolbar.cs

@ -0,0 +1,18 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public class Toolbar
{
public string Name { get; }
public List<ToolbarItem> Items { get; }
public Toolbar([NotNull] string name)
{
Name = Check.NotNull(name, nameof(name));
Items = new List<ToolbarItem>();
}
}
}

68
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/ToolbarConfigurationContext.cs

@ -0,0 +1,68 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public class ToolbarConfigurationContext : IToolbarConfigurationContext
{
public IServiceProvider ServiceProvider { get; }
private readonly object _serviceProviderLock = new object();
private TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
if (reference == null)
{
lock (_serviceProviderLock)
{
if (reference == null)
{
reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
}
}
}
return reference;
}
public IAuthorizationService AuthorizationService => LazyGetRequiredService(typeof(IAuthorizationService), ref _authorizationService);
private IAuthorizationService _authorizationService;
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(typeof(IStringLocalizerFactory),ref _stringLocalizerFactory);
public Toolbar Toolbar { get; }
public ToolbarConfigurationContext(Toolbar toolbar, IServiceProvider serviceProvider)
{
Toolbar = toolbar;
ServiceProvider = serviceProvider;
}
public Task<bool> IsGrantedAsync(string policyName)
{
return AuthorizationService.IsGrantedAsync(policyName);
}
[CanBeNull]
public IStringLocalizer GetDefaultLocalizer()
{
return StringLocalizerFactory.CreateDefaultOrNull();
}
[NotNull]
public IStringLocalizer GetLocalizer<T>()
{
return StringLocalizerFactory.Create<T>();
}
[NotNull]
public IStringLocalizer GetLocalizer(Type resourceType)
{
return StringLocalizerFactory.Create(resourceType);
}
}
}

23
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/ToolbarItem.cs

@ -0,0 +1,23 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public class ToolbarItem
{
public Type ComponentType
{
get => _componentType;
set => _componentType = Check.NotNull(value, nameof(value));
}
private Type _componentType;
public int Order { get; set; }
public ToolbarItem([NotNull] Type componentType, int order = 0)
{
Order = order;
ComponentType = Check.NotNull(componentType, nameof(componentType));
}
}
}

39
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Toolbars/ToolbarManager.cs

@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Toolbars
{
public class ToolbarManager : IToolbarManager, ITransientDependency
{
protected AbpToolbarOptions Options { get; }
protected IServiceProvider ServiceProvider { get; }
public ToolbarManager(
IOptions<AbpToolbarOptions> options,
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Options = options.Value;
}
public async Task<Toolbar> GetAsync(string name)
{
var toolbar = new Toolbar(name);
using (var scope = ServiceProvider.CreateScope())
{
var context = new ToolbarConfigurationContext(toolbar, scope.ServiceProvider);
foreach (var contributor in Options.Contributors)
{
await contributor.ConfigureToolbarAsync(context);
}
}
return toolbar;
}
}
}

35
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/wwwroot/abp_theming.js

@ -0,0 +1,35 @@
var abp = abp || {};
(function () {
abp.utils = abp.utils || {};
abp.utils.addClassToTag = function (tagName, className) {
var tags = document.getElementsByTagName(tagName);
for (var i = 0; i < tags.length; i++) {
tags[i].classList.add(className);
}
};
abp.utils.removeClassFromTag = function (tagName, className) {
var tags = document.getElementsByTagName(tagName);
for (var i = 0; i < tags.length; i++) {
tags[i].classList.remove(className);
}
};
abp.utils.hasClassOnTag = function (tagName, className) {
var tags = document.getElementsByTagName(tagName);
if (tags.length) {
return tags[0].classList.contains(className);
}
return false;
};
abp.utils.replaceLinkHrefById = function (linkId, hrefValue) {
var link = document.getElementById(linkId);
if (link && link.href !== hrefValue) {
link.href = hrefValue;
}
};
})();

8
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs

@ -6,7 +6,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.AspNetCore.Components.WebAssembly;
using Volo.Abp.AspNetCore.Mvc.Client;
using Volo.Abp.Modularity;
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
@ -42,9 +41,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
using (var scope = serviceProvider.CreateScope())
{
await scope.ServiceProvider
.GetRequiredService<ICachedApplicationConfigurationClient>()
.InitializeAsync();
foreach (var service in scope.ServiceProvider.GetServices<IAsyncInitialize>())
{
await service.InitializeAsync();
}
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
public interface IUiNotificationService
{
Task Info(string message);
}
}

13
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs

@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
public class NullUiNotificationService : IUiNotificationService, ITransientDependency
{
public Task Info(string message)
{
return Task.CompletedTask;
}
}
}

21
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs

@ -3,26 +3,43 @@ using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.AspNetCore.Mvc.Client;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.DynamicProxying;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
[ExposeServices(
typeof(WebAssemblyCachedApplicationConfigurationClient),
typeof(ICachedApplicationConfigurationClient),
typeof(IAsyncInitialize)
)]
public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency
{
protected IHttpClientProxy<IAbpApplicationConfigurationAppService> Proxy { get; }
protected ApplicationConfigurationCache Cache { get; }
protected ICurrentTenantAccessor CurrentTenantAccessor { get; }
public WebAssemblyCachedApplicationConfigurationClient(
IHttpClientProxy<IAbpApplicationConfigurationAppService> proxy,
ApplicationConfigurationCache cache)
ApplicationConfigurationCache cache,
ICurrentTenantAccessor currentTenantAccessor)
{
Proxy = proxy;
Cache = cache;
CurrentTenantAccessor = currentTenantAccessor;
}
public virtual async Task InitializeAsync()
{
Cache.Set(await Proxy.Service.GetAsync());
var configurationDto = await Proxy.Service.GetAsync();
Cache.Set(configurationDto);
CurrentTenantAccessor.Current = new BasicTenantInfo(
configurationDto.CurrentTenant.Id,
configurationDto.CurrentTenant.Name
);
}
public virtual Task<ApplicationConfigurationDto> GetAsync()

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

Loading…
Cancel
Save