Browse Source

Merge branch 'dev' into ux-enhancements

pull/18842/head
Enis Necipoglu 3 years ago
parent
commit
2761cdbb77
No known key found for this signature in database GPG Key ID: 1EC55E13241E1680
  1. 4
      Directory.Packages.props
  2. 29
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  3. 1
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  4. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json
  5. 46
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  6. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json
  7. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hr.json
  8. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json
  9. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ru.json
  10. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
  11. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json
  12. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json
  13. 33
      docs/en/Getting-Started-Running-Solution.md
  14. 2
      docs/en/Migration-Guides/Abp-8_0.md
  15. 197
      docs/en/UI/Angular/HTTP-Error-Handling.md
  16. 144
      docs/en/UI/Angular/HTTP-Requests.md
  17. 15
      docs/en/docs-nav.json
  18. 1
      framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj
  19. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/IMenuItemAdminAppService.cs
  20. 10
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemWithDetailsDto.cs
  21. 3
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs
  22. 15
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs
  23. 4
      modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/Volo/CmsKit/Admin/Menus/MenuItemAdminClientProxy.Generated.cs
  24. 3104
      modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json
  25. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Menus/MenuItemAdminController.cs
  26. 3
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebAutoMapperProfile.cs
  27. 45
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml
  28. 26
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml.cs
  29. 45
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml
  30. 21
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml.cs
  31. 26
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/createModal.js
  32. 20
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/updateModal.js
  33. 5
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  34. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs
  35. 6
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItemManager.cs
  36. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Pages/IPageRepository.cs
  37. 5
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Pages/EfCorePageRepository.cs
  38. 6
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs
  39. 78
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml
  40. 36
      modules/cms-kit/test/Volo.CmsKit.Application.Tests/Menus/MenuItemAdminAppService_Tests.cs
  41. 14
      modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Menus/MenuManager_Test.cs
  42. 17
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs
  43. 4
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs
  44. 19
      modules/cms-kit/test/Volo.CmsKit.TestBase/Pages/PageRepository_Test.cs
  45. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/cs.json
  46. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de-DE.json
  47. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de.json
  48. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en-GB.json
  49. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/es.json
  50. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/fi.json
  51. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/fr.json
  52. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/hr.json
  53. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/hu.json
  54. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/it.json
  55. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/nl.json
  56. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json
  57. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pt-BR.json
  58. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ro-RO.json
  59. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/sk.json
  60. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/sl.json
  61. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json
  62. 6
      npm/ng-packs/packages/core/src/lib/services/rest.service.ts
  63. 21
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  64. 4
      npm/ng-packs/packages/theme-shared/src/lib/models/common.ts
  65. 10
      npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts
  66. 4
      npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts
  67. 3
      npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts
  68. 7
      templates/app/aspnet-core/README.md

4
Directory.Packages.props

@ -6,7 +6,7 @@
<PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="2.0.24" />
<PackageVersion Include="aliyun-net-sdk-sts" Version="3.1.2" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
<PackageVersion Include="AsyncKeyedLock" Version="6.3.3" />
<PackageVersion Include="AsyncKeyedLock" Version="6.3.4" />
<PackageVersion Include="Autofac" Version="8.0.0" />
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
@ -27,7 +27,7 @@
<PackageVersion Include="Dapper" Version="2.1.21" />
<PackageVersion Include="Dapr.AspNetCore" Version="1.12.0" />
<PackageVersion Include="Dapr.Client" Version="1.12.0" />
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.1.151.7" />
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.3.10.8" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.5" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.2" />
<PackageVersion Include="DeepL.net" Version="1.8.0" />

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

@ -563,8 +563,35 @@
"RegistrationUrl": "Registration Url",
"AllowAbpStudioBetaAccess": "Allow ABP Studio Beta Access",
"TotalQuestionCanNotBeNullMessage": "Total Question can not be null",
"Permission:OrganizationAutoRenewalPayments": "Organization Auto Renewal Payments",
"Permission:RetryFailedPayments": "Retry Failed Payments",
"AutoRenewalIsNotEnabled": "Auto Renewal is not enabled!",
"LicenseIsNotExpired": "License is not expired!",
"PaymentNotFound": "Payment not found!",
"PaymentAlreadyTried": "Payment already tried!",
"PaymentIsNotFailed": "Payment is not failed!",
"OrganizationIdIsNull": "OrganizationId is null!",
"Menu:AutoRenewals": "Auto Renewals",
"OrganizationAutoRenewalPayments": "Organization Auto Renewal Payments",
"PaymentDate": "Payment Date",
"TryCount": "Try Count",
"ErrorMessage": "Error Message",
"ErrorCode": "Error Code",
"CreditCard": "Credit Card",
"BuyerName": "Buyer Name",
"BuyerSurname": "Buyer Surname",
"BuyerEmail": "Buyer Email",
"AutoRenewalStartTime": "Auto Renewal Start Time",
"AutoRenewalEndTime": "Auto Renewal End Time",
"AutoRenewalEnabled": "Auto Renewal Enabled",
"LastAutoRenewalPaymentTime": "Last Auto Renewal Payment Time",
"OrganizationDoesNotHaveACreditCard": "Organization does not have a credit card!",
"Permission:EditWinners": "Edit Winners",
"Permission:ChangeDrawingStatus": "Change Drawing Status",
"RemoveAllWinnersConfirmationMessage": "Are you sure you want to remove all winners?"
"Menu:Licenses": "Licenses",
"OrganizationId": "Organization Id",
"RemoveAllWinnersConfirmationMessage": "Are you sure you want to remove all winners?",
"AutoRenewals": "Auto Renewals",
"OrganizationHasCreditCard": "Organization has credit card. Please remove credit card first!"
}
}

1
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -29,6 +29,7 @@
"Volo.AbpIo.Domain:030012": "A user is entitled to have only 1 free trial period. You already used your trial license.",
"Volo.AbpIo.Domain:030013": "A user with an active license cannot start a trial license.",
"Volo.AbpIo.Domain:040000": "Telemetry already exists!.",
"Volo.AbpIo.Domain:050000": "Organization has credit card. You can not delete it!",
"Volo.AbpIo.Domain:070000": "The organization name can only contain latin letters, numbers, dots and hyphens!",
"Volo.AbpIo.Domain:070001": "The company name can only contain latin letters, numbers, dots, space and hyphens!",
"WantToLearn?": "Want to learn?",

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

@ -531,8 +531,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "تاريخ بدء الترخيص - تاريخ انتهاء الصلاحية",
"MyOrganizations_Detail_OwnerRightInfo": "أنت تستخدم {0} من {1} حقوق المالك.",
"MyOrganizations_Detail_CopyApiKey": "انسخ المفتاح",
"MyOrganizations_Detail_ApiKeyDescription": "مفتاح API هو الرمز المميز لحزم PRO المستضافة على <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "مصدر NuGet الخاص بك هو <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "مفتاح API هو الرمز المميز لحزم PRO المستضافة على <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "مصدر NuGet الخاص بك هو <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "تتم إضافة هذا تلقائيًا كوجز إلى NuGet.Config في حل ABP الخاص بك. لا تشارك مفتاحك الخاص مع مستخدمين غير مصرح لهم!",
"MyOrganizations_Detail_DeveloperSeatInfo": "أنت تستخدم {0} من {1} مقاعد مطوري البرامج.",
"NeedMoreSeatsForYourTeam": "هل تحتاج إلى المزيد من المقاعد لفريقك؟",
@ -615,7 +615,7 @@
"Tools_Page_Description": "يوفر ABP التجاري أدوات تطوير سريعة للتطبيقات لزيادة إنتاجية المطورين. يتيح لك ABP Suite إنشاء صفحات CRUD بسهولة.",
"DeveloperPrice": "سعر المطور",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} <small>مطورين</small>",
"LicenseRemainingDays": "لمدة <span> {0} </span> يوم",
"LicenseRemainingDays": "لمدة <span class=\"text-white\"> {0} </span> يوم",
"ExtendPaymentInfoSection_Description": "من خلال تمديد / تجديد رخصتك، ستستمر في الحصول على <a href=\"{0}\" target=\"_blank\">الدعم المتميز</a>. ستتمكن أيضًا من الحصول على تحديثات رئيسية أو ثانوية للوحدات والسمات. ستتمكن من الاستمرار في إنشاء مشاريع جديدة. وستظل قادرًا على استخدام <a href=\"{1}\" target=\"_blank\">ABP Suite</a> مما يسرع من تطويرك.",
"LicenseRenewalPrice": "سعر تجديد الرخصة",
"LicensePrice": "سعر الرخصة",

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

@ -556,8 +556,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "License Start Date - Expiry Date",
"MyOrganizations_Detail_OwnerRightInfo": "You are using {0} of your {1} owners rights.",
"MyOrganizations_Detail_CopyApiKey": "Copy the Key",
"MyOrganizations_Detail_ApiKeyDescription": "The API Key is the token of PRO packages hosted on <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Your private NuGet source is <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "The API Key is the token of PRO packages hosted on <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Your private NuGet source is <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "This is automatically added as a feed to your NuGet.Config in your ABP solution. Do not share your private key with unauthorized users!",
"MyOrganizations_Detail_DeveloperSeatInfo": "You are using {0} of your {1} developer seats.",
"NeedMoreSeatsForYourTeam": "Need more seats for your team?",
@ -641,7 +641,7 @@
"Tools_Page_Description": "ABP Commercial provides rapid application development tooling to increase developer productivity. ABP Suite allows you to create CRUD pages easily.",
"DeveloperPrice": "Developer Price",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} <small>developers</small>",
"LicenseRemainingDays": "for <span> {0} </span> days",
"LicenseRemainingDays": "for <span class=\"text-white\"> {0} </span> days",
"ExtendPaymentInfoSection_Description": "By extending/renewing your license, you will continue to get <a href=\"{0}\" target=\"_blank\">premium support</a>. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. And you will still be able to use <a href=\"{1}\" target=\"_blank\">ABP Suite</a> which speeds up your development.",
"LicenseRenewalPrice": "License renewal price",
"LicensePrice": "License Price",
@ -838,7 +838,7 @@
"ModulesPageTitle": "ABP Pre-Built Application Modules",
"Volo.AbpIo.Commercial:040001": "API Access Key is incorrect.",
"GetLepton": "Get Lepton Now",
"MyOrganizations_Detail_LicenseStartDate": "License Start Date",
"MyOrganizations_Detail_LicenseStartDate": "Start Date",
"MyOrganizations_Detail_LicenseExpiryDate": "Expiry Date",
"BlazoriseSupport": "How do I get the Blazorise license key and support from the Blazorise team?",
"BlazoriseSupportExplanation": "Follow the steps below to get support from the Blazorise team and get your Blazorise license key:",
@ -1090,8 +1090,46 @@
"AbpStudioBetaAccessInfoTitle": "ABP Studio Beta Access",
"AbpStudioBetaAccessInfoText": "We're thrilled to share with you the <span class=\"text-highlight-white\">beta</span> version of <span class=\"text-highlight-white\">ABP Studio</span>! This release marks a significant milestone in our development journey, and we're eager to gather your feedback to make the application even better.",
"YouAreNotAuthorizedToDownloadStudio": "You are not authorized to download ABP Studio.",
"OrganizationHasNoDefaultCreditCard": "Your organization has no default credit card. Please add a credit card to your organization.",
"YouAreNotAuthorizedToPayThisPaymentRequest": "You are not authorized to pay this payment request.",
"YouAreNotAuthorizedToCreateBillingInfoForThisPaymentRequest": "You are not authorized to create billing info for this payment request.",
"OrganizationNotFound": "Organization not found.",
"CannotDeleteDefaultCardBecauseAutoRenewalEnabled": "You cannot delete this card at the moment because the Auto-Renewal feature is enabled. To delete the card, first disable Auto-Renewal.",
"AreYouSureWantToDeleteThisCard": "Are you sure you want to delete this card?",
"AreYouSureWantToSetThisCardAsDefault": "Are you sure you want to set this card as default?",
"OrganizationBillingInfoIsNotSuitableForIyzicoPayment": "Your organization's billing info is not suitable for iyzico payment.",
"AutomaticRenewal": "Automatic Renewal",
"AutomaticRenewal_Description": "Renewing a license before it expires lets you get a discount of up to %40. The auto-renewal process lets you renew your license without losing this discount, and your development will never interrupt. Auto-renewal is only available for credit card payment. You can disable auto-renewal at any time by accessing your Organization Management page. ABP does not save your credit card information, but our global payment gateways do secure savings.",
"CardNotFoundMessage": "Do you want to add a new card?",
"CardNotFoundTitle": "Card Not Found",
"AutoRenewalEnabled": "Auto Renewal Enabled",
"AutoRenewalDisabled": "Auto Renewal Disabled",
"PaymentRequestIdIsNotProvided": "Payment request id is not provided.",
"PaymentFailedInfo": "Sorry, payment failed! This could be due to insufficient funds, invalid credit card numbers or invalid pin",
"UsedPayment": "This payment has been already used",
"ABPStudioBetaAccess": "ABP Studio Beta Access",
"VisitABPStudio": "Visit ABP Studio",
"EditBillingInformation": "Edit Billing Information",
"Organization": "Organization",
"E-Book": "E-Book",
"CreditCards": "Credit Cards",
"BillingInformation": "Billing Information",
"AddNewCreditCard": "Add New Credit Card",
"MyOrganizations_LearnMorePlan": "Learn more about plans on the pricing page",
"AutoLicenseRenewalIsNotEnabled": "Auto license renewal is not enabled.",
"SetAsDefaultPaymentMethod": "Set as default payment method",
"{0}PerAdditionalDeveloper": "{0} per additional developer",
"CardAlias": "Card Alias",
"AbpDoesNotSaveYourPaymentDetails_Description": "The payment data will be saved in <a href=\"{0}\" target=\"_blank\" class=\"{1}\">{2}</a> security vaults and you can remove stored data anytime. Enabling auto-renewal ensures that your ABP subscription will automatically renew prior to expiration, providing a valid credit card. Disabling auto-renewal means you will have to renew your subscription manually. To continue your project development without interruption, we suggest you enable the Auto-Renewal option.",
"AddBillingInformation": "Add Billing Information",
"YouHaveNoCardsSaved": "You have no cards saved.",
"CreateCreditCardModal_BillingDetails_Description": "You must save your billing details to be able to add your credit card.",
"TaxNo": "Tax No",
"CardNumber": "Card Number",
"NameOnCard": "Name on Card",
"BillingDetails": "Billing Details",
"ThereIsNoDeveloper": "There is no developer.",
"CardDetails": "Card Details",
"NoActiveLicence": "You are not eligible for this action! You have no active license."
}
}

6
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json

@ -553,8 +553,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "Lisenssin alkamispäivä - viimeinen voimassaolopäivä",
"MyOrganizations_Detail_OwnerRightInfo": "Käytät {0} {1} omistajan oikeuksistasi.",
"MyOrganizations_Detail_CopyApiKey": "Kopioi avain",
"MyOrganizations_Detail_ApiKeyDescription": "API-avain on <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1}-palvelussa isännöivien PRO-pakettien tunnus.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Yksityinen NuGet-lähteesi on <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "API-avain on <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1}-palvelussa isännöivien PRO-pakettien tunnus.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Yksityinen NuGet-lähteesi on <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "Tämä lisätään automaattisesti syötteeksi NuGet.Configiin ABP-ratkaisussasi. Älä jaa yksityistä avaintasi luvattomien käyttäjien kanssa!",
"MyOrganizations_Detail_DeveloperSeatInfo": "Käytät {0} {1} kehittäjäpaikastasi.",
"NeedMoreSeatsForYourTeam": "Tarvitsetko lisää paikkoja tiimillesi?",
@ -639,7 +639,7 @@
"Tools_Page_Description": "ABP Commercial tarjoaa nopeat sovelluskehitystyökalut kehittäjien tuottavuuden lisäämiseksi. ABP Suiten avulla voit luoda CRUD-sivuja helposti.",
"DeveloperPrice": "Kehittäjän hinta",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} <small>kehittäjät</small>",
"LicenseRemainingDays": "<span> {0}</span> päivän ajan",
"LicenseRemainingDays": "<span class=\"text-white\"> {0}</span> päivän ajan",
"ExtendPaymentInfoSection_Description": "Pidentämällä/uusimalla käyttölupaasi saat edelleen <a href=\"{0}\" target=\"_blank\">premium-tuen</a>. Voit myös saada suurempia tai pieniä päivityksiä moduuleille ja teemoille. Voit jatkaa uusien projektien luomista. Voit silti käyttää <a href=\"{1}\" target=\"_blank\">ABP Suitea</a>, joka nopeuttaa kehitystäsi.",
"LicenseRenewalPrice": "Lisenssin uusimisen hinta",
"LicensePrice": "Lisenssin hinta",

6
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hr.json

@ -556,8 +556,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "Datum početka licence - Datum isteka",
"MyOrganizations_Detail_OwnerRightInfo": "Koristite {0} od svojih {1} vlasničkih prava.",
"MyOrganizations_Detail_CopyApiKey": "Kopirajte ključ",
"MyOrganizations_Detail_ApiKeyDescription": "API ključ je token PRO paketa hostiranih na <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Vaš privatni NuGet izvor je <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "API ključ je token PRO paketa hostiranih na <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Vaš privatni NuGet izvor je <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "Ovo se automatski dodaje kao feed u vaš NuGet.Config u vašem ABP rješenju. Ne dijelite svoj privatni ključ s neovlaštenim korisnicima!",
"MyOrganizations_Detail_DeveloperSeatInfo": "Koristite {0} od svojih {1} razvojnih mjesta.",
"NeedMoreSeatsForYourTeam": "Trebate više mjesta za svoj tim?",
@ -641,7 +641,7 @@
"Tools_Page_Description": "ABP Commercial pruža alate za brzi razvoj aplikacija za povećanje produktivnosti programera. ABP Suite vam omogućuje jednostavno stvaranje CRUD stranica.",
"DeveloperPrice": "Cijena programera",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} <small>programeri</small>",
"LicenseRemainingDays": "za <span>{0}</span> dana",
"LicenseRemainingDays": "za <span class=\"text-white\">{0}</span> dana",
"ExtendPaymentInfoSection_Description": "Produljenjem/obnavljanjem licence i dalje ćete dobivati <a href=\"{0}\" target=\"_blank\">premium podršku</a> . Također ćete moći dobiti veća ili manja ažuriranja za module i teme. Moći ćete nastaviti stvarati nove projekte. I dalje ćete moći koristiti <a href=\"{1}\" target=\"_blank\">ABP Suite</a> koji ubrzava vaš razvoj.",
"LicenseRenewalPrice": "Cijena obnove licence",
"LicensePrice": "Cijena licence",

6
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json

@ -554,8 +554,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "licensz kezdő dátuma – lejárati dátum",
"MyOrganizations_Detail_OwnerRightInfo": "Ön {1} tulajdonosi jogának {0}-át használja.",
"MyOrganizations_Detail_CopyApiKey": "Másolja ki a kulcsot",
"MyOrganizations_Detail_ApiKeyDescription": "Az API-kulcs a(z) <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1} webhelyen tárolt PRO-csomagok tokenje.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Az Ön privát NuGet-forrása <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "Az API-kulcs a(z) <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1} webhelyen tárolt PRO-csomagok tokenje.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Az Ön privát NuGet-forrása <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "Ez automatikusan hozzáadódik feedként a NuGet.Config-hoz az ABP-megoldásban. Ne ossza meg privát kulcsát illetéktelen felhasználókkal!",
"MyOrganizations_Detail_DeveloperSeatInfo": "Ön {1} fejlesztői helyéből {0}-ot használ.",
"NeedMoreSeatsForYourTeam": "Több hozzáférésre van szüksége a csapatának?",
@ -639,7 +639,7 @@
"Tools_Page_Description": "Az ABP Commercial gyors alkalmazásfejlesztési eszközöket biztosít a fejlesztők termelékenységének növelése érdekében. Az ABP Suite segítségével könnyedén hozhat létre CRUD oldalakat.",
"DeveloperPrice": "Fejlesztői ár",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} <small>fejlesztő</small>",
"LicenseRemainingDays": "<span>{0}</span> napig",
"LicenseRemainingDays": "<span class=\"text-white\">{0}</span> napig",
"ExtendPaymentInfoSection_Description": "A licensz meghosszabbításával/megújításával továbbra is <a href=\"{0}\" target=\"_blank\">prémium támogatást</a> kap. Emellett kisebb-nagyobb frissítéseket is kaphat a modulokhoz és témákhoz. Folytathatja új projektek létrehozását. És továbbra is használhatja az <a href=\"{1}\" target=\"_blank\">ABP Suite</a> -ot, amely felgyorsítja a fejlesztést.",
"LicenseRenewalPrice": "Licensz megújítási ára",
"LicensePrice": "Licensz ára",

6
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ru.json

@ -557,8 +557,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "Дата начала действия лицензии – дата истечения срока действия",
"MyOrganizations_Detail_OwnerRightInfo": "Вы используете {0} ваших прав владельца {1}.",
"MyOrganizations_Detail_CopyApiKey": "Скопируйте ключ",
"MyOrganizations_Detail_ApiKeyDescription": "Ключ API — это токен PRO-пакетов, размещенных на <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Ваш личный источник NuGet: <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "Ключ API — это токен PRO-пакетов, размещенных на <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1}.</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "Ваш личный источник NuGet: <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "Он автоматически добавляется в качестве канала в ваш NuGet.Config в вашем решении ABP. ",
"MyOrganizations_Detail_DeveloperSeatInfo": "Вы используете {0} из {1} мест разработчика.",
"NeedMoreSeatsForYourTeam": "Вам нужно больше мест для вашей команды?",
@ -642,7 +642,7 @@
"Tools_Page_Description": "ABP Commercial предоставляет инструменты для быстрой разработки приложений, позволяющие повысить продуктивность разработчиков. ",
"DeveloperPrice": "Цена застройщика",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} <small>Разработчики</small>",
"LicenseRemainingDays": "для <span> {0} </span> дни",
"LicenseRemainingDays": "для <span class=\"text-white\"> {0} </span> дни",
"ExtendPaymentInfoSection_Description": "Продлив/продлив лицензию, вы продолжите получать <a href=\"{0}\" target=\"_blank\">премиум-поддержка</a>. <a href=\"{1}\" target=\"_blank\">ABP Люкс</a> что ускоряет ваше развитие.",
"LicenseRenewalPrice": "Стоимость продления лицензии",
"LicensePrice": "Цена лицензии",

6
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json

@ -563,8 +563,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "Lisans Başlangıç Tarihi - Sona Erme Tarihi",
"MyOrganizations_Detail_OwnerRightInfo": "{1} sahiplik hakkınızın {0}'ını kullanıyorsunuz.",
"MyOrganizations_Detail_CopyApiKey": "Anahtarı Kopyala",
"MyOrganizations_Detail_ApiKeyDescription": "API Anahtarı, <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1}</a> üzerinde barındırılan PRO paketlerinin belirtecidir.",
"MyOrganizations_Detail_YourPrivateNugetSource": "Özel NuGet kaynağınız <a href=\"{0}\" arget=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "API Anahtarı, <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1}</a> üzerinde barındırılan PRO paketlerinin belirtecidir.",
"MyOrganizations_Detail_YourPrivateNugetSource": "Özel NuGet kaynağınız <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "Bu, ABP çözümünüzdeki NuGet.Config'inize otomatik olarak bir besleme olarak eklenir. Özel anahtarınızı yetkisiz kullanıcılarla paylaşmayın!",
"MyOrganizations_Detail_DeveloperSeatInfo": "{1} geliştirici koltuğunuzun {0} tanesini kullanıyorsunuz.",
"NeedMoreSeatsForYourTeam": "Takımınız için daha fazla geliştirici koltuğu gerekiyor mu?",
@ -648,7 +648,7 @@
"Tools_Page_Description": "ABP Commercial, geliştirici verimliliğini artırmak için hızlı uygulama geliştirme araçları sağlar. ABP Suite, CRUD sayfalarını kolayca oluşturmanızı sağlar.",
"DeveloperPrice": "Geliştirici Fiyatı",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} <small>geliştiriciler</small>",
"LicenseRemainingDays": "<span> {0} </span> gün boyunca",
"LicenseRemainingDays": "<span class=\"text-white\"> {0} </span> gün boyunca",
"ExtendPaymentInfoSection_Description": "Lisansınızı uzatarak/yenileyerek <a href=\"{0}\" target=\"_blank\">premium destek</a> almaya devam edeceksiniz. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. Ve gelişiminizi hızlandıran <a href=\"{1}\" target=\"_blank\">ABP Suite</a>'i kullanmaya devam edebileceksiniz.",
"LicenseRenewalPrice": "Lisans yenileme fiyatı",
"LicensePrice": "Lisans fiyatı",

6
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json

@ -556,8 +556,8 @@
"MyOrganizations_Detail_LicenseStartAndExpiryDate": "许可证开始日期 - 到期日期",
"MyOrganizations_Detail_OwnerRightInfo": "您正在使用{1}所有者权利中的{0}项权利。",
"MyOrganizations_Detail_CopyApiKey": "复制密钥",
"MyOrganizations_Detail_ApiKeyDescription": "API 密钥是 <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1}.</a> 上托管的 PRO 包的标记。",
"MyOrganizations_Detail_YourPrivateNugetSource": "您的 NuGet 私有源是 <a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_ApiKeyDescription": "API 密钥是 <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{1}.</a> 上托管的 PRO 包的标记。",
"MyOrganizations_Detail_YourPrivateNugetSource": "您的 NuGet 私有源是 <a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "此密钥会自动添加到 ABP 解决方案中的 NuGet.Config 中。请勿与未经授权的用户共享您的私人密钥!",
"MyOrganizations_Detail_DeveloperSeatInfo": "您正在使用{1}个开发人员席位中的{0}个。",
"NeedMoreSeatsForYourTeam": "您的团队需要更多座位?",
@ -641,7 +641,7 @@
"Tools_Page_Description": "ABP 商业版提供快速应用程序开发工具,以提高开发人员的工作效率。ABP Suite 可让您轻松创建 CRUD 页面。",
"DeveloperPrice": "开发者价格",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0}<小>开发人员</小",
"LicenseRemainingDays": "为<span> {0} </span> 天",
"LicenseRemainingDays": "为<span class=\"text-white\"> {0} </span> 天",
"ExtendPaymentInfoSection_Description": "延长/续订许可证后,您将继续获得<a href=\"{0}\" target=\"_blank\">高级支持</a>。您还可以获得模块和主题的主要或次要更新。您还可以继续创建新项目。您还可以使用 <a href=\"{1}\" target=\"_blank\">ABP Suite</a> 来加快开发速度。",
"LicenseRenewalPrice": "许可证续期价格",
"LicensePrice": "许可证价格",

4
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json

@ -545,7 +545,7 @@
"MyOrganizations_Detail_OwnerRightInfo": "您正在使用您的 {1} 項所有者權利中的 {0} 項。",
"MyOrganizations_Detail_CopyApiKey": "複製密鑰",
"MyOrganizations_Detail_ApiKeyDescription": "API 金鑰是<a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{1} 上託管的 PRO 包的令牌。</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "您的私有 NuGet 來源是<a href=\"{0}\" target=\"_blank\" class=\"text-primary\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_YourPrivateNugetSource": "您的私有 NuGet 來源是<a href=\"{0}\" target=\"_blank\" rel=\"noopener\">{0}</a>",
"MyOrganizations_Detail_PrivateNugetSourceWarning": "這會作為 feed 自動添加到 ABP 解決方案中的 NuGet.Config 中。不要與未經授權的用戶分享您的私鑰!",
"MyOrganizations_Detail_DeveloperSeatInfo": "您正在使用 {1} 個開發者席位中的 {0} 個。",
"NeedMoreSeatsForYourTeam": "您的團隊需要更多座位嗎?",
@ -628,7 +628,7 @@
"Tools_Page_Description": "ABP Commercial 提供快速應用程式開發工具來提高開發人員的工作效率。 ABP Suite 讓您輕鬆建立 CRUD 頁面。",
"DeveloperPrice": "開發商價格",
"AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0}<small>開發者</small>",
"LicenseRemainingDays": "<span>{0}</span>天",
"LicenseRemainingDays": "<span class=\"text-white\">{0}</span>天",
"ExtendPaymentInfoSection_Description": "透過延長/更新您的許可證,您將繼續獲得<a href=\"{0}\" target=\"_blank\">高級支援</a>。您還可以獲得模組和主題的主要或次要更新。您將能夠繼續建立新專案。而且您仍然可以使用<a href=\"{1}\" target=\"_blank\">ABP Suite</a>來加快您的開發速度。",
"LicenseRenewalPrice": "許可證更新價格",
"LicensePrice": "許可價格",

33
docs/en/Getting-Started-Running-Solution.md

@ -127,6 +127,29 @@ abp bundle
> **Note**: Before starting the application, run `abp install-libs` command in your Web directory to restore the client-side libraries. This will populate the `libs` folder.
{{ if UI == "BlazorServer" }}
> **Important:** The `.AuthServer` application serves as the **Authentication Server** for the `.Blazor` application. It is essential to have the `.AuthServer` application running in the background to ensure the proper functioning of the `.Blazor` application.
To do this, open terminal in `.AuthServer` project folder and run the following command.
````bash
dotnet run
````
Once the `.AuthServer`application has started, it is time to run `.HttpApi.Host` application.
> **Important:** Prior to launching the `.Blazor` project, it is essential to execute the `.HttpApi.Host` application as well.
To do this, open terminal in `.HttpApi.Host` project folder and run the following command.
````bash
dotnet run
````
Once the `.AuthServer` and `.HttpApi.Host` applications has started, you can proceed to run the `.Blazor` project.
{{ end # UI }}
{{ if Tiered == "Yes" }}
> Tiered solutions use **Redis** as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.
@ -195,6 +218,16 @@ You can see the application APIs and test them here. Get [more info](https://swa
### Running the Blazor Application (Client Side)
> **Important:** The `.HttpApi.Host` application serves as the **Authentication Server** for the `.Blazor` application. It is essential to have the `.HttpApi.Host` application running in the background to ensure the proper functioning of the `.Blazor` application.
To do this, you can open terminal in `.HttpApi.Host` project folder and run the following command.
````bash
dotnet run
````
Once the `.HttpApi.Host` application has started, you can proceed to run the `.Blazor` application.
Ensure that the `.Blazor` project is the startup project and run the application.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.

2
docs/en/Migration-Guides/Abp-8_0.md

@ -181,7 +181,7 @@ You can see the following list of NuGet libraries that have been upgraded with .
| Dapper | 2.0.123 | 2.1.21 |
| Dapr.AspNetCore | 1.9.0 | 1.12.0 |
| Dapr.Client | 1.9.0 | 1.12.0 |
| Devart.Data.Oracle.EFCore | 10.1.134.7 | 10.1.151.7 |
| Devart.Data.Oracle.EFCore | 10.1.134.7 | 10.3.10.8 |
| DistributedLock.Core | 1.0.4 | 1.0.5 |
| DistributedLock.Redis | 1.0.1 | 1.0.2 |
| EphemeralMongo.Core | 1.1.0 | 1.1.3 |

197
docs/en/UI/Angular/HTTP-Error-Handling.md

@ -0,0 +1,197 @@
# HTTP Error Handling
When the `RestService` is used, all HTTP errors are reported to the [`HttpErrorReporterService`](./HTTP-Error-Reporter-Service), and then `ErrorHandler`, a service exposed by the `@abp/ng.theme.shared` package automatically handles the errors.
## Custom HTTP Error Handler
### Function Method `Deprecated`
A custom HTTP error handler can be registered to an injection token named `HTTP_ERROR_HANDLER`. If a custom handler function is registered, the `ErrorHandler` executes that function.
See an example:
```ts
// http-error-handler.ts
import { ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { HttpErrorResponse } from '@angular/common/http';
import { Injector } from '@angular/core';
import { of, EMPTY } from 'rxjs';
import { Error404Component } from './error404/error404.component';
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 400) {
const toaster = injector.get(ToasterService);
toaster.error(httpError.error?.error?.message || 'Bad request!', '400');
return EMPTY;
}
if (httpError.status === 404) {
const contentProjection = injector.get(ContentProjectionService);
contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component));
return EMPTY;
}
return of(httpError);
}
// app.module.ts
import { Error404Component } from './error404/error404.component';
import { handleHttpErrors } from './http-error-handling';
import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared';
@NgModule({
// ...
providers: [
// ...
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }
],
declarations: [
//...
Error404Component],
})
export class AppModule {}
```
In the example above:
- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs.
- 400 bad request errors is handled. When a 400 error occurs.
- Since `of(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors.
**Note 1:** If you put `return EMPTY` to next line of handling an error, default error handling will not work for that error. [EMPTY](https://rxjs.dev/api/index/const/EMPTY) can be imported from `rxjs`.
```ts
export function handleHttpErrors(
injector: Injector,
httpError: HttpErrorResponse
) {
if (httpError.status === 403) {
// handle 403 errors here
return EMPTY; // put return EMPTY to skip default error handling
}
}
```
**Note 2:** If you put `return of(httpError)`, default error handling will work.
- `of` is a function. It can be imported from `rxjs`.
- `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`.
```ts
import { of } from "rxjs";
export function handleHttpErrors(
injector: Injector,
httpError: HttpErrorResponse
) {
if (httpError.status === 500) {
// handle 500 errors here
}
// you can return the of(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above.
return of(httpError);
}
```
### Service Method
You can provide **more than one handler** with services, a custom HTTP error handler service can be registered with injection token named **`CUSTOM_ERROR_HANDLERS`**. ABP has some default [error handlers](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts).
### How To Add New Handler Service
ABP error handler services are implements the interface of **CustomHttpErrorHandlerService**.
**Interface of `CUSTOM_ERROR_HANDLERS`**
```ts
interface CustomHttpErrorHandlerService {
readonly priority: number;
canHandle(error: unknown): boolean;
execute(): void;
}
```
- **`priority`** ABP sorts the services according to the number of the priority variable. Higher priority will be checked first.
- **`canHandle`** Check if the service can handle the error. Returns boolean.
- **`execute`** If the service can handle the error, then run the execute method.
**In Summary**
- Services are sorted by their priority number.
- Start from highest priority service and run canHandle() method. Pick the service if can handle the error, if not check next service.
- If the service found, run the execute method of a service. Done.
See an example:
```ts
// custom-error-handler.service.ts
import { inject, Injectable } from "@angular/core";
import { HttpErrorResponse } from "@angular/common/http";
import { CustomHttpErrorHandlerService } from "@abp/ng.theme.shared";
import { CUSTOM_HTTP_ERROR_HANDLER_PRIORITY } from "@abp/ng.theme.shared";
import { ToasterService } from "@abp/ng.theme.shared";
@Injectable({ providedIn: "root" })
export class MyCustomErrorHandlerService
implements CustomHttpErrorHandlerService
{
// You can write any number here, ex: 9999
readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh;
protected readonly toaster = inject(ToasterService);
private error: HttpErrorResponse | undefined = undefined;
// What kind of error should be handled by this service? You can decide it in this method. If error is suitable to your case then return true; otherwise return false.
canHandle(error: unknown): boolean {
if (error instanceof HttpErrorResponse && error.status === 400) {
this.error = error;
return true;
}
return false;
}
// If this service is picked from ErrorHandler, this execute method will be called.
execute() {
this.toaster.error(
this.error.error?.error?.message || "Bad request!",
"400"
);
}
}
```
```ts
// app.module.ts
import { CUSTOM_ERROR_HANDLERS, ... } from '@abp/ng.theme.shared';
import { MyCustomErrorHandlerService } from './custom-error-handler.service';
@NgModule({
// ...
providers: [
// ...
{
provide: CUSTOM_ERROR_HANDLERS,
useExisting: MyCustomErrorHandlerService,
multi: true,
}
]
})
export class AppModule {}
```
In the example above:
- Created a service named `MyCustomErrorHandlerService`, and provided via `useExisting` key because we dont want another instance of it. And set `multi` key to true because ABP default error handlers are also provided with **CUSTOM_ERROR_HANDLERS** injection token.
- 400 errors are handled from custom `MyCustomErrorHandlerService`. When a 400 error occurs, backend error message will be displayed as shown below:
![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg)
### Notes
- If your service cannot handle the error. Then ABP will check the next Error Service.
- If none of the service handle the error. Then basic confirmation message about the error will be shown to the user.
- You can provide more than one service, with CUSTOM_ERROR_HANDLER injection token.
- If you want your custom service to be evaluated (checked) earlier, set the priority variable high.

144
docs/en/UI/Angular/HTTP-Requests.md

@ -1,7 +1,5 @@
# How to Make HTTP Requests
## About HttpClient
Angular has the amazing [HttpClient](https://angular.io/guide/http) for communication with backend services. It is a layer on top and a simplified representation of [XMLHttpRequest Web API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). It also is the recommended agent by Angular for any HTTP request. There is nothing wrong with using the `HttpClient` in your ABP project.
@ -23,14 +21,10 @@ Although clear and flexible, handling errors this way is repetitive work, even w
An `HttpInterceptor` is able to catch `HttpErrorResponse` and can be used for a centralized error handling. Nevertheless, cases where default error handler, therefore the interceptor, must be disabled require additional work and comprehension of Angular internals. Check [this issue](https://github.com/angular/angular/issues/20203) for details.
## RestService
ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by default.
### Getting Started with RestService
In order to use the `RestService`, you must inject it in your class as a dependency.
@ -48,11 +42,9 @@ class DemoService {
You do not have to provide the `RestService` at module or component/directive level, because it is already **provided in root**.
### How to Make a Request with RestService
You can use the `request` method of the `RestService` is for HTTP requests. Here is an example:
You can use the `request` method of the `RestService` is for HTTP requests. Here is an example:
```js
getFoo(id: number) {
@ -65,8 +57,6 @@ getFoo(id: number) {
}
```
The `request` method always returns an `Observable<T>`. Therefore you can do the following wherever you use `getFoo` method:
```js
@ -79,12 +69,8 @@ doSomethingWithFoo(id: number) {
}
```
**You do not have to worry about unsubscription.** The `RestService` uses `HttpClient` behind the scenes, so every observable it returns is a finite observable, i.e. it closes subscriptions automatically upon success or error.
As you see, `request` method gets a request options object with `Rest.Request<T>` type. This generic type expects the interface of the request body. You may pass `null` when there is no body, like in a `GET` or a `DELETE` request. Here is an example where there is one:
```js
@ -99,11 +85,7 @@ postFoo(body: Foo) {
}
```
You may [check here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23) for complete `Rest.Request<T>` type, which has only a few changes compared to [HttpRequest](https://angular.io/api/common/http/HttpRequest) class in Angular.
You may [check here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23) for complete `Rest.Request<T>` type, which has only a few changes compared to [HttpRequest](https://angular.io/api/common/http/HttpRequest) class in Angular.
### How to Disable Default Error Handler of RestService
@ -120,8 +102,6 @@ deleteFoo(id: number) {
}
```
`skipHandleError` config option, when set to `true`, disables the error handler and the returned observable starts throwing an error that you can catch in your subscription.
```js
@ -137,14 +117,10 @@ removeFooFromList(id: number) {
}
```
### How to Get a Specific API Endpoint From Application Config
Another nice config option that `request` method receives is `apiName` (available as of v2.4), which can be used to get a specific module endpoint from application configuration.
```js
putFoo(body: Foo, id: string) {
const request: Rest.Request<Foo> = {
@ -157,8 +133,6 @@ putFoo(body: Foo, id: string) {
}
```
`putFoo` above will request `https://localhost:44305/api/some/path/to/foo/{id}` as long as the environment variables are as follows:
```js
@ -167,19 +141,17 @@ putFoo(body: Foo, id: string) {
export const environment = {
apis: {
default: {
url: 'https://localhost:44305',
url: "https://localhost:44305",
},
foo: {
url: 'https://localhost:44305/api/some/path/to/foo',
url: "https://localhost:44305/api/some/path/to/foo",
},
},
/* rest of the environment variables here */
}
};
```
### How to Observe Response Object or HTTP Events Instead of Body
`RestService` assumes you are generally interested in the body of a response and, by default, sets `observe` property as `'body'`. However, there may be times you are rather interested in something else, such as a custom proprietary header. For that, the `request` method receives `observe` property in its config object.
@ -202,104 +174,6 @@ getSomeCustomHeaderValue() {
You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10).
## HTTP Error Handling
When the `RestService` is used, all HTTP errors are reported to the [`HttpErrorReporterService`](./HTTP-Error-Reporter-Service), and then `ErrorHandler`, a service exposed by the `@abp/ng.theme.shared` package automatically handles the errors.
### Custom HTTP Error Handler
A custom HTTP error handler can be registered to an injection token named `HTTP_ERROR_HANDLER`. If a custom handler function is registered, the `ErrorHandler` executes that function.
See an example:
```js
// http-error-handler.ts
import { ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { HttpErrorResponse } from '@angular/common/http';
import { Injector } from '@angular/core';
import { throwError } from 'rxjs';
import { Error404Component } from './error404/error404.component';
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 400) {
const toaster = injector.get(ToasterService);
toaster.error(httpError.error?.error?.message || 'Bad request!', '400');
return;
}
if (httpError.status === 404) {
const contentProjection = injector.get(ContentProjectionService);
contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component));
return;
}
return throwError(httpError);
}
// app.module.ts
import { Error404Component } from './error404/error404.component';
import { handleHttpErrors } from './http-error-handling';
import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared';
@NgModule({
// ...
providers: [
// ...
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }
],
declarations: [
//...
Error404Component],
})
export class AppModule {}
```
In the example above:
- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs.
- 400 bad request errors is handled. When a 400 error occurs, backend error message will be displayed as shown below:
![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg)
- 404 not found errors is handled. When a 404 error occurs, `Error404Component` will be appended to the `<body>` as shown below:
![custom-error-handler-404-component](images/custom-error-handler-404-component.jpg)
- Since `throwError(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors.
**Note 1:** If you put `return` to next line of handling an error, default error handling will not work for that error.
```js
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 403) {
// handle 403 errors here
return; // put return to skip default error handling
}
}
```
**Note 2:** If you put `return throwError(httpError)`, default error handling will work.
- `throwError` is a function. It can be imported from `rxjs`.
- `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`.
```js
import { throwError } from 'rxjs';
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 500) {
// handle 500 errors here
return;
}
// you can return the throwError(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above.
return throwError(httpError)
}
```
### How to Skip HTTP interceptors and ABP headers
The ABP Framework adds several HTTP headers to the HttpClient, such as the "Auth token" or "tenant Id".
@ -309,4 +183,8 @@ The ABP Http interceptors check the value of the `IS_EXTERNAL_REQUEST` token. If
The `ExternalHttpClient` extends from `HTTPClient` and sets the `IS_EXTERNAL_REQUEST` context token to true.
When you are using `ExternalHttpClient` as HttpClient in your components, it does not add ABP-specific headers.
Note: With `IS_EXTERNAL_REQUEST` or without it, ABP loading service works.
Note: With `IS_EXTERNAL_REQUEST` or without it, ABP loading service works.
## See Also
- [HTTP Error Handling / Customization](./HTTP-Error-Handling)

15
docs/en/docs-nav.json

@ -1053,7 +1053,16 @@
},
{
"text": "HTTP Requests",
"path": "UI/Angular/HTTP-Requests.md"
"items": [
{
"text": "How to Make HTTP Requests",
"path": "UI/Angular/HTTP-Requests.md"
},
{
"text": "HTTP Error Handling / Customization",
"path": "UI/Angular/HTTP-Error-Handling.md"
}
]
},
{
"text": "Localization",
@ -1173,8 +1182,8 @@
"path": "UI/Angular/Content-Security-Strategy.md"
},
{
"text":"Abp Window Service",
"path":"UI/Angular/Abp-Window-Service.md"
"text": "Abp Window Service",
"path": "UI/Angular/Abp-Window-Service.md"
}
]
},

1
framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj

@ -21,7 +21,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" VersionOverride="7.0.14" />
<PackageReference Include="Devart.Data.Oracle.EFCore" />
</ItemGroup>

2
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/IMenuItemAdminAppService.cs

@ -10,7 +10,7 @@ public interface IMenuItemAdminAppService : IApplicationService
{
Task<ListResultDto<MenuItemDto>> GetListAsync();
Task<MenuItemDto> GetAsync(Guid id);
Task<MenuItemWithDetailsDto> GetAsync(Guid id);
Task<MenuItemDto> CreateAsync(MenuItemCreateInput input);

10
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemWithDetailsDto.cs

@ -0,0 +1,10 @@
using System;
using Volo.CmsKit.Menus;
namespace Volo.CmsKit.Admin.Menus;
[Serializable]
public class MenuItemWithDetailsDto : MenuItemDto
{
public string? PageTitle { get; set; }
}

3
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs

@ -46,5 +46,8 @@ public class CmsKitAdminApplicationAutoMapperProfile : Profile
CreateMap<MediaDescriptor, MediaDescriptorDto>().MapExtraProperties();
CreateMap<MenuItem, MenuItemDto>().MapExtraProperties();
CreateMap<MenuItem, MenuItemWithDetailsDto>()
.Ignore(x => x.PageTitle)
.MapExtraProperties();
}
}

15
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs

@ -43,10 +43,17 @@ public class MenuItemAdminAppService : CmsKitAdminAppServiceBase, IMenuItemAdmin
);
}
public virtual async Task<MenuItemDto> GetAsync(Guid id)
public virtual async Task<MenuItemWithDetailsDto> GetAsync(Guid id)
{
var menu = await MenuItemRepository.GetAsync(id);
return ObjectMapper.Map<MenuItem, MenuItemDto>(menu);
var menuItem = await MenuItemRepository.GetAsync(id);
var dto = ObjectMapper.Map<MenuItem, MenuItemWithDetailsDto>(menuItem);
if (menuItem.PageId.HasValue)
{
dto.PageTitle = await PageRepository.FindTitleAsync(menuItem.PageId.Value);
}
return dto;
}
[Authorize(CmsKitAdminPermissions.Menus.Create)]
@ -87,7 +94,7 @@ public class MenuItemAdminAppService : CmsKitAdminAppServiceBase, IMenuItemAdmin
}
else
{
menuItem.SetUrl(input.Url);
MenuManager.SetPageUrl(menuItem, input.Url);
}
menuItem.SetDisplayName(input.DisplayName);

4
modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/Volo/CmsKit/Admin/Menus/MenuItemAdminClientProxy.Generated.cs

@ -23,9 +23,9 @@ public partial class MenuItemAdminClientProxy : ClientProxyBase<IMenuItemAdminAp
return await RequestAsync<ListResultDto<MenuItemDto>>(nameof(GetListAsync));
}
public virtual async Task<MenuItemDto> GetAsync(Guid id)
public virtual async Task<MenuItemWithDetailsDto> GetAsync(Guid id)
{
return await RequestAsync<MenuItemDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
return await RequestAsync<MenuItemWithDetailsDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
{
{ typeof(Guid), id }
});

3104
modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json

File diff suppressed because it is too large

2
modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Menus/MenuItemAdminController.cs

@ -36,7 +36,7 @@ public class MenuItemAdminController : CmsKitAdminController, IMenuItemAdminAppS
[HttpGet]
[Route("{id}")]
public virtual Task<MenuItemDto> GetAsync(Guid id)
public virtual Task<MenuItemWithDetailsDto> GetAsync(Guid id)
{
return MenuItemAdminAppService.GetAsync(id);
}

3
modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebAutoMapperProfile.cs

@ -1,4 +1,5 @@
using AutoMapper;
using Volo.Abp.AutoMapper;
using Volo.CmsKit.Admin.Blogs;
using Volo.CmsKit.Admin.Menus;
using Volo.CmsKit.Admin.Pages;
@ -37,7 +38,7 @@ public class CmsKitAdminWebAutoMapperProfile : Profile
{
CreateMap<Volo.CmsKit.Admin.Web.Pages.CmsKit.Menus.MenuItems.CreateModalModel.MenuItemCreateViewModel, MenuItemCreateInput>().MapExtraProperties();
CreateMap<Volo.CmsKit.Admin.Web.Pages.CmsKit.Menus.MenuItems.UpdateModalModel.MenuItemUpdateViewModel, MenuItemUpdateInput>().MapExtraProperties();
CreateMap<MenuItemDto, Volo.CmsKit.Admin.Web.Pages.CmsKit.Menus.MenuItems.UpdateModalModel.MenuItemUpdateViewModel>().MapExtraProperties();
CreateMap<MenuItemWithDetailsDto, Volo.CmsKit.Admin.Web.Pages.CmsKit.Menus.MenuItems.UpdateModalModel.MenuItemUpdateViewModel>().MapExtraProperties();
}
protected virtual void CreatePageMappings()

45
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml

@ -19,28 +19,45 @@
Layout = null;
}
<form asp-page="/CmsKit/Menus/MenuItems/CreateModal">
<form id="menu-item-form" asp-page="/CmsKit/Menus/MenuItems/CreateModal">
<abp-modal id="menu-create-modal">
<abp-modal-header title="@L["New"].Value"></abp-modal-header>
<abp-modal-body>
<abp-tabs tab-style="Tab">
<abp-tab name="url" title="@L["Url"]">
<abp-input asp-for="ViewModel.Url"/>
@if (Model.IsPageFeatureEnabled)
{
<abp-tabs tab-style="Tab">
<abp-tab name="url" title="@L["Url"]">
<abp-input asp-for="ViewModel.Url" />
</abp-tab>
<abp-tab title="@L["Page"]">
<label class="form-label" asp-for="ViewModel.PageId"></label>
</abp-tab>
<abp-tab title="@L["Page"]">
<select asp-for="ViewModel.PageId"
class="auto-complete-select"
data-autocomplete-api-url="/api/cms-kit-admin/menu-items/lookup/pages"
data-autocomplete-display-property="title"
data-autocomplete-value-property="id"
data-autocomplete-items-property="items"
data-autocomplete-filter-param-name="filter"
data-autocomplete-allow-clear="true"
data-autocomplete-parent-selector="#menu-create-modal">
</select>
<label class="form-label" asp-for="ViewModel.PageId"></label>
<span asp-validation-for="ViewModel.PageId"></span>
</abp-tab>
<select class="form-control" asp-for="ViewModel.PageId"></select>
<span asp-validation-for="ViewModel.PageId"></span>
</abp-tab>
</abp-tabs>
<hr />
}
else
{
<abp-input asp-for="ViewModel.Url" />
}
</abp-tabs>
<hr/>
<abp-input asp-for="ViewModel.ParentId"/>
<abp-input asp-for="ViewModel.DisplayName"/>
<abp-input asp-for="ViewModel.IsActive"/>

26
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml.cs

@ -1,35 +1,40 @@
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Features;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.ObjectExtending;
using Volo.CmsKit.Admin.Menus;
using Volo.CmsKit.Features;
using Volo.CmsKit.GlobalFeatures;
namespace Volo.CmsKit.Admin.Web.Pages.CmsKit.Menus.MenuItems;
public class CreateModalModel : CmsKitAdminPageModel
{
protected IMenuItemAdminAppService MenuAdminAppService { get; }
protected IFeatureChecker FeatureChecker { get; }
[BindProperty]
public MenuItemCreateViewModel ViewModel { get; set; }
public CreateModalModel(IMenuItemAdminAppService menuAdminAppService)
public bool IsPageFeatureEnabled { get; set; }
public CreateModalModel(IMenuItemAdminAppService menuAdminAppService, IFeatureChecker featureChecker)
{
MenuAdminAppService = menuAdminAppService;
FeatureChecker = featureChecker;
ViewModel = new MenuItemCreateViewModel();
}
public virtual Task OnGetAsync(Guid? parentId)
public virtual async Task OnGetAsync(Guid? parentId)
{
ViewModel.ParentId = parentId;
return Task.CompletedTask;
IsPageFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled<PagesFeature>()
&& await FeatureChecker.IsEnabledAsync(CmsKitFeatures.PageEnable);
}
public virtual async Task<IActionResult> OnPostAsync()
@ -52,6 +57,7 @@ public class CreateModalModel : CmsKitAdminPageModel
public bool IsActive { get; set; } = true;
[Required]
public string Url { get; set; }
public Guid? PageId { get; set; }

45
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml

@ -19,28 +19,49 @@
Layout = null;
}
<form asp-page="/CmsKit/Menus/MenuItems/UpdateModal">
<form id="menu-item-form" asp-page="/CmsKit/Menus/MenuItems/UpdateModal">
<abp-modal id="menu-update-modal">
<abp-modal-header title="@L["New"].Value"></abp-modal-header>
<abp-modal-header title="@L["Edit"].Value"></abp-modal-header>
<abp-modal-body>
<abp-input asp-for="Id"/>
<abp-input asp-for="ViewModel.ConcurrencyStamp"/>
<abp-tabs tab-style="Tab">
<abp-tab name="url" title="@L["Url"]">
<abp-input asp-for="ViewModel.Url"/>
@if (Model.IsPageFeatureEnabled)
{
<abp-tabs tab-style="Tab">
<abp-tab name="url" title="@L["Url"]">
<abp-input asp-for="ViewModel.Url" />
</abp-tab>
<abp-tab title="@L["Page"]">
</abp-tab>
<abp-tab title="@L["Page"]">
<label class="form-label" asp-for="ViewModel.PageId"></label>
<label class="form-label" asp-for="ViewModel.PageId"></label>
<select asp-for="ViewModel.PageId"
class="auto-complete-select"
data-autocomplete-api-url="/api/cms-kit-admin/menu-items/lookup/pages"
data-autocomplete-display-property="title"
data-autocomplete-value-property="id"
data-autocomplete-items-property="items"
data-autocomplete-filter-param-name="filter"
data-autocomplete-allow-clear="true"
data-autocomplete-parent-selector="#menu-update-modal">
<select class="form-control" asp-for="ViewModel.PageId"></select>
</abp-tab>
<!-- You can define selected option(s) here -->
@if (Model.ViewModel.PageId.HasValue)
{
<option selected value="@Model.ViewModel.PageId">@Model.ViewModel.PageTitle</option>
}
</select>
</abp-tab>
</abp-tabs>
</abp-tabs>
<hr/>
<hr />
}
else
{
<abp-input asp-for="ViewModel.Url" />
}
<abp-input asp-for="ViewModel.DisplayName"/>
<abp-input asp-for="ViewModel.IsActive"/>

21
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml.cs

@ -5,8 +5,12 @@ using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Features;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.ObjectExtending;
using Volo.CmsKit.Admin.Menus;
using Volo.CmsKit.Features;
using Volo.CmsKit.GlobalFeatures;
using Volo.CmsKit.Menus;
namespace Volo.CmsKit.Admin.Web.Pages.CmsKit.Menus.MenuItems;
@ -14,6 +18,7 @@ namespace Volo.CmsKit.Admin.Web.Pages.CmsKit.Menus.MenuItems;
public class UpdateModalModel : CmsKitAdminPageModel
{
protected IMenuItemAdminAppService MenuAdminAppService { get; }
protected IFeatureChecker FeatureChecker { get; }
[BindProperty]
public MenuItemUpdateViewModel ViewModel { get; set; }
@ -22,16 +27,23 @@ public class UpdateModalModel : CmsKitAdminPageModel
[BindProperty(SupportsGet = true)]
public Guid Id { get; set; }
public UpdateModalModel(IMenuItemAdminAppService menuAdminAppService)
public bool IsPageFeatureEnabled { get; set; }
public UpdateModalModel(IMenuItemAdminAppService menuAdminAppService, IFeatureChecker featureChecker)
{
MenuAdminAppService = menuAdminAppService;
FeatureChecker = featureChecker;
IsPageFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled<PagesFeature>();
}
public async Task OnGetAsync()
{
var menuItemDto = await MenuAdminAppService.GetAsync(Id);
ViewModel = ObjectMapper.Map<MenuItemDto, MenuItemUpdateViewModel>(menuItemDto);
IsPageFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled<PagesFeature>()
&& await FeatureChecker.IsEnabledAsync(CmsKitFeatures.PageEnable);
ViewModel = ObjectMapper.Map<MenuItemWithDetailsDto, MenuItemUpdateViewModel>(menuItemDto);
}
public async Task<IActionResult> OnPostAsync()
@ -43,8 +55,6 @@ public class UpdateModalModel : CmsKitAdminPageModel
return new OkObjectResult(result);
}
[AutoMap(typeof(MenuItemDto))]
[AutoMap(typeof(MenuItemUpdateInput), ReverseMap = true)]
public class MenuItemUpdateViewModel : ExtensibleObject, IHasConcurrencyStamp
{
[Required]
@ -52,6 +62,7 @@ public class UpdateModalModel : CmsKitAdminPageModel
public bool IsActive { get; set; }
[Required]
public string Url { get; set; }
public string Icon { get; set; }
@ -64,6 +75,8 @@ public class UpdateModalModel : CmsKitAdminPageModel
public Guid? PageId { get; set; }
public string? PageTitle { get; set; }
[HiddenInput]
public string ConcurrencyStamp { get; set; }
}

26
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/createModal.js

@ -7,36 +7,22 @@ $(function () {
var $pageId = $('#ViewModel_PageId');
var $url = $('#ViewModel_Url');
var $displayName = $('#ViewModel_DisplayName');
var $pageIdClearButton = $('#url-tab');
initSelectPageId();
var $menuItemForm = $('#menu-item-form');
$pageId.on('change', function (params) {
$url.prop('disabled', $pageId.val());
if ($pageId.val())
{
if (!$displayName.val()){
$displayName.val($pageId.text());
$displayName.val($pageId.text().trim());
}
}
})
$pageIdClearButton.click(function (){
$pageId.val('');
$pageId.trigger('change');
})
function initSelectPageId() {
$pageId.data('autocompleteApiUrl', '/api/cms-kit-admin/menu-items/lookup/pages');
$pageId.data('autocompleteDisplayProperty', 'title');
$pageId.data('autocompleteValueProperty', 'id');
$pageId.data('autocompleteItemsProperty', 'items');
$pageId.data('autocompleteFilterParamName', 'filter');
$pageId.data('autocompleteParentSelector', '#menu-create-modal');
abp.dom.initializers.initializeAutocompleteSelects($pageId);
}
$menuItemForm.on('submit', function (e) {
$('[href="#url"]').tab('show');
});
};
return {

20
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/updateModal.js

@ -7,9 +7,7 @@ $(function () {
var $pageId = $('#ViewModel_PageId');
var $url = $('#ViewModel_Url');
var $displayName = $('#ViewModel_DisplayName');
var $pageIdClearButton = $('#url-tab');
initSelectPageId();
var $menuItemForm = $('#menu-item-form');
$pageId.on('change', function (params) {
$url.prop('disabled', $pageId.val());
@ -23,21 +21,9 @@ $(function () {
$pageId.trigger('change');
$pageIdClearButton.click(function () {
$pageId.val('');
$pageId.trigger('change');
$menuItemForm.on('submit', function (e) {
$('[href="#url"]').tab('show');
});
function initSelectPageId() {
$pageId.data('autocompleteApiUrl', '/api/cms-kit-admin/menu-items/lookup/pages');
$pageId.data('autocompleteDisplayProperty', 'title');
$pageId.data('autocompleteValueProperty', 'id');
$pageId.data('autocompleteItemsProperty', 'items');
$pageId.data('autocompleteFilterParamName', 'filter');
$pageId.data('autocompleteParentSelector', '#menu-update-modal');
abp.dom.initializers.initializeAutocompleteSelects($pageId);
}
};
return {

5
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -1,5 +1,5 @@
{
"culture": "en",
"culture": "en",
"texts": {
"AddSubMenuItem": "Add Sub Menu Item",
"AreYouSure": "Are You Sure?",
@ -227,6 +227,7 @@
"CssClass": "CSS Class",
"TagsHelpText": "Tags should be comma-separated (e.g.: tag1, tag2, tag3)",
"ThisPartOfContentCouldntBeLoaded": "This part of content couldn't be loaded.",
"DuplicateCommentAttemptMessage": "Duplicate comment post attempt detected. Your comment has already been submitted."
"DuplicateCommentAttemptMessage": "Duplicate comment post attempt detected. Your comment has already been submitted.",
"NoBlogPostYet": "No blog post yet!"
}
}

2
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs

@ -80,7 +80,7 @@ public class MenuItem : AuditedAggregateRoot<Guid>, IMultiTenant
Url = Check.NotNullOrEmpty(url, nameof(url), MenuItemConsts.MaxUrlLength);
}
internal void SetPageId(Guid pageId)
internal void SetPageId(Guid? pageId)
{
PageId = pageId;
}

6
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItemManager.cs

@ -25,6 +25,12 @@ public class MenuItemManager : CmsKitDomainServiceBase
menuItem.SetUrl(page.Slug.EnsureStartsWith('/'));
}
public virtual void SetPageUrl(MenuItem menuItem, string url)
{
menuItem.SetPageId(null);
menuItem.SetUrl(url);
}
[UnitOfWork]
public virtual async Task MoveAsync(Guid menuItemId, Guid? newParentMenuItemId, int position = 0)
{

2
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Pages/IPageRepository.cs

@ -24,4 +24,6 @@ public interface IPageRepository : IBasicRepository<Page, Guid>
Task<bool> ExistsAsync(string slug, CancellationToken cancellationToken = default);
Task<List<Page>> GetListOfHomePagesAsync(CancellationToken cancellationToken = default);
Task<string?> FindTitleAsync(Guid pageId, CancellationToken cancellationToken = default);
}

5
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Pages/EfCorePageRepository.cs

@ -68,4 +68,9 @@ public class EfCorePageRepository : EfCoreRepository<ICmsKitDbContext, Page, Gui
{
return GetListAsync(x => x.IsHomePage, cancellationToken: GetCancellationToken(cancellationToken));
}
public async Task<string?> FindTitleAsync(Guid pageId, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync()).Where(x => x.Id == pageId).Select(x => x.Title).FirstOrDefaultAsync(cancellationToken);
}
}

6
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs

@ -76,4 +76,10 @@ public class MongoPageRepository : MongoDbRepository<ICmsKitMongoDbContext, Page
{
return GetListAsync(x => x.IsHomePage, cancellationToken: GetCancellationToken(cancellationToken));
}
public async Task<string?> FindTitleAsync(Guid pageId, CancellationToken cancellationToken = default)
{
return await (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.Id == pageId).Select(x => x.Title)
.FirstOrDefaultAsync(cancellationToken);
}
}

78
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml

@ -59,38 +59,48 @@
<hr />
}
<abp-row id="blogs-container">
@foreach (var blog in Model.Blogs.Items)
{
<abp-column size="_12" size-md="_6" size-lg="_4">
<abp-card>
@if (blog.CoverImageMediaId != null)
{
<img src="/api/cms-kit/media/@blog.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" />
}
else
{
<img src="@(dummyImageSource)?text=@blog.Title" class="card-img-top" />
}
<abp-card-body class="p-4">
<h5>@blog.Title</h5>
<p class="mb-2">
<span class="font-weight-bold author-name-span" data-author-id="@blog.Author.Id">@@@blog.Author?.UserName</span>
<small style="opacity:.65;">@blog.CreationTime</small>
</p>
<p style="min-height: 60px;">@blog.ShortDescription</p>
<div class="d-grid gap-2">
<a href="/@CmsBlogsWebConsts.BlogsRoutePrefix/@Model.BlogSlug/@blog.Slug" class="btn btn-light">
@L["Read"]
</a>
</div>
</abp-card-body>
</abp-card>
@if (Model.Blogs.TotalCount > 0)
{
<abp-row id="blogs-container">
@foreach (var blog in Model.Blogs.Items)
{
<abp-column size="_12" size-md="_6" size-lg="_4">
<abp-card>
@if (blog.CoverImageMediaId != null)
{
<img src="/api/cms-kit/media/@blog.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" />
}
else
{
<img src="@(dummyImageSource)?text=@blog.Title" class="card-img-top" />
}
<abp-card-body class="p-4">
<h5>@blog.Title</h5>
<p class="mb-2">
<span class="font-weight-bold author-name-span" data-author-id="@blog.Author.Id">@@@blog.Author?.UserName</span>
<small style="opacity:.65;">@blog.CreationTime</small>
</p>
<p style="min-height: 60px;">@blog.ShortDescription</p>
<div class="d-grid gap-2">
<a href="/@CmsBlogsWebConsts.BlogsRoutePrefix/@Model.BlogSlug/@blog.Slug" class="btn btn-light">
@L["Read"]
</a>
</div>
</abp-card-body>
</abp-card>
</abp-column>
}
</abp-row>
<abp-row>
<abp-column>
<abp-paginator model="Model.PagerModel" />
</abp-column>
}
</abp-row>
<abp-row>
<abp-column>
<abp-paginator model="Model.PagerModel" />
</abp-column>
</abp-row>
</abp-row>
}
else
{
<div class="w-100 h-100 d-flex flex-column align-items-center justify-content-center py-5">
<h1><i class="fa fa-ban"></i></h1>
<h3>@L["NoBlogPostYet"]</h3>
</div>
}

36
modules/cms-kit/test/Volo.CmsKit.Application.Tests/Menus/MenuItemAdminAppService_Tests.cs

@ -32,6 +32,15 @@ public class MenuItemAdminAppService_Tests : CmsKitApplicationTestBase
menu.ShouldNotBeNull();
}
[Fact]
public async Task GetAsync_PageTitleShouldBeCorrect_WithRelatedPage()
{
var menu = await MenuAdminAppService.GetAsync(TestData.MenuItem_4_With_Page_1_Id);
menu.ShouldNotBeNull();
menu.PageTitle.ShouldBe(TestData.Page_1_Title);
}
[Fact]
public async Task GetListAsync_ShouldWorkProperly()
{
@ -39,7 +48,7 @@ public class MenuItemAdminAppService_Tests : CmsKitApplicationTestBase
result.ShouldNotBeNull();
result.Items.ShouldNotBeEmpty();
result.Items.Count.ShouldBe(3);
result.Items.Count.ShouldBe(4);
}
[Fact]
@ -48,7 +57,7 @@ public class MenuItemAdminAppService_Tests : CmsKitApplicationTestBase
var name = "My Awesome Menu";
var menu = await MenuAdminAppService.CreateAsync(new MenuItemCreateInput
{
DisplayName = name
DisplayName = name,
});
menu.ShouldNotBeNull();
@ -77,6 +86,29 @@ public class MenuItemAdminAppService_Tests : CmsKitApplicationTestBase
menu.Url.ShouldBe(newUrl);
}
[Fact]
public async Task UpdateAsync_ShouldRemoveRelation_WithoutPageId()
{
var newUrl = "/my-new-url";
await WithUnitOfWorkAsync(async () =>
{
await MenuAdminAppService.UpdateAsync(
TestData.MenuItem_4_With_Page_1_Id,
new MenuItemUpdateInput
{
DisplayName = TestData.MenuItem_4_With_Page_1_Name,
Url = newUrl,
PageId = null
});
});
var menu = await MenuRepository.FindAsync(TestData.MenuItem_4_With_Page_1_Id);
menu.ShouldNotBeNull();
menu.Url.ShouldBe(newUrl);
menu.PageId.ShouldBeNull();
}
[Fact]
public async Task DeleteAsync_ShouldWorkProperly_WithExistingId()
{

14
modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Menus/MenuManager_Test.cs

@ -35,6 +35,20 @@ public class MenuManager_Test : CmsKitDomainTestBase
menuItem.Url.ShouldBe(page.Slug.EnsureStartsWith('/'));
}
[Fact]
public async Task SetPageUrl_ShouldSetUrlSameWithPage_WithStringUrl()
{
var newUrl = "/my-new-url";
var menuItem = await menuItemRepository.GetAsync(testData.MenuItem_4_With_Page_1_Id);
menuManager.SetPageUrl(menuItem, newUrl);
menuItem.Url.ShouldNotBeNullOrEmpty();
menuItem.Url.ShouldBe(newUrl);
menuItem.PageId.ShouldBeNull();
}
[Fact]
public async Task MoveAsync_ShouldMoveCorrectly_UnderAnotherMenu()
{

17
modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs

@ -1,8 +1,9 @@
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.BlobStoring;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
@ -194,7 +195,8 @@ public class CmsKitDataSeedContributor : IDataSeedContributor, ITransientDepende
"comment",
null,
_cmsKitTestData.User1Id
){ IdempotencyToken = _cmsKitTestData.IdempotencyToken_1 });
)
{ IdempotencyToken = _cmsKitTestData.IdempotencyToken_1 });
await _commentRepository.InsertAsync(new Comment(_guidGenerator.Create(),
_cmsKitTestData.EntityType1,
@ -416,6 +418,13 @@ public class CmsKitDataSeedContributor : IDataSeedContributor, ITransientDepende
private async Task SeedMenusAsync()
{
var menuItem4 = new MenuItem(
_cmsKitTestData.MenuItem_4_With_Page_1_Id,
_cmsKitTestData.MenuItem_4_With_Page_1_Name,
_cmsKitTestData.Page_1_Slug.EnsureStartsWith('/'));
menuItem4.SetPageId(_cmsKitTestData.Page_1_Id);
await _menuItemRepository.InsertManyAsync(new[]
{
new MenuItem(
@ -429,8 +438,8 @@ public class CmsKitDataSeedContributor : IDataSeedContributor, ITransientDepende
new MenuItem(
_cmsKitTestData.MenuItem_3_Id,
_cmsKitTestData.MenuItem_3_Name,
_cmsKitTestData.MenuItem_3_Url)
_cmsKitTestData.MenuItem_3_Url),
menuItem4
});
}
}

4
modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs

@ -128,6 +128,10 @@ public class CmsKitTestData : ISingletonDependency
public string MenuItem_3_Url { get; } = "/products";
public Guid MenuItem_4_With_Page_1_Id { get; } = Guid.NewGuid();
public string MenuItem_4_With_Page_1_Name { get; } = "Products";
public string PollName { get; } = "Poll";
public string WidgetName { get; } = "CmsPollByCode";

19
modules/cms-kit/test/Volo.CmsKit.TestBase/Pages/PageRepository_Test.cs

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Modularity;
@ -99,4 +100,20 @@ public abstract class PageRepository_Test<TStartupModule> : CmsKitTestBase<TStar
pages.ShouldBeEmpty();
}
[Fact]
public async Task ShouldFindTitleAsync()
{
var result = await _pageRepository.FindTitleAsync(_cmsKitTestData.Page_1_Id);
result.ShouldNotBeNull();
result.ShouldBe(_cmsKitTestData.Page_1_Title);
}
[Fact]
public async Task ShouldReturnNullFindTitleAsync()
{
var nonExistingId = Guid.NewGuid();
var result = await _pageRepository.FindTitleAsync(nonExistingId);
result.ShouldBeNull();
}
}

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/cs.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funkce",
"NoFeatureFoundMessage": "Nejsou zde dostupné žádné funkce.",
"ManageHostFeatures": "Správa funkcí hostitele",
"ManageHostFeatures": "Správa Funkcí Hostitele",
"ManageHostFeaturesText": "Funkce na straně hostitele můžete spravovat kliknutím na následující tlačítko.",
"Permission:FeatureManagement": "Správa Funkcí",
"Permission:FeatureManagement.ManageHostFeatures": "Správa funkcí hostitele",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de-DE.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Features",
"NoFeatureFoundMessage": "Es ist kein Feature verfügbar.",
"ManageHostFeatures": "Host-Features verwalten",
"ManageHostFeatures": "Host-Features Verwalten",
"Permission:FeatureManagement": "Feature-Verwaltung",
"Permission:FeatureManagement.ManageHostFeatures": "Host-Features verwalten",
"Volo.Abp.FeatureManagement:InvalidFeatureValue": "{0} Feature-Wert ist ungültig!",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funktionen",
"NoFeatureFoundMessage": "Es ist keine Funktion verfügbar.",
"ManageHostFeatures": "Hostfunktionen verwalten",
"ManageHostFeatures": "Hostfunktionen Verwalten",
"ManageHostFeaturesText": "Sie können die hostseitigen Funktionen verwalten, indem Sie auf die folgende Schaltfläche klicken.",
"Permission:FeatureManagement": "Funktionsverwaltung",
"Permission:FeatureManagement.ManageHostFeatures": "Hostfunktionen verwalten",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en-GB.json

@ -3,8 +3,8 @@
"texts": {
"Features": "Features",
"NoFeatureFoundMessage": "There isn't any available feature.",
"ManageHostFeatures": "Manage Host features",
"Permission:FeatureManagement": "Feature Management",
"ManageHostFeatures": "Manage Host Features",
"Permission:FeatureManagement.ManageHostFeatures": "Manage Host features",
"Volo.Abp.FeatureManagement:InvalidFeatureValue": "{0} feature value is not valid!",
"Menu:FeatureManagement": "Feature Management",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/es.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Características",
"NoFeatureFoundMessage": "No hay ninguna característica disponible.",
"ManageHostFeatures": "Administrar funciones de host",
"ManageHostFeatures": "Administrar Funciones de Host",
"ManageHostFeaturesText": "Puede administrar las funciones del lado del host haciendo clic en el siguiente botón.",
"Permission:FeatureManagement": "Gestión de Características",
"Permission:FeatureManagement.ManageHostFeatures": "Gestionar características del Host",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/fi.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Ominaisuudet",
"NoFeatureFoundMessage": "Käytettävissä olevia ominaisuuksia ei löydy.",
"ManageHostFeatures": "Hallinnoi isäntäominaisuuksia",
"ManageHostFeatures": "Hallinnoi Isäntä Ominaisuuksia",
"ManageHostFeaturesText": "Voit hallita isäntäpuolen ominaisuuksia napsauttamalla seuraavaa painiketta.",
"Permission:FeatureManagement": "Ominaisuuden Hallinta",
"Permission:FeatureManagement.ManageHostFeatures": "Hallinnoi isäntäominaisuuksia",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/fr.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Fonctionnalités",
"NoFeatureFoundMessage": "Il n'y a aucune fonctionnalité disponible.",
"ManageHostFeatures": "Gérer les fonctionnalités de l’hôte",
"ManageHostFeatures": "Gérer les Fonctionnalités de l’hôte",
"ManageHostFeaturesText": "Vous pouvez gérer les fonctionnalités côté hôte en cliquant sur le bouton suivant.",
"Permission:FeatureManagement": "Gestion des Fonctionnalités",
"Permission:FeatureManagement.ManageHostFeatures": "Gérer les fonctionnalités de l'hôte",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/hr.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Značajke",
"NoFeatureFoundMessage": "Ne postoji nijedna dostupna značajka.",
"ManageHostFeatures": "Upravljanje značajkama domaćina",
"ManageHostFeatures": "Upravljanje Značajkama Domaćina",
"ManageHostFeaturesText": "Možete upravljati značajkama strane domaćina klikom na sljedeći gumb.",
"Permission:FeatureManagement": "Upravljanje Značajkama",
"Permission:FeatureManagement.ManageHostFeatures": "Upravljanje značajkama domaćina",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/hu.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funkciók",
"NoFeatureFoundMessage": "Nincs elérhető funkció.",
"ManageHostFeatures": "Host funkciók kezelése",
"ManageHostFeatures": "Host Funkciók Kezelése",
"ManageHostFeaturesText": "A gazdaoldali funkciókat a következő gombra kattintva kezelheti.",
"Permission:FeatureManagement": "Funkciókezelés",
"Permission:FeatureManagement.ManageHostFeatures": "Host funkciók kezelése",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/it.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funzionalità",
"NoFeatureFoundMessage": "Non ci sono funzioni disponibili.",
"ManageHostFeatures": "Gestisci le funzionalità dell'host",
"ManageHostFeatures": "Gestisci le Funzionalità Dell'host",
"ManageHostFeaturesText": "È possibile gestire le funzionalità lato host facendo clic sul pulsante seguente.",
"Permission:FeatureManagement": "Gestione Funzionalità",
"Permission:FeatureManagement.ManageHostFeatures": "Gestisci le funzionalità dell'host",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/nl.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Functies",
"NoFeatureFoundMessage": "Er is geen functie beschikbaar.",
"ManageHostFeatures": "Beheer host functies",
"ManageHostFeatures": "Beheer Host Functies",
"ManageHostFeaturesText": "U kunt de functies aan de hostzijde beheren door op de volgende knop te klikken.",
"Permission:FeatureManagement": "Functiebeheer",
"Permission:FeatureManagement.ManageHostFeatures": "Beheer host functies",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funkcje",
"NoFeatureFoundMessage": "Brak dostępnej funkcji.",
"ManageHostFeatures": "Zarządzaj funkcjami hosta",
"ManageHostFeatures": "Zarządzaj Funkcjami Hosta",
"ManageHostFeaturesText": "Możesz zarządzać funkcjami po stronie hosta, klikając poniższy przycisk.",
"Permission:FeatureManagement": "Zarządzanie Funkcjami",
"Permission:FeatureManagement.ManageHostFeatures": "Zarządzaj funkcjami hosta",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pt-BR.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funcionalidades",
"NoFeatureFoundMessage": "Não há nenhuma funcionalidade disponivel.",
"ManageHostFeatures": "Gerenciar recursos do Host",
"ManageHostFeatures": "Gerenciar Recursos do Host",
"ManageHostFeaturesText": "Você pode gerenciar os recursos do host clicando no botão a seguir.",
"Permission:FeatureManagement": "Gerenciamento de Recursos",
"Permission:FeatureManagement.ManageHostFeatures": "Gerenciar recursos do Host",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ro-RO.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Caracteristici",
"NoFeatureFoundMessage": "Nu există nicio caracteristică disponibilă.",
"ManageHostFeatures": "Administraţi caracteristicile de host",
"ManageHostFeatures": "Administraţi Caracteristicile de Host",
"ManageHostFeaturesText": "Puteți gestiona funcțiile din partea gazdă făcând clic pe următorul buton.",
"Permission:FeatureManagement": "Administrare Caracteristici",
"Permission:FeatureManagement.ManageHostFeatures": "Gestionare caracteristici gazdă",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/sk.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funkcie",
"NoFeatureFoundMessage": "Nie je k dispozícii žiadna funkcia.",
"ManageHostFeatures": "Spravovať funkcie poskytovateľa",
"ManageHostFeatures": "Spravovať Funkcie Poskytovateľa",
"ManageHostFeaturesText": "Funkcie na strane hostiteľa môžete spravovať kliknutím na nasledujúce tlačidlo.",
"Permission:FeatureManagement": "Správa Funkcií",
"Permission:FeatureManagement.ManageHostFeatures": "Správa funkcií poskytovateľa",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/sl.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Funkcionalnosti",
"NoFeatureFoundMessage": "Na voljo ni nobene funkcionalnosti.",
"ManageHostFeatures": "Upravljajte funkcije gostitelja",
"ManageHostFeatures": "Upravljajte Funkcije Gostitelja",
"ManageHostFeaturesText": "Funkcije gostiteljske strani lahko upravljate s klikom na naslednji gumb.",
"Permission:FeatureManagement": "Upravljanje Funkcij",
"Permission:FeatureManagement.ManageHostFeatures": "Upravljajte funkcije gostitelja",

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json

@ -3,7 +3,7 @@
"texts": {
"Features": "Özellikler",
"NoFeatureFoundMessage": "Hiç özellik yok.",
"ManageHostFeatures": "Host özelliklerini yönetin",
"ManageHostFeatures": "Host Özelliklerini Yönetin",
"ManageHostFeaturesText": "Aşağıdaki butona tıklayarak host özelliklerini yönetebilirsiniz.",
"Permission:FeatureManagement": "Özellik Yönetimi",
"Permission:FeatureManagement.ManageHostFeatures": "Host özelliklerini düzenle",

6
npm/ng-packs/packages/core/src/lib/services/rest.service.ts

@ -20,7 +20,7 @@ export class RestService {
protected externalHttp: ExternalHttpClient,
protected environment: EnvironmentService,
protected httpErrorReporter: HttpErrorReporterService,
) {}
) { }
protected getApiFromStore(apiName: string | undefined): string {
return this.environment.getApiUrl(apiName);
@ -28,7 +28,7 @@ export class RestService {
handleError(err: any): Observable<any> {
this.httpErrorReporter.reportError(err);
return throwError(err);
return throwError(() => err);
}
request<T, R>(
@ -51,7 +51,7 @@ export class RestService {
}),
...options,
} as any)
.pipe(catchError(err => (skipHandleError ? throwError(err) : this.handleError(err))));
.pipe(catchError(err => (skipHandleError ? throwError(() => err) : this.handleError(err))));
}
private getHttpClient(isExternal: boolean) {
return isExternal ? this.externalHttp : this.http;

21
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -1,7 +1,7 @@
import { inject, Injectable, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, filter, switchMap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
import { HttpErrorReporterService } from '@abp/ng.core';
@ -22,9 +22,7 @@ export class ErrorHandler {
protected readonly routerErrorHandlerService = inject(RouterErrorHandlerService);
protected readonly httpErrorConfig = inject(HTTP_ERROR_CONFIG);
protected readonly customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS);
protected readonly defaultHttpErrorHandler = (_, err: HttpErrorResponse) => throwError(() => err);
protected readonly httpErrorHandler =
inject(HTTP_ERROR_HANDLER, { optional: true }) || this.defaultHttpErrorHandler;
protected readonly httpErrorHandler = inject(HTTP_ERROR_HANDLER, { optional: true });
constructor(protected injector: Injector) {
this.listenToRestError();
@ -42,10 +40,11 @@ export class ErrorHandler {
}
protected executeErrorHandler = (error: HttpErrorResponse) => {
const errHandler = this.httpErrorHandler(this.injector, error);
const isObservable = errHandler instanceof Observable;
if (this.httpErrorHandler) {
return this.httpErrorHandler(this.injector, error);
}
return (isObservable ? errHandler : of(null)).pipe(catchError(err => of(err)));
return of(error);
};
protected sortHttpErrorHandlers(
@ -57,12 +56,12 @@ export class ErrorHandler {
protected handleError(err: unknown) {
if (this.customErrorHandlers && this.customErrorHandlers.length) {
const canHandleService = this.customErrorHandlers
const errorHandlerService = this.customErrorHandlers
.sort(this.sortHttpErrorHandlers)
.find(service => service.canHandle(err));
if (canHandleService) {
canHandleService.execute();
if (errorHandlerService) {
errorHandlerService.execute();
return;
}
}

4
npm/ng-packs/packages/theme-shared/src/lib/models/common.ts

@ -1,5 +1,5 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Type } from '@angular/core';
import { Injector, Type } from '@angular/core';
import { Validation } from '@ngx-validate/core';
import { Observable } from 'rxjs';
import { ConfirmationIcons } from '../tokens/confirmation-icons.token';
@ -20,7 +20,7 @@ export interface HttpErrorConfig {
hideCloseIcon?: boolean;
};
}
export type HttpErrorHandler<T = any> = (httpError: HttpErrorResponse) => Observable<T>;
export type HttpErrorHandler<T = any> = (injector: Injector, httpError: HttpErrorResponse) => Observable<T>;
export type LocaleDirection = 'ltr' | 'rtl';
export interface CustomHttpErrorHandlerService {

10
npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts

@ -5,25 +5,25 @@ import { AbpFormatErrorHandlerService } from '../services/abp-format-error-handl
import { StatusCodeErrorHandlerService } from '../services/status-code-error-handler.service';
import { UnknownStatusCodeErrorHandlerService } from '../services/unknown-status-code-error-handler.service';
export const ERROR_HANDLERS_PROVIDERS: Provider[] = [
export const DEFAULT_HANDLERS_PROVIDERS: Provider[] = [
{
provide: CUSTOM_ERROR_HANDLERS,
multi: true,
useClass: TenantResolveErrorHandlerService,
useExisting: TenantResolveErrorHandlerService,
},
{
provide: CUSTOM_ERROR_HANDLERS,
multi: true,
useClass: AbpFormatErrorHandlerService,
useExisting: AbpFormatErrorHandlerService,
},
{
provide: CUSTOM_ERROR_HANDLERS,
multi: true,
useClass: StatusCodeErrorHandlerService,
useExisting: StatusCodeErrorHandlerService,
},
{
provide: CUSTOM_ERROR_HANDLERS,
multi: true,
useClass: UnknownStatusCodeErrorHandlerService,
useExisting: UnknownStatusCodeErrorHandlerService,
},
];

4
npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts

@ -29,7 +29,7 @@ import { NgxDatatableListDirective } from './directives/ngx-datatable-list.direc
import { DocumentDirHandlerService } from './handlers/document-dir.handler';
import { ErrorHandler } from './handlers/error.handler';
import { RootParams } from './models/common';
import { ERROR_HANDLERS_PROVIDERS, NG_BOOTSTRAP_CONFIG_PROVIDERS } from './providers';
import { DEFAULT_HANDLERS_PROVIDERS, NG_BOOTSTRAP_CONFIG_PROVIDERS } from './providers';
import { THEME_SHARED_ROUTE_PROVIDERS } from './providers/route.provider';
import { THEME_SHARED_APPEND_CONTENT } from './tokens/append-content.token';
import { HTTP_ERROR_CONFIG, httpErrorConfigFactory } from './tokens/http-error.token';
@ -149,7 +149,7 @@ export class ThemeSharedModule {
},
},
tenantNotFoundProvider,
ERROR_HANDLERS_PROVIDERS,
DEFAULT_HANDLERS_PROVIDERS,
],
};
}

3
npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts

@ -15,6 +15,9 @@ export function httpErrorConfigFactory(config = {} as HttpErrorConfig) {
export const HTTP_ERROR_CONFIG = new InjectionToken<HttpErrorConfig>('HTTP_ERROR_CONFIG');
/**
@deprecated use **`CUSTOM_ERROR_HANDLERS`** injection token instead of this, see more info https://docs.abp.io/en/abp/latest/UI/Angular/HTTP-Requests
*/
export const HTTP_ERROR_HANDLER = new InjectionToken<HttpErrorHandler>('HTTP_ERROR_HANDLER');
export const CUSTOM_ERROR_HANDLERS = new InjectionToken<CustomHttpErrorHandlerService[]>(

7
templates/app/aspnet-core/README.md

@ -64,12 +64,9 @@ abp install-libs
> This command installs all NPM packages for MVC/Razor Pages and Blazor Server UIs and this command is already run by the ABP CLI, so most of the time you don't need to run this command manually.
### Deploying the Application
#### Create the Database
Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. However, there are some topics that you should care about when you are deploying your applications. You can check ABP's [Deployment documentation](https://docs.abp.io/en/abp/latest/Deployment/Index) before deploying your application.
* Run `abp install-libs` command on your solution folder to install client-side package dependencies. This step is automatically done when you create a new solution with ABP CLI. However, you should run it yourself if you have first cloned this solution from your source control, or added a new client-side package dependency to your solution.
* Run `MyCompanyName.MyProjectName.DbMigrator` to create the initial database. This should be done in the first run. It is also needed if a new database migration is added to the solution later.
Run `MyCompanyName.MyProjectName.DbMigrator` to create the initial database. This should be done in the first run. It is also needed if a new database migration is added to the solution later.
### Solution structure

Loading…
Cancel
Save