Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into feat/app-provider

pull/4423/head
mehmet-erim 6 years ago
parent
commit
18141ac2e2
  1. 156
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
  2. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json
  3. 15
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
  4. 156
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json
  5. 1
      docs/en/CLI.md
  6. 133
      docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md
  7. 118
      docs/en/Distributed-Event-Bus.md
  8. 3
      docs/en/RabbitMq.md
  9. 8
      docs/en/UI/Angular/Permission-Management.md
  10. 2
      docs/zh-Hans/Blob-Storing.md
  11. 134
      docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md
  12. 300
      docs/zh-Hans/Distributed-Event-Bus.md
  13. 11
      docs/zh-Hans/Event-Bus.md
  14. 227
      docs/zh-Hans/Local-Event-Bus.md
  15. 8
      docs/zh-Hans/UI/Angular/Permission-Management.md
  16. 13
      docs/zh-Hans/docs-nav.json
  17. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs
  18. 12
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
  19. 47
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  20. 49
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs
  21. 10
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  22. 45
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs
  23. 143
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs
  24. 5
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorList.cs
  25. 39
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs
  26. 4
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IAutoEntityDistributedEventSelectorList.cs
  27. 133
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs
  28. 7
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/tr.json
  29. 14
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json
  30. 139
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Entity_Tests.cs
  31. 22
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions_Tests.cs
  32. 6
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json
  33. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/EntityChangeEvents_Tests.cs
  34. 1
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
  35. 5
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/tr.json
  36. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/tr.json
  37. 3
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/tr.json
  38. 6
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/tr.json
  39. 39
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/EntityChangeEvents_Tests.cs
  40. 10
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json
  41. 4
      modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/Models/UserLoginInfo.cs
  42. 7
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json
  43. 3
      modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/tr.json
  44. 16
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/tr.json
  45. 17
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/tr.json
  46. 155
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  47. 6
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js
  48. 89
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css
  49. 2
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.min.css
  50. 131
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss
  51. 14
      modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs
  52. 12
      modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/tr.json
  53. 10
      modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hant.json
  54. 14
      npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts
  55. 7
      npm/ng-packs/packages/core/src/lib/core.module.ts
  56. 1
      npm/ng-packs/packages/core/src/lib/handlers/index.ts
  57. 45
      npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts
  58. 9
      npm/ng-packs/packages/core/src/lib/models/common.ts
  59. 67
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  60. 15
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  61. 16
      npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts
  62. 24
      npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts
  63. 40
      npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts
  64. 86
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts
  65. 17
      npm/ng-packs/packages/core/src/lib/utils/route-utils.ts
  66. 1
      npm/ng-packs/packages/identity/config/src/enums/index.ts
  67. 5
      npm/ng-packs/packages/identity/config/src/enums/policy-names.ts
  68. 6
      npm/ng-packs/packages/identity/config/src/providers/route.provider.ts
  69. 1
      npm/ng-packs/packages/setting-management/config/src/enums/index.ts
  70. 3
      npm/ng-packs/packages/setting-management/config/src/enums/policy-names.ts
  71. 5
      npm/ng-packs/packages/setting-management/config/src/providers/route.provider.ts
  72. 1
      npm/ng-packs/packages/tenant-management/config/src/enums/index.ts
  73. 4
      npm/ng-packs/packages/tenant-management/config/src/enums/policy-names.ts
  74. 4
      npm/ng-packs/packages/tenant-management/config/src/providers/route.provider.ts
  75. 2
      npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html
  76. 25
      npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts
  77. 7
      npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts
  78. 2
      npm/ng-packs/packages/theme-shared/src/lib/providers/route.provider.ts
  79. 13
      npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts
  80. 10
      samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/tr.json
  81. 16
      samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/tr.json

156
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json

@ -1,5 +1,161 @@
{
"culture": "tr",
"texts": {
"Permission:Organizations": "Organizasyonlar",
"Permission:Manage": "Organizasyonları Yönet",
"Permission:DiscountRequests": "İndirim Talepleri",
"Permission:DiscountManage": "İndirim Taleplerini Yönet",
"Permission:Disable": "Devre Dışı Bırak",
"Permission:Enable": "Etkinleşir",
"Permission:EnableSendEmail": "E-Posta Göndermeyi Etkinleştir",
"Permission:SendEmail": "E-Posta Gönder",
"Permission:NpmPackages": "NPM Paketleri",
"Permission:NugetPackages": "Nuget Paketleri",
"Permission:Maintenance": "Bakım",
"Permission:Maintain": "Bakım Yap",
"Permission:ClearCaches": "Önbelleği temizle",
"Permission:Modules": "Modüller",
"Permission:Packages": "Paketler",
"Permission:Edit": "Güncelle",
"Permission:Delete": "Sil",
"Permission:Create": "Oluştur",
"Permission:Accounting": "Muhasebe",
"Permission:Accounting:Quotation": "Fiyatlandırma",
"Permission:Accounting:Invoice": "Fatura",
"Menu:Organizations": "Organizasyonlar",
"Menu:Accounting": "Muhasebe",
"Menu:Packages": "Paketler",
"Menu:DiscountRequests": "İndirim Talepleri",
"NpmPackageDeletionWarningMessage": "Bu NPM Paketi silinecektir. Onaylıyor musunuz?",
"NugetPackageDeletionWarningMessage": "Bu Nuget Paketi silinecektir. Onaylıyor musunuz?",
"ModuleDeletionWarningMessage": "Bu Modül silinecektir. Onaylıyor musunuz?",
"Name": "İsim",
"DisplayName": "Görüntülenen isim",
"ShortDescription": "Kısa açıklama",
"NameFilter": "İsim",
"CreationTime": "Oluşturma zamanı",
"IsPro": "Is pro",
"ShowOnModuleList": "Modül listesinde göster",
"EfCoreConfigureMethodName": "Metot adını yapılandır",
"IsProFilter": "Is pro",
"ApplicationType": "Uygulama tipi",
"Target": "Hedef",
"TargetFilter": "Hedef",
"ModuleClass": "Modül sınıfı",
"NugetPackageTarget.DomainShared": "Domain Shared",
"NugetPackageTarget.Domain": "Domain",
"NugetPackageTarget.Application": "Application",
"NugetPackageTarget.ApplicationContracts": "Application Contracts",
"NugetPackageTarget.HttpApi": "Http Api",
"NugetPackageTarget.HttpApiClient": "Http Api Client",
"NugetPackageTarget.Web": "Web",
"NugetPackageTarget.EntityFrameworkCore": "DeleteAllEntityFramework Core",
"NugetPackageTarget.MongoDB": "MongoDB",
"Edit": "Güncelle",
"Delete": "Sil",
"Refresh": "Yenile",
"NpmPackages": "NPM Paketleri",
"NugetPackages": "Nuget Paketleri",
"NpmPackageCount": "NPM Paket Sayısı",
"NugetPackageCount": "Nuget Paket Sayısı",
"Module": "Modüller",
"ModuleInfo": "Modül bilgisi",
"CreateANpmPackage": "Bir NPM paketi oluştur",
"CreateAModule": "Bir modül oluştur",
"CreateANugetPackage": "Bir Nuget paketi oluştur",
"AddNew": "Yenisini Ekle",
"PackageAlreadyExist{0}": "\"{0}\"isimli paket zaten eklendi.",
"ModuleAlreadyExist{0}": "\"{0}\" isimli modül zaten eklendi.",
"ClearCache": "Önbelleği Temizle",
"SuccessfullyCleared": "Başarıyla temizlendi",
"Menu:NpmPackages": "NPM Paketleri",
"Menu:Modules": "Modüller",
"Menu:Maintenance": "Bakım",
"Menu:NugetPackages": "Nuget Paketleri",
"CreateAnOrganization": "Bir organizasyon oluştur",
"Organizations": "Organizasyonlar",
"LongName": "Uzun isim",
"LicenseType": "Lisans tipi",
"MissingLicenseTypeField": "Lisans tipi alanı zorunludur.",
"LicenseStartTime": "Lisans başlama zamanı",
"LicenseEndTime": "Lisans bitiş zamanı",
"AllowedDeveloperCount": "İzin verilen developer sayısı",
"UserNameOrEmailAddress": "Kullanıcı adı veya e-posta adresi",
"AddOwner": "Owner ekle",
"UserName": "Kullanıcı Adı",
"Email": "E-Posta",
"Developers": "Developers",
"AddDeveloper": "Developer Ekle",
"Create": "Oluştur",
"UserNotFound": "Kullanıcı bulunamadı",
"{0}WillBeRemovedFromDevelopers": "{0} kullanıcı adlı developer silinecektir, Onaylıyor musunuz?",
"{0}WillBeRemovedFromOwners": "{0} kullanıcı adlı owner silinecektir, Onaylıyor musunuz?",
"Computers": "Bilgisayarlar",
"UniqueComputerId": "Özgün bilgisayar id",
"LastSeenDate": "Son görülme tarihi",
"{0}Computer{1}WillBeRemovedFromRecords": "{0} kullanıcı isimli kullanıcının bilgisayarı ({1}) kayıtlardan kaldırılacaktır",
"OrganizationDeletionWarningMessage": "Organizasyon silinecektir.",
"DeletingLastOwnerWarningMessage": "Bir organizasyon en az bir ownera sahip olmalıdır! Bu nedenle bu ownerı kaldıramazsınız",
"This{0}AlreadyExistInThisOrganization": "{0} zaten bu organizasyonda bulunmaktadır",
"AreYouSureYouWantToDeleteAllComputers": "Tüm bilgisayaları silmek istediğinize emin misiniz?",
"DeleteAll": "Tümünü sil",
"DoYouWantToCreateNewUser": "Yeni kullanıcı oluşturmak istiyor musunuz?",
"MasterModules": "Master Modüller",
"OrganizationName": "Organizasyon adı",
"OrganizationNamePlaceholder": "Organizasyon adı...",
"UsernameOrEmail": "Kullanıcı adı veya e-posta",
"UsernameOrEmailPlaceholder": "Kullanıcı adı veya e-posta",
"Member": "Üye",
"PurchaseOrderNo": "Satın alma sipariş no",
"QuotationDate": "Fiyatlandırma tarihi",
"CompanyName": "Şirket adı",
"CompanyAddress": "Şirket adresi",
"Price": "Fiyat",
"DiscountText": "İndirim metni",
"DiscountQuantity": "İndirim miktarı",
"DiscountPrice": "İndirim fiyatı",
"Quotation": "Fiyatlandırma",
"ExtraText": "Eksta metin",
"ExtraAmount": "Eksta miktar",
"DownloadQuotation": "Fiyatlandırmayı İndir",
"Invoice": "Fatura",
"TaxNumber": "Vergi Numarası",
"InvoiceNumber": "Fatura Numarası",
"InvoiceDate": "Fatura Tarihi",
"InvoiceNote": "Fatura Notu",
"Quantity": "Miktar",
"AddProduct": "Ürün Ekle",
"AddProductWarning": "Ürün eklemelisiniz!",
"TotalPrice": "Toplam Fiyat",
"Generate": "Üret",
"MissingQuantityField": "Miktar alanı zorunludur!",
"MissingPriceField": "Fiyat alanı zorunludur!",
"CodeUsageStatus": "Statü",
"Country": "Ülke",
"DeveloperCount": "Developer Sayısı",
"RequestCode": "Talep Kodu",
"WebSite": "Web Sitesi",
"GithubUsername": "Github Kullanıcı adı",
"PhoneNumber": "Telefon Numarası",
"ProjectDescription": "Proje Açıklaması",
"Referrer": "Yönlendiren",
"DiscountRequests": "İndirim Talebi",
"Copylink": "Kopyalama Linki",
"Disable": "Devre Dışı Bırak",
"Enable": "Etkinleştir",
"EnableSendEmail": "E-Posta Göndermeyi Etkinleştir",
"SendEmail": "E-Posta Gönder",
"SuccessfullyDisabled": "Başarıyla Devre Dışı Bırakıldı",
"SuccessfullyEnabled": "Başarıyla Etkinleştirildi",
"EmailSent": "E-Posta Gönderildi",
"SuccessfullySent": "Başarıyla Gönderildi",
"SuccessfullyDeleted": "Başarıyla Silindi",
"DiscountRequestDeletionWarningMessage": "İndirim talebi silinecektir",
"BusinessType": "İş tipi",
"TotalQuestionCount": "Toplam soru sayısı",
"RemainingQuestionCount": "Kalan soru sayısı",
"TotalQuestionMustBeGreaterWarningMessage": "Toplam soru sayısı kalan soru sayısından büyük olmalıdır!",
"QuestionCountsMustBeGreaterThanZero": "Toplam soru sayısı ve kalan soru sayısı sıfır veya sıfırdan daha büyük olmalıdır!",
"UnlimitedQuestionCount": "Sınırsız soru sayısı"
}
}

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

@ -1,4 +1,4 @@
{
{
"culture": "tr",
"texts": {
"Volo.AbpIo.Domain:010004": "Maksimum üye sayısı aşıldı!",
@ -18,12 +18,14 @@
"ReadyToGetStarted?": "Başlamaya hazır mısın?",
"JoinOurCommunity": "Topluluğumuza katılın",
"GetStartedUpper": "BAŞLAYIN",
"ForkMeOnGitHub": "Fork me on GitHub",
"Features": "Özellikler",
"GetStarted": "Başlayın",
"Documents": "Dokümanlar",
"Community": "Topluluk",
"ContributionGuide": "Katkı Rehberi",
"Blog": "Blog",
"Commercial": "Ticari",
"SeeDocuments": "Dokümanlara Göz Atın"
}
}
}

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

@ -1,4 +1,4 @@
{
{
"culture": "tr",
"texts": {
"OrganizationManagement": "Organizasyon yönetimi",
@ -6,6 +6,8 @@
"Volo.AbpIo.Commercial:010003": "Bu organizasyonda yetkili değilsiniz!",
"OrganizationNotFoundMessage": "Organizasyon bulunamadı!",
"DeveloperCount": "Yazılımcı sayısı",
"QuestionCount": "Kalan / toplam sorular",
"Unlimited": "Sınırsız",
"Owners": "Yetkili sayısı",
"AddMember": "Üye ekle",
"AddOwner": "Yetkili ekle",
@ -19,12 +21,15 @@
"StartDate": "Başlangıç tarihi",
"EndDate": "bitiş tarihi",
"Modules": "Modüller",
"Volo.AbpIo.Commercial:010004": "Kullanıcı bulunamadı! İlgili kullanıcının daha önceden sisteme kayıt olmuş olması gerekiyor.",
"LicenseExtendMessage": "Lisans bitiş tarihiniz {0} tarihine kadar uzatıldı",
"LicenseUpgradeMessage": "Lisansınız {0} lisansa yükseltildi",
"LicenseAddDeveloperMessage": "Lisansınıza {0} geliştirici eklendi",
"MyOrganizations": "Organizasyonlarım",
"ApiKey": "API anahtarı",
"UserNameNotFound": "{0} kullanıcı adı ile bir kullanıcı yok"
"Volo.AbpIo.Commercial:010004": "Kullanıcı bulunamadı! İlgili kullanıcının daha önceden sisteme kayıt olmuş olması gerekiyor.",
"MyOrganizations": "Organizasyonlarım",
"ApiKey": "API anahtarı",
"UserNameNotFound": "{0} kullanıcı adı ile bir kullanıcı yok",
"SuccessfullyAddedToNewsletter": "Bültenimize abone olduğunuz için teşekkürler!",
"MyProfile": "Profilim",
"EmailNotValid": "Lütfen uygun bir e-posta adresi giriniz"
}
}

156
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json

@ -1,5 +1,161 @@
{
"culture": "tr",
"texts": {
"GetStarted": "Başlamak - Başlangıç Templateleri",
"Create": "Oluştur",
"NewProject": "Yeni Proje",
"DirectDownload": "Doğrudan İndir",
"ProjectName": "Proje ismi",
"ProjectType": "Proje tipi",
"DatabaseProvider": "Veritabanı sağlayacısı",
"NTier": "N-Tier",
"IncludeUserInterface": "Kullanıcı arayüzünü dahil et",
"CreateNow": "Şimdi oluştur",
"TheStartupProject": "Başlangıç projesi",
"Tutorial": "Öğretici",
"UsingCLI": "CLI Kullanmak",
"SeeDetails": "Detayları Görüntüle",
"AbpShortDescription": "ABP modern web uygulamaları geliştirmek için eksiksiz bir mimari ve günlü bir altyapıdır! Size SOLID geliştirme tecrübelerini sunmak için en iyi uygulama ve kuralları takip eder.",
"SourceCodeUpper": "KAYNAK KOD",
"LatestReleaseLogs": "Son release kayıtları",
"Infrastructure": "Altyapı",
"Architecture": "Mimari",
"Modular": "Modüler",
"DontRepeatYourself": "Kendini Tekrar Etme",
"DeveloperFocused": "Developer Odaklı",
"FullStackApplicationInfrastructure": "Full stack uygulama altyapısı",
"DomainDrivenDesign": "Domain Driven Design",
"DomainDrivenDesignExplanation": "DDD paterni ve prensiplerinden yola çıkarak dizayn edildi ve geliştirildi. Uygulamanız için katmanlı bir model sunmaktadır.",
"Authorization": "Yetkilendirme",
"AuthorizationExplanation": "Kullanıcı, rol ve ayrıntılı izin sistemi ile modern yetkilendirme. Microsoft Identity library üzerine kurulmuştur.",
"MultiTenancy": "Multi-Tenancy",
"MultiTenancyExplanationShort": "SaaS uygulamaları kolaylaştı! Veritababından kullanıcı arayüzüne entegre edilmiş multi-tenancy",
"CrossCuttingConcerns": "Cross Cutting Concerns",
"CrossCuttingConcernsExplanationShort": "Yetkilendirme, validasyon, hata yakalama, caching, audit logging, işlem yönetimi ve bunun gibi konular için eksiksiz altyapı.",
"BuiltInBundlingMinification": "Hazır Paketleme & Küçültme",
"BuiltInBundlingMinificationExplanation": "Paketleme ve küçültmek için external araçları kullanmayı bırakın. ABP daha basit, dinamik, güçlü, modüler ve hazır yolları öneriyor.",
"VirtualFileSystem": "Sanal Dosya Sistemi",
"VirtualFileSystemExplanation": "Sayfaları, scriptleri, stilleri, resimleri... paketlere/kütüpanelere gömün ve farklı uygulamalarda yeniden kullanın. ",
"Theming": "Theming",
"ThemingExplanationShort": "Bootstrap tabanlı standart kullanıcı arayüzlerini kullan ve kişiselleştir veya kendin yeni bir tane oluştur.",
"BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dinamik Formlar",
"BootstrapTagHelpersDynamicFormsExplanation": "Bootstrap komponentlerinin tekrar eden detaylarını manuel olarak yazmak yerine, Bu işlemi basitleştirmek ve iyileştirme avantajından faydalanmak için ABP'nin tag helperlarını kullanın. Dinamik form bir C# sınıfından model olarak eksiksik form oluşturabilir.",
"HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies",
"HTTPAPIsDynamicProxiesExplanation": "Application servislerini otomatik olarak Rest stil Http API olarak ayarlayın ve dinamaik Javascript & C# proxyler ile kullanın.",
"CompleteArchitectureInfo": "Bakım yapılabilir yazılım çözümleri üretmek için modern mimari.",
"DomainDrivenDesignBasedLayeringModelExplanation": "DDD tabanlı bir katmanlı mimari geliştirmek ve bakım yapılabilir bir kod altyapısı inşaa etmek için size yardım eder.",
"DomainDrivenDesignBasedLayeringModelExplanationCont": "DDD patern ve prensiplerinden yola çıkarak uygulamanızı geliştirmeye yardımcı olmak için başlanıç templateler, soyutlamalar, base sınıflar, servisler, dokümantasyon ve rehberlik sağlar. ",
"MicroserviceCompatibleModelExplanation": "Core framework & pre-build modüller mikroservis mimari göz önünde bulundurularak dizayn edildi.",
"MicroserviceCompatibleModelExplanationCont": "Microservice çözümlerini daha kolay geliştirmek için altyapı, entegrasyon, örnekler ve dokümantasyon sunarken eğer bir tek parça uygulama istiyorsanız ek karmaşıklık getirmez.",
"ModularInfo": "ABP yeniden kullanılabilir uygulama modülleri geliştirebilmenize izin veren eksiksiz modüler sistem sunar.",
"PreBuiltModulesThemes": "Pre-Built Modüller & Temalar",
"PreBuiltModulesThemesExplanation": "Açık kaynak ve ticari modüller & temalar iş uygulamanızda kullanıma hazırdır.",
"NuGetNPMPackages": "NUGET & NPM Packages",
"NuGetNPMPackagesExplanation": "NUGET & NPM paketleri olarak dağıtılmıştır. Yüklemek ve güncellemek kolaydır.",
"ExtensibleReplaceable": "Genişletilebilir/Değiştirilebilir",
"ExtensibleReplaceableExplanation": "Tüm sevisler & modüller genişletilebilirlik göz önünde bulundurularak dizayn edildi. Servislerin, sayfaların stillerin, komponentlerin vb. yerlerini değiştirebilirsizinz.",
"CrossCuttingConcernsExplanation2": "Kodunu daha temiz tut ve kendi uygulama koduna odaklan.",
"CrossCuttingConcernsExplanation3": "Ortak uygulama isterlerini tekrar ve tekrar geliştirmek için zaman harcamayın.",
"AuthenticationAuthorization": "Kimlik Doğrulama & Yetkilendirme",
"ExceptionHandling": "Hata yakalama",
"Validation": "Validasyon",
"DatabaseConnection": "Veritabanı bağlantısı",
"TransactionManagement": "İşlem yönetimi",
"AuditLogging": "Audit Logging",
"Caching": "Caching",
"Multitenancy": "Multitenancy",
"DataFiltering": "Date filtreleme",
"ConventionOverConfiguration": "Yapılandırma Üzerinde Kurallar",
"ConventionOverConfigurationExplanation": "ABP minimal veya sıfır yapılandırma ile ortak uygulama kurallarını varsayılan olarak uygular.",
"ConventionOverConfigurationExplanationList1": "Dependency injection için bilinen servisler otomatik olarak kaydedilir.",
"ConventionOverConfigurationExplanationList2": "Application servisler isimlerdirme kuralları ile HTTP API ler olarak uygulanır.",
"ConventionOverConfigurationExplanationList3": "C# ve JavaScript için dinamik HTTP istemci proxyleri yaratır.",
"ConventionOverConfigurationExplanationList4": "Entityleriniz için varsayılaan repositoriler sunar.",
"ConventionOverConfigurationExplanationList5": "Her web request veya application servis metodu için Unit of Work işlemini yönetir.",
"ConventionOverConfigurationExplanationList6": "Entityleriniz için oluşturma, güncelleme & silme işlemlerini yayınlar.",
"BaseClasses": "Ana Sınıflar",
"BaseClassesExplanation": "Ortak uygulama paternleri için pre-built ana sınıflar.",
"DeveloperFocusedExplanation": "ABP developerlar içindir.",
"DeveloperFocusedExplanationCont": "İhtiyacınız olduğunda düşük seviyede çalışmanızı kısıtlamadan günlük yazılım geliştirmenizi basitleştirmeyi amaçlar.",
"SeeAllFeatures": "Tüm Özellikleri Görüntüle",
"CLI_CommandLineInterface": "CLI (Command Line Interface)",
"CLI_CommandLineInterfaceExplanation": "CLI yeni proje oluşturma ve uygulamanıza modüller ekleme işlemlerini otomatik hale getirir.",
"StartupTemplates": "Başlangıç Templateler",
"StartupTemplatesExplanation": "Çeşitli başlangıç templateleri size geliştirme başlatmak için tam yapılandırılmış bir çözüm sağlar.",
"BasedOnFamiliarTools": "Bilinen Araçlara Dayalı ",
"BasedOnFamiliarToolsExplanation": "Zaten bildiğiniz popüler araçlar ile geliştirilme ve egtegre edilmiştir. Düşük öğrenme eğrisi, koaly adaptasyon, rahat geliştirme.",
"ORMIndependent": "ORM Bağımsız",
"ORMIndependentExplanation": "",
"Features": "ABP Framework Özelliklerini Keşfet",
"ABPCLI": "ABP CLI",
"Modularity": "Modülerlik",
"BootstrapTagHelpers": "Bootstrap Tag Helpers",
"DynamicForms": "Dinamik Formlar",
"BundlingMinification": "Paketleme & Küçültme",
"BackgroundJobs": "Arkaplan İşleri",
"DDDInfrastructure": "DDD altyapısı",
"DomainDrivenDesignInfrastructure": "Domain Driven Design Altyapısı",
"AutoRESTAPIs": "Otomatik REST APIler",
"DynamicClientProxies": "Dinamik Client Proxies",
"DistributedEventBus": "Dağıtılmış Event Bus",
"DistributedEventBusWithRabbitMQIntegration": "RabbitMQ Entegrasyonu ile Dağıtılmış Event Bus",
"TestInfrastructure": "Test ALtyapısı",
"AuditLoggingEntityHistories": "Audit Logging & Entity Histories",
"ObjectToObjectMapping": "Object to Object Mapping",
"EmailSMSAbstractions": "E-Posta & SMS Soyutlamaları",
"EmailSMSAbstractionsWithTemplatingSupport": "Template Destekli E-Posta & SMS Soyutlamaları",
"Localization": "Localization",
"SettingManagement": "Ayar Yönetimi",
"ExtensionMethods": "Extension Methods",
"ExtensionMethodsHelpers": "Extension Methods & Helpers",
"AspectOrientedProgramming": "Aspect Oriented Programming",
"DependencyInjection": "Dependency Injection",
"DependencyInjectionByConventions": "Dependency Injection by Conventions",
"ABPCLIExplanation": "ABP CLI (Command Line Interface) ABP tabanlı çözümler ortak işlemleri gerçekleştiren bir komut satırı aracıdır.",
"ModularityExplanation": "ABP, entityleri, servisleri, veritabanı entegrasyonu, APIleri, UI komponentleri ve bunun gibi özelliklere sahip olabilecek kendi uygulama modüllerini geliştirmeniz için eksiksiz bir altyapı sağlar. ",
"MultiTenancyExplanation": "ABP framework sadece multi-tenant uygulama geliştirmenizi desteklemekle kalmaz aynı zamanda kodunuzun çoğunlukla tenantların birbirinden haberi olmaycak şekilde olmasını sağlar.",
"MultiTenancyExplanation2": "Anlık tenant'ı otomatik olarak belirleybilir, farklı tenantların verilerini birbirlerinden izole edebilir.",
"MultiTenancyExplanation3": "Tek bir veritabanını, her tenant için ayrı bir veritabanını ve hibrid yaklaşımları destekler.",
"MultiTenancyExplanation4": "Sen kendi uygulama kodunu odaklan ve bırak framework sizin adınıza multi-tenancy üstesinden gelsin.",
"BootstrapTagHelpersExplanation": "Bootstrap komponentlerinin tekrar eden detaylarını manuel olarak yazmak yerine, Bu işlemi basitleştirmek ve iyileştirme avantajından faydalanmak için ABP'nin tag helperlarını kullanın. Dinamik form bir C# sınıfından model olarak eksiksik form oluşturabilir.",
"DynamicFormsExplanation": "Dinamik form & input tag helpers bir C# sınıfından model olarak eksiksik form oluşturabilir.",
"AuthenticationAuthorizationExplanation": "ASP.NET Core Identity & IdentityServer4 ile entegre edilmiş zengin kimlik doğrulama ve yetkilendirme opsiyonları. Genişletilebilir ve detaylandırılabilr bir izin sistemi sunar.",
"CrossCuttingConcernsExplanation": "Tüm bu ortak şeyleri geliştirmek için kendini sürekli tekrar etme. Kendi iş koduna odaklan ve bırak ABP bunları kurallar ile otomatik hale getirsin.",
"DatabaseConnectionTransactionManagement": "Veritabanı Bğlantısı & İşlem Yönetimi",
"CorrelationIdTracking": "Correlation-Id Tracking",
"BundlingMinificationExplanation": "ABP daha basit, dinamik, güçlü, modüler ve hazır paketlenmiş ve küçültülmüş sistemi öneriyor.",
"VirtualFileSystemnExplanation": "Sanal Dosya Sistemi fiziksel olarak disk üzerinde var olmayan dosyalarını yönetmeyi mümkün kılmaktadır. Bunlar genellikle önceden assemblyler içerisinde gömülü olan(js,css,image,cshtml..) dosyalardır ve bunlar fiziksel dosylar gibi runtimeda kullanılır.",
"ThemingExplanation": "Theming sistem son Bootstrap Framework tabanlı ortak bir kütüphane ve layout tanımlayarak uygulamanızı & modüllerini bağımsız olarak geliştirmenizi sağlamaktadır.",
"DomainDrivenDesignInfrastructureExplanation": "Domain Driven Design pattern ve prensiplerine dayalı katmanlı uygulama geliştirmek için eksiksiz bit altyapı",
"Specification": "Specification",
"Repository": "Repository",
"DomainService": "Domain Service",
"ValueObject": "Value Object",
"ApplicationService": "Application Service",
"DataTransferObject": "Data Transfer Object",
"AggregateRootEntity": "Aggregate Root, Entity",
"AutoRESTAPIsExplanation": "ABP, application servislerinizi otomatik olarak API Controller olarak kurallı bir şekilde yapılandırabilir.",
"DynamicClientProxiesExplanation": "Apilerinizi, JavaScript ve C# clients tarafından kolaylıkla kullanın.",
"DistributedEventBusWithRabbitMQIntegrationExplanation": "Easily publish & consume distributed events using built-in Distributed Event Bus with RabbitMQ integration available.",
"TestInfrastructureExplanation": "The framework has been developed unit & integration testing in mind. Provides you base classes to make it easier. Startup templates come with pre-configured for testing.",
"AuditLoggingEntityHistoriesExplanation": "",
"EmailSMSAbstractionsWithTemplatingSupportExplanation": "",
"LocalizationExplanation": "",
"SettingManagementExplanation": "",
"ExtensionMethodsHelpersExplanation": "",
"AspectOrientedProgrammingExplanation": "",
"DependencyInjectionByConventionsExplanation": "",
"DataFilteringExplanation": "",
"PublishEvents": "Publish Events",
"HandleEvents": "Handle Events",
"AndMore": "",
"Code": "Code",
"Result": "Sonuç",
"SeeTheDocumentForMoreInformation": "See the <a href=\"{1}\">{0} document</a> for more information",
"IndexPageHeroSection": "<span class=\"first-line shine\"><strong>open source</strong></span><span class=\"second-line text-uppercase\">Web Application<br />Framework </span><span class=\"third-line shine2\"><strong>for asp.net core</strong></span>",
"UiFramework": "UI Framework",
"EmailAddress": "E-Posta Adresi",
"Mobile": "Mobil",
"ReactNative": "React Native"
}
}

1
docs/en/CLI.md

@ -115,6 +115,7 @@ abp update [options]
* `--nuget`: Only updates NuGet packages.
* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
* `--check-all`: Check the new version of each package separately. Default is `false`.
### add-package

133
docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md

@ -1,3 +1,134 @@
# Distributed Event Bus RabbitMQ Integration
TODO
> This document explains **how to configure the [RabbitMQ](https://www.rabbitmq.com/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system
## Installation
Use the ABP CLI to add [Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.RabbitMQ` package.
* Run `abp add-package Volo.Abp.EventBus.RabbitMQ` command.
If you want to do it manually, install the [Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusRabbitMqModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
You can configure using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes.
### `appsettings.json` file configuration
This is the simplest way to configure the RabbitMQ settings. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/).
**Example: The minimal configuration to connect to a local RabbitMQ server with default configurations**
````json
{
"RabbitMQ": {
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
````
* `ClientName` is the name of this application, which is used as the **queue name** on the RabbitMQ.
* `ExchangeName` is the **exchange name**.
See [the RabbitMQ document](https://www.rabbitmq.com/dotnet-api-guide.html#exchanges-and-queues) to understand these options better.
#### Connections
If you need to connect to another server than the localhost, you need to configure the connection properties.
**Example: Specify the host name (as an IP address)**
````json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
````
Defining multiple connections is allowed. In this case, you can specify the connection that is used for the event bus.
**Example: Declare two connections and use one of them for the event bus**
````json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123"
},
"SecondConnection": {
"HostName": "321.321.321.321"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName",
"ConnectionName": "SecondConnection"
}
}
}
````
This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus.
You can use any of the [ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) properties as the connection properties.
**Example: Specify the connection port**
````csharp
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123",
"Port": "5672"
}
}
}
}
````
### The Options Classes
`AbpRabbitMqOptions` and `AbpRabbitMqEventBusOptions` classes can be used to configure the connection strings and event bus options for the RabbitMQ.
You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md).
**Example: Configure the connection**
````csharp
Configure<AbpRabbitMqOptions>(options =>
{
options.Connections.Default.UserName = "user";
options.Connections.Default.Password = "pass";
options.Connections.Default.HostName = "123.123.123.123";
options.Connections.Default.Port = 5672;
});
````
**Example: Configure the client and exchange names**
````csharp
Configure<AbpRabbitMqEventBusOptions>(options =>
{
options.ClientName = "TestApp1";
options.ExchangeName = "TestMessages";
});
````
Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file.

118
docs/en/Distributed-Event-Bus.md

@ -179,3 +179,121 @@ That's all.
You can inject any service and perform any required logic here. A single event handler class can **subscribe to multiple events** but implementing the `IDistributedEventHandler<TEvent>` interface for each event type.
> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options.
## Pre-Defined Events
ABP Framework **automatically publishes** distributed events for **create, update and delete** operations for an [entity](Entities.md) once you configure it.
### Event Types
There are three pre-defined event types:
* `EntityCreatedEto<T>` is published when an entity of type `T` was created.
* `EntityUpdatedEto<T>` is published when an entity of type `T` was updated.
* `EntityDeletedEto<T>` is published when an entity of type `T` was deleted.
These types are generics. `T` is actually the type of the **E**vent **T**ransfer **O**bject (ETO) rather than the type of the entity. Because, an entity object can not be transferred as a part of the event data. So, it is typical to define a ETO class for an entity class, like `ProductEto` for `Product` entity.
### Subscribing to the Events
Subscribing to the auto events is same as subscribing a regular distributed event.
**Example: Get notified once a product updated**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
namespace AbpDemo
{
public class MyHandler :
IDistributedEventHandler<EntityUpdatedEto<ProductEto>>,
ITransientDependency
{
public async Task HandleEventAsync(EntityUpdatedEto<ProductEto> eventData)
{
var productId = eventData.Entity.Id;
//TODO
}
}
}
````
* `MyHandler` implements the `IDistributedEventHandler<EntityUpdatedEto<ProductEto>>`.
### Configuration
You can configure the `AbpDistributedEntityEventOptions` in the `ConfigureServices` of your [module](Module-Development-Basics.md) to add a selector.
**Example: Configuration samples**
````csharp
Configure<AbpDistributedEntityEventOptions>(options =>
{
//Enable for all entities
options.AutoEventSelectors.AddAll();
//Enable for a single entity
options.AutoEventSelectors.Add<IdentityUser>();
//Enable for all entities in a namespace (and child namespaces)
options.AutoEventSelectors.AddNamespace("Volo.Abp.Identity");
//Custom predicate expression that should return true to select a type
options.AutoEventSelectors.Add(
type => type.Namespace.StartsWith("MyProject.")
);
});
````
* The last one provides flexibility to decide if the events should be published for the given entity type. Returns `true` to accept a `Type`.
You can add more than one selector. If one of the selectors match for an entity type, then it is selected.
### Event Transfer Object
Once you enable **auto events** for an entity, ABP Framework starts to publish events on the changes on this entity. If you don't specify a corresponding **E**vent **T**ransfer **O**bject (ETO) for the entity, ABP Framework uses a standard type, named `EntityEto`, which has only two properties:
* `EntityType` (`string`): Full name (including namespace) of the entity class.
* `KeysAsString` (`string`): Primary key(s) of the changed entity. If it has a single key, this property will be the primary key value. For a composite key, it will contain all keys separated by `,` (comma).
So, you can implement the `IDistributedEventHandler<EntityUpdatedEto<EntityEto>>` to subscribe the events. However, it is not a good approach to subscribe to such a generic event. You can define the corresponding ETO for the entity type.
**Example: Declare to use `ProductEto` for the `Product` entity**
````csharp
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.AutoEventSelectors.Add<Product>();
options.EtoMappings.Add<Product, ProductEto>();
});
````
This example;
* Adds a selector to allow to publish the create, update and delete events for the `Product` entity.
* Configure to use the `ProductEto` as the event transfer object to publish for the `Product` related events.
Distributed event system use the [object to object mapping](Object-To-Object-Mapping.md) system to map `Product` objects to `ProductEto` objects. So, you need to configure the mapping. You can check the object to object mapping document for all options, but the following example shows how to configure it with the [AutoMapper](https://automapper.org/) library.
**Example: Configure `Product` to `ProductEto` mapping using the AutoMapper**
````csharp
using System;
using AutoMapper;
using Volo.Abp.Domain.Entities.Events.Distributed;
namespace AbpDemo
{
[AutoMap(typeof(Product))]
public class ProductEto : EntityEto
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
````
This example uses the `AutoMap` attribute of the AutoMapper to configure the mapping. You could create a profile class instead. Please refer to the AutoMapper document for more options.

3
docs/en/RabbitMq.md

@ -0,0 +1,3 @@
# RabbitMQ
TODO!

8
docs/en/UI/Angular/Permission-Management.md

@ -8,7 +8,7 @@ You can get permission as boolean value from store:
```js
import { Store } from '@ngxs/store';
import { ConfigState } from '../states';
import { ConfigState } from '@abp/ng.core';
export class YourComponent {
constructor(private store: Store) {}
@ -24,7 +24,7 @@ export class YourComponent {
Or you can get it via `ConfigStateService`:
```js
import { ConfigStateService } from '../services/config-state.service';
import { ConfigStateService } from '@abp/ng.core';
export class YourComponent {
constructor(private configStateService: ConfigStateService) {}
@ -42,7 +42,7 @@ export class YourComponent {
You can use the `PermissionDirective` to manage visibility of a DOM Element accordingly to user's permission.
```html
<div *abpPermission="AbpIdentity.Roles">
<div *abpPermission="'AbpIdentity.Roles'">
This content is only visible if the user has 'AbpIdentity.Roles' permission.
</div>
```
@ -58,6 +58,8 @@ You can use `PermissionGuard` if you want to control authenticated user's permis
Add `requiredPolicy` to the `routes` property in your routing module.
```js
import { PermissionGuard } from '@abp/ng.core';
// ...
const routes: Routes = [
{
path: 'path',

2
docs/zh-Hans/Blob-Storing.md

@ -169,7 +169,7 @@ public class ProfileAppService : ApplicationService
如果不使用泛型参数,直接注入 `IBlobContainer` (如上所述),会得到默认容器. 注入默认容器的另一种方法是使用 `IBlobContainer<DefaultContainer>`,它返回完全相同的容器.
默认容器的名称是 `Default`.
默认容器的名称是 `default`.
### 命令容器

134
docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md

@ -0,0 +1,134 @@
# 分布式事件总线RabbitMQ集成
> 本文解释了**如何配置[RabbitMQ](https://www.rabbitmq.com/)**做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
## 安装
使用ABP CLI添加[Volo.Abp.EventBus.RabbitMQ[Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ)NuGet包到你的项目:
* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI),如果你还没有安装.
* 在你想要安装 `Volo.Abp.EventBus.RabbitMQ` 包的 `.csproj` 文件目录打开命令行(终端).
* 运行 `abp add-package Volo.Abp.EventBus.RabbitMQ` 命令.
如果你想要手动安装,安装[Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) NuGet 包到你的项目然后添加 `[DependsOn(typeof(AbpEventBusRabbitMqModule))]` 到你的项目[模块](Module-Development-Basics.md)类.
## 配置
可以使用配置使用标准的[配置系统](Configuration.md),如 `appsettings.json` 文件,或[选项](Options.md)类.
### `appsettings.json` 文件配置
这是配置RabbitMQ设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持的](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量).
**示例:最小化配置与默认配置连接到本地的RabbitMQ服务器**
````json
{
"RabbitMQ": {
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
````
* `ClientName` 是应用程序的名称,用于RabbitMQ的**队列名称**.
* `ExchangeName`**交换机名称**.
参阅[RabbitMQ文档](https://www.rabbitmq.com/dotnet-api-guide.html#exchanges-and-queues)更好的了解这些选项.
#### 连接
如果需要连接到本地主机以外的另一台服务器,需要配置连接属性.
**示例: 指定主机名 (如IP地址)**
````json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
````
允许定义多个连接. 在这种情况下,你可以指定用于事件总线的连接.
**示例: 声明两个连接并将其中一个用于事件总线**
````json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123"
},
"SecondConnection": {
"HostName": "321.321.321.321"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName",
"ConnectionName": "SecondConnection"
}
}
}
````
这允许你可以在你的应用程序使用多个RabbitMQ服务器,但将其中一个做为事件总线.
你可以使用任何[ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties)属性作为连接属性.
**示例: 指定连接端口**
````csharp
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123",
"Port": "5672"
}
}
}
}
````
### 选项类
`AbpRabbitMqOptions``AbpRabbitMqEventBusOptions` 类用于配置RabbitMQ的连接字符串和事件总线选项.
你可以在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法配置选项.
**示例: 配置连接**
````csharp
Configure<AbpRabbitMqOptions>(options =>
{
options.Connections.Default.UserName = "user";
options.Connections.Default.Password = "pass";
options.Connections.Default.HostName = "123.123.123.123";
options.Connections.Default.Port = 5672;
});
````
**示例: 配置客户端和交换机名称**
````csharp
Configure<AbpRabbitMqEventBusOptions>(options =>
{
options.ClientName = "TestApp1";
options.ExchangeName = "TestMessages";
});
````
使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.

300
docs/zh-Hans/Distributed-Event-Bus.md

@ -0,0 +1,300 @@
# 分布式事件总线
分布式事件总线系统允许**发布**和**订阅跨应用/服务边界**传输的事件. 你可以使用分布式事件总线在**微服务**或**应用程序**之间异步发送和接收消息.
## 提供程序
分布式事件总线系统提供了一个可以被任何提供程序实现的**抽象**. 有两种开箱即用的提供程序:
* `LocalDistributedEventBus` 是默认实现,实现作为进程内工作的分布式事件总线. 是的!如果没有配置真正的分布式提供程序,**默认实现的工作方式与[本地事件总线](Local-Event-Bus.md)一样**.
* `RabbitMqDistributedEventBus` 通过[RabbitMQ](https://www.rabbitmq.com/)实现分布式事件总线. 请参阅[RabbitMQ集成文档](Distributed-Event-Bus-RabbitMQ-Integration.md)了解如何配置它.
使用本地事件总线作为默认具有一些重要的优点. 最重要的是:它允许你编写与分布式体系结构兼容的代码. 您现在可以编写一个整体应用程序,以后可以拆分成微服务. 最好通过分布式事件而不是本地事件在边界上下文之间(或在应用程序模块之间)进行通信.
例如,[预构建的应用模块](Modules/Index.md)被设计成在分布式系统中作为服务工作,同时它们也可以在独立应用程序中作为模块工作,而不依赖于外部消息代理.
## 发布事件
以下介绍了两种发布分布式事件的方法.
### IDistributedEventBus
可以[注入](Dependency-Injection.md) `IDistributedEventBus` 并且使用发布分布式事件.
**示例: 产品的存货数量发生变化时发布分布式事件**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IDistributedEventBus _distributedEventBus;
public MyService(IDistributedEventBus distributedEventBus)
{
_distributedEventBus = distributedEventBus;
}
public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
{
await _distributedEventBus.PublishAsync(
new StockCountChangedEvent
{
ProductId = productId,
NewCount = newCount
}
);
}
}
}
````
`PublishAsync` 方法需要一个参数:事件对象,它负责保持与事件相关的数据,是一个简单的普通类:
````csharp
using System;
namespace AbpDemo
{
[EventName("MyApp.Product.StockChange")]
public class StockCountChangedEto
{
public Guid ProductId { get; set; }
public int NewCount { get; set; }
}
}
````
即使你不需要传输任何数据也需要创建一个类(在这种情况下为空类).
> `Eto` 是我们按照约定使用的**E**vent **T**ransfer **O**bjects(事件传输对象)的后缀. s虽然这不是必需的,但我们发现识别这样的事件类很有用(就像应用层上的[DTO](Data-Transfer-Objects.md) 一样).
#### 事件名称
`EventName`attribute是可选的,但建议使用. 如果不声明,事件名将事件名称将是事件类的全名. 这里是 `AbpDemo.StockCountChangedEto`.
#### 关于序列化的事件对象
事件传输对象**必须是可序列化**的,因为将其传输到流程外时,它们将被序列化/反序列化为JSON或其他格式.
避免循环引用,多态,私有setter,并提供默认(空)构造函数,如果你有其他的构造函数.(虽然某些序列化器可能会正常工作),就像DTO一样.
### 实体/聚合根类
[实体](Entities.md)不能通过依赖注入注入服务,但是在实体/聚合根类中发布分布式事件是非常常见的.
**示例: 在聚合根方法内发布分布式事件**
````csharp
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
public int StockCount { get; private set; }
private Product() { }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
public void ChangeStockCount(int newCount)
{
StockCount = newCount;
//ADD an EVENT TO BE PUBLISHED
AddDistributedEvent(
new StockCountChangedEto
{
ProductId = Id,
NewCount = newCount
}
);
}
}
}
````
`AggregateRoot` 类定义了 `AddDistributedEvent` 来添加一个新的分布式事件,事件在聚合根对象保存(创建,更新或删除)到数据库时发布.
> 如果实体发布这样的事件,以可控的方式更改相关属性是一个好的实践,就像上面的示例一样 - `StockCount`只能由保证发布事件的 `ChangeStockCount` 方法来更改.
#### IGeneratesDomainEvents 接口
实际上添加分布式事件并不是 `AggregateRoot` 类独有的. 你可以为任何实体类实现 `IGeneratesDomainEvents`. 但是 `AggregateRoot` 默认实现了它简化你的工作.
> 不建议为不是聚合根的实体实现此接口,因为它可能不适用于此类实体的某些数据库提供程序. 例如它适用于EF Core,但不适用于MongoDB.
#### 它是如何实现的?
调用 `AddDistributedEvent` 不会立即发布事件. 当你将更改保存到数据库时发布该事件;
* 对于 EF Core, 它在 `DbContext.SaveChanges` 中发布.
* 对于 MongoDB, 它在你调用仓储的 `InsertAsync`, `UpdateAsync``DeleteAsync` 方法时发由 (因为MongoDB没有更改跟踪系统).
## 订阅事件
一个服务可以实现 `IDistributedEventHandler<TEvent>` 来处理事件.
**示例: 处理上面定义的`StockCountChangedEto`**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace AbpDemo
{
public class MyHandler
: IDistributedEventHandler<StockCountChangedEto>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEto eventData)
{
var productId = eventData.ProductId;
}
}
}
````
这就是全部.
* `MyHandler` 由ABP框架**自动发现**,并在发生 `StockCountChangedEto` 事件时调用 `HandleEventAsync`.
* 如果你使用的是分布式消息代理,比如RabbitMQ,ABP会自动**订阅消息代理上的事件**,获取消息执行处理程序.
* 如果事件处理程序成功执行(没有抛出任何异常),它将向消息代理发送**确认(ACK)**.
你可以在处理程序注入任何服务来执行所需的逻辑. 一个事件处理程序可以**订阅多个事件**,但是需要为每个事件实现 `IDistributedEventHandler<TEvent>` 接口.
> 事件处理程序类必须注册到依赖注入(DI),示例中使用了 `ITransientDependency`. 参阅[DI文档](Dependency-Injection.md)了解更多选项.
## 预定义的事件
如果你配置,ABP框架会为[实体](Entities.md)**自动发布创建,更新和删除**分布式事件.
### 事件类型
有三种预定义的事件类型:
* `EntityCreatedEto<T>` 是实体 `T` 创建后发布.
* `EntityUpdatedEto<T>` 是实体 `T` 更新后发布.
* `EntityDeletedEto<T>` 是实体 `T` 删除后发布.
这些都是泛型的, `T` 实际上是**E**vent **T**ransfer **O**bject (ETO)的类型,而不是实体的类型,因为实体对象不能做为事件数据传输,所以通常会为实体类定义一个ETO类,如为 `Product` 实体定义 `ProductEto`.
### 订阅事件
订阅自动事件与订阅常规分布式事件相同.
**示例: 产品更新后获取通知**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
namespace AbpDemo
{
public class MyHandler :
IDistributedEventHandler<EntityUpdatedEto<ProductEto>>,
ITransientDependency
{
public async Task HandleEventAsync(EntityUpdatedEto<ProductEto> eventData)
{
var productId = eventData.Entity.Id;
//TODO
}
}
}
````
* `MyHandler` 实现了 `IDistributedEventHandler<EntityUpdatedEto<ProductEto>>`.
### 配置
你可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 中配置 `AbpDistributedEntityEventOptions`添加选择器.
**示例: 配置示例**
````csharp
Configure<AbpDistributedEntityEventOptions>(options =>
{
//Enable for all entities
options.AutoEventSelectors.AddAll();
//Enable for a single entity
options.AutoEventSelectors.Add<IdentityUser>();
//Enable for all entities in a namespace (and child namespaces)
options.AutoEventSelectors.AddNamespace("Volo.Abp.Identity");
//Custom predicate expression that should return true to select a type
options.AutoEventSelectors.Add(
type => type.Namespace.StartsWith("MyProject.")
);
});
````
* 最后一个提供了灵活性来决定是否应该针对给定的实体类型发布事件. 返回 `true` 代表为该 `Type` 发布事件.
你可以添加多个选择器. 如果选择器之一与实体类型匹配,则将其选中.
### 事件传输对象
一旦你为一个实体启用了**自动事件**,ABP框架就会为实体上的更改发布事件. 如果你没有为实体指定对应的**E**vent **T**ransfer **O**bject(ETO), ABP框架会使用一个标准类型 `EntityEto`,它只有两个属性:
* `EntityType` (`string`): 实体类的全名(包括命令空间).
* `KeysAsString` (`string`): 已更改实体的主键.如果它只有一个主键,这个属性将是主键值. 对于复合键,它包含所有用`,`(逗号)分隔的键.
因此可以实现 `IDistributedEventHandler<EntityUpdatedEto<EntityEto>>` 订阅事件. 但是订阅这样的通用事件不是一个好方法,你可以为实体类型定义对应的ETO.
**示例: 为 `Product` 声明使用 `ProductDto`**
````csharp
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.AutoEventSelectors.Add<Product>();
options.EtoMappings.Add<Product, ProductEto>();
});
````
在这个示例中;
* 添加选择器允许发布 `Product` 实体的创建,更新和删除事件.
* 配置为使用 `ProductEto` 作为事件传输对象来发布与 `Product` 相关的事件.
分布式事件系统使用[对象到对象的映射](Object-To-Object-Mapping.md)系统来映射 `Product` 对象到 `ProductEto` 对象,你需要配置映射. 请参阅可以对象到对象映射文档了解所有选项,下面的示例展示了如何使用[AutoMapper](https://automapper.org/)库配置它.
**示例: 使用AutoMapper配置 `Product``ProductEto` 映射**
````csharp
using System;
using AutoMapper;
using Volo.Abp.Domain.Entities.Events.Distributed;
namespace AbpDemo
{
[AutoMap(typeof(Product))]
public class ProductEto : EntityEto
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
````
此示例使用AutoMapper的 `AutoMap` 属性配置的映射. 你可以创建一个配置文件类代替. 请参阅AutoMapper文档了解更多选项.

11
docs/zh-Hans/Event-Bus.md

@ -1,3 +1,10 @@
# Event Bus
# 事件总线
TODO
事件总线是将消息从发送方传输到接收方的中介. 它在对象,服务和应用程序之间提供了一种松散耦合的通信方式.
## 事件总线类型
ABP框架提供了两种事件总线类型;
* **[本地事件总线](Local-Event-Bus.md)** 适合进程内消息传递.
* **[分布式事件总线](Distributed-Event-Bus.md)** 适合进程间消息传递,如微服务发布和订阅分布式事件.

227
docs/zh-Hans/Local-Event-Bus.md

@ -0,0 +1,227 @@
# 本地事件总线
本地事件总线允许服务发布和订阅**进程内事件**. 这意味着如果两个服务(发布者和订阅者)在同一个进程中运行,那么它是合适的.
## 发布事件
以下介绍了两种发布本地事件的方法.
### ILocalEventBus
可以[注入](Dependency-Injection.md) `ILocalEventBus` 并且使用发布本地事件.
**示例: 产品的存货数量发生变化时发布本地事件**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly ILocalEventBus _localEventBus;
public MyService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
{
//TODO: IMPLEMENT YOUR LOGIC...
//PUBLISH THE EVENT
await _localEventBus.PublishAsync(
new StockCountChangedEvent
{
ProductId = productId,
NewCount = newCount
}
);
}
}
}
````
`PublishAsync` 方法需要一个参数:事件对象,它负责保持与事件相关的数据,是一个简单的普通类:
````csharp
using System;
namespace AbpDemo
{
public class StockCountChangedEvent
{
public Guid ProductId { get; set; }
public int NewCount { get; set; }
}
}
````
即使你不需要传输任何数据也需要创建一个类(在这种情况下为空类).
### 实体/聚合根类
[实体](Entities.md)不能通过依赖注入注入服务,但是在实体/聚合根类中发布本地事件是非常常见的.
**示例: 在聚合根方法内发布本地事件**
````csharp
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
public int StockCount { get; private set; }
private Product() { }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
public void ChangeStockCount(int newCount)
{
StockCount = newCount;
//ADD an EVENT TO BE PUBLISHED
AddLocalEvent(
new StockCountChangedEvent
{
ProductId = Id,
NewCount = newCount
}
);
}
}
}
````
`AggregateRoot` 类定义了 `AddLocalEvent` 来添加一个新的本地事件,事件在聚合根对象保存(创建,更新或删除)到数据库时发布.
> 如果实体发布这样的事件,以可控的方式更改相关属性是一个好的实践,就像上面的示例一样 - `StockCount`只能由保证发布事件的 `ChangeStockCount` 方法来更改.
#### IGeneratesDomainEvents 接口
实际上添加本地事件并不是 `AggregateRoot` 类独有的. 你可以为任何实体类实现 `IGeneratesDomainEvents`. 但是 `AggregateRoot` 默认实现了它简化你的工作.
> 不建议为不是聚合根的实体实现此接口,因为它可能不适用于此类实体的某些数据库提供程序. 例如它适用于EF Core,但不适用于MongoDB.
#### 它是如何实现的?
调用 `AddLocalEvent` 不会立即发布事件. 当你将更改保存到数据库时发布该事件;
* 对于 EF Core, 它在 `DbContext.SaveChanges` 中发布.
* 对于 MongoDB, 它在你调用仓储的 `InsertAsync`, `UpdateAsync``DeleteAsync` 方法时发由 (因为MongoDB没有更改跟踪系统).
## 订阅事件
一个服务可以实现 `ILocalEventHandler<TEvent>` 来处理事件.
**示例: 处理上面定义的`StockCountChangedEvent`**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
namespace AbpDemo
{
public class MyHandler
: ILocalEventHandler<StockCountChangedEvent>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEvent eventData)
{
//TODO: your code that does somthing on the event
}
}
}
````
这就是全部,`MyHandler` 由ABP框架**自动发现**,并在发生 `StockCountChangedEvent` 事件时调用 `HandleEventAsync`.
* 事件可以由**0个或多个处理程序**订阅.
* 一个事件处理程序可以**订阅多个事件**,但是需要为每个事件实现 `ILocalEventHandler<TEvent>` 接口.
> 事件处理程序类必须注册到依赖注入(DI),示例中使用了 `ITransientDependency`. 参阅[DI文档](Dependency-Injection.md)了解更多选项.
## 事务和异常行为
当一个事件发布,订阅的事件处理程序将立即执行.所以;
* 如果处理程序**抛出一个异常**,它会影响发布该事件的代码. 这意味着它在 `PublishAsync` 调用上获得异常. 因此如果你想隐藏错误,在事件处理程序中**使用try-catch**.
*如果在一个[工作单元](Unit-Of-Work.md)范围内执行的事件发布的代码,该事件处理程序也由工作单元覆盖. 这意味着,如果你的UOW是事务和处理程序抛出一个异常,事务会回滚.
## 预定义的事件
**发布实体创建,更新,删除事件**是常见的操作. ABP框架为所有的实体**自动**发布这些事件. 你只需要订阅相关的事件.
**示例: 订阅用户创建事件**
````csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace AbpDemo
{
public class MyHandler
: ILocalEventHandler<EntityCreatedEventData<IdentityUser>>,
ITransientDependency
{
public async Task HandleEventAsync(
EntityCreatedEventData<IdentityUser> eventData)
{
var userName = eventData.Entity.UserName;
var email = eventData.Entity.Email;
//...
}
}
}
````
这个类订阅 `EntityCreatedEventData<IdentityUser>`,它在用户创建后发布. 你可能需要向新用户发送一封"欢迎"电子邮件.
这些事件有两种类型:过去时态的事件和进行时态的事件.
### 用过去时态事件
当相关工作单元完成且实体更改成功保存到数据库时,将发布带有过去时态的事件. 如果在这些事件处理程序上抛出异常,则**无法回滚**事务,因为事务已经提交.
事件类型;
* `EntityCreatedEventData<T>` 当实体创建创建成功后发布.
* `EntityUpdatedEventData<T>` 当实体创建更新成功后发布.
* `EntityDeletedEventData<T>` 当实体创建删除成功后发布.
* `EntityChangedEventData<T>` 当实体创建,更新,删除后发布. 如果你需要监听任何类型的更改,它是一种快捷方式 - 而不是订阅单个事件.
### 用于进行时态事件
带有进行时态的事件在完成事务之前发布(如果数据库事务由所使用的数据库提供程序支持). 如果在这些事件处理程序上抛出异常,它**会回滚**事务,因为事务还没有完成,更改也没有保存到数据库中.
事件类型;
* `EntityCreatingEventData<T>` 当新实体保存到数据库前发布.
* `EntityUpdatingEventData<T>` 当已存在实体更新到数据库前发布.
* `EntityDeletingEventData<T>` 删除实体前发布.
* `EntityChangingEventData<T>` 当实体创建,更新,删除前发布. 如果你需要监听任何类型的更改,它是一种快捷方式 - 而不是订阅单个事件.
#### 它是如何实现的?
在将更改保存到数据库时发布预构建事件;
* 对于 EF Core, 他们在 `DbContext.SaveChanges` 发布.
* 对于 MongoDB, 在你调用仓储的 `InsertAsync`, `UpdateAsync``DeleteAsync` 方法发布(因为MongoDB没有更改追踪系统).

8
docs/zh-Hans/UI/Angular/Permission-Management.md

@ -8,7 +8,7 @@
```js
import { Store } from '@ngxs/store';
import { ConfigState } from '../states';
import { ConfigState } from '@abp/ng.core';
export class YourComponent {
constructor(private store: Store) {}
@ -24,7 +24,7 @@ export class YourComponent {
或者你可以通过 `ConfigStateService` 获取它:
```js
import { ConfigStateService } from '../services/config-state.service';
import { ConfigStateService } from '@abp/ng.core';
export class YourComponent {
constructor(private configStateService: ConfigStateService) {}
@ -42,7 +42,7 @@ export class YourComponent {
你可以使用 `PermissionDirective` 来根据用户的权限控制DOM元素是否可见.
```html
<div *abpPermission="AbpIdentity.Roles">
<div *abpPermission="'AbpIdentity.Roles'">
仅当用户具有`AbpIdentity.Roles`权限时,此内容才可见.
</div>
```
@ -58,6 +58,8 @@ export class YourComponent {
`requiredPolicy` 添加到路由模块中的 `routes`属性.
```js
import { PermissionGuard } from '@abp/ng.core';
// ...
const routes: Routes = [
{
path: 'path',

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

@ -154,16 +154,23 @@
]
},
{
"text": "事件",
"text": "事件总线",
"items": [
{
"text": "本地 Event Bus"
"text": "概述",
"path": "Event-Bus.md"
},
{
"text": "本地 Event Bus",
"path": "Local-Event-Bus.md"
},
{
"text": "分布式 Event Bus",
"path": "Distributed-Event-Bus.md",
"items": [
{
"text": "RabbitMQ 集成"
"text": "RabbitMQ 集成",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
}
]
}

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs

@ -12,6 +12,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2
{
//TODO: Add select2.full.min.js or localize!
context.Files.AddIfNotContains("/libs/select2/js/select2.min.js");
context.Files.AddIfNotContains("/libs/select2/js/select2-bootstrap-modal-patch.js");
}
}
}

12
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs

@ -63,11 +63,13 @@ namespace Volo.Abp.Cli.Commands
solution = Directory.GetFiles(directory, "*.sln", SearchOption.AllDirectories).FirstOrDefault();
}
var checkAll = commandLineArgs.Options.ContainsKey(Options.CheckAll.Long);
if (solution != null)
{
var solutionName = Path.GetFileName(solution).RemovePostFix(".sln");
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(solution, includePreviews);
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(solution, includePreviews, checkAll: checkAll);
Logger.LogInformation($"Volo packages are updated in {solutionName} solution.");
return;
@ -79,7 +81,7 @@ namespace Volo.Abp.Cli.Commands
{
var projectName = Path.GetFileName(project).RemovePostFix(".csproj");
await _nugetPackagesVersionUpdater.UpdateProjectAsync(project, includePreviews);
await _nugetPackagesVersionUpdater.UpdateProjectAsync(project, includePreviews, checkAll: checkAll);
Logger.LogInformation($"Volo packages are updated in {projectName} project.");
return;
@ -107,6 +109,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("--nuget (Only updates Nuget packages)");
sb.AppendLine("-sp|--solution-path (Specify the solution path)");
sb.AppendLine("-sn|--solution-name (Specify the solution name)");
sb.AppendLine("--check-all (Check the new version of each package separately)");
sb.AppendLine("");
sb.AppendLine("Some examples:");
sb.AppendLine("");
@ -149,6 +152,11 @@ namespace Volo.Abp.Cli.Commands
public const string Npm = "npm";
public const string NuGet = "nuget";
}
public static class CheckAll
{
public const string Long = "check-all";
}
}
}
}

47
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs

@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -52,34 +53,38 @@ namespace Volo.Abp.Cli.ProjectModification
_npmGlobalPackagesChecker.Check();
foreach (var file in fileList)
var packagesUpdated = new ConcurrentDictionary<string, bool>();
async Task UpdateAsync(string file)
{
var packagesUpdated = await UpdatePackagesInFile(file, includePreviews, switchToStable);
var updated = await UpdatePackagesInFile(file, includePreviews, switchToStable);
packagesUpdated.TryAdd(file, updated);
};
if (packagesUpdated)
{
var fileDirectory = Path.GetDirectoryName(file).EnsureEndsWith(Path.DirectorySeparatorChar);
Task.WaitAll(fileList.Select(UpdateAsync).ToArray());
foreach (var file in packagesUpdated.Where(x => x.Value))
{
var fileDirectory = Path.GetDirectoryName(file.Key).EnsureEndsWith(Path.DirectorySeparatorChar);
if (IsAngularProject(fileDirectory))
if (IsAngularProject(fileDirectory))
{
if (includePreviews)
{
if (includePreviews)
{
await CreateNpmrcFileAsync(Path.GetDirectoryName(file));
}
else if (switchToStable)
{
await DeleteNpmrcFileAsync(Path.GetDirectoryName(file));
}
await CreateNpmrcFileAsync(Path.GetDirectoryName(file.Key));
}
RunYarn(fileDirectory);
if (!IsAngularProject(fileDirectory))
else if (switchToStable)
{
Thread.Sleep(500);
RunGulp(fileDirectory);
await DeleteNpmrcFileAsync(Path.GetDirectoryName(file.Key));
}
}
RunYarn(fileDirectory);
if (!IsAngularProject(fileDirectory))
{
Thread.Sleep(500);
RunGulp(fileDirectory);
}
}
}

49
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs

@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using NuGet.Versioning;
using System.IO;
using System.Linq;
@ -24,19 +25,47 @@ namespace Volo.Abp.Cli.ProjectModification
Logger = NullLogger<VoloNugetPackagesVersionUpdater>.Instance;
}
public async Task UpdateSolutionAsync(string solutionPath, bool includePreviews = false, bool switchToStable = false)
public async Task UpdateSolutionAsync(string solutionPath, bool includePreviews = false, bool switchToStable = false, bool checkAll = false)
{
var projectPaths = ProjectFinder.GetProjectFiles(solutionPath);
foreach (var filePath in projectPaths)
if (checkAll)
{
await UpdateInternalAsync(filePath, includePreviews, switchToStable);
Task.WaitAll(projectPaths.Select(projectPath => UpdateInternalAsync(projectPath, includePreviews, switchToStable)).ToArray());
}
else
{
var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core");
var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core");
async Task UpdateAsync(string filePath)
{
var fileContent = File.ReadAllText(filePath);
var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable, latestVersionFromNuget, latestVersionFromMyGet);
File.WriteAllText(filePath, updatedContent);
}
Task.WaitAll(projectPaths.Select(UpdateAsync).ToArray());
}
}
public async Task UpdateProjectAsync(string projectPath, bool includePreviews = false, bool switchToStable = false)
public async Task UpdateProjectAsync(string projectPath, bool includePreviews = false, bool switchToStable = false, bool checkAll = false)
{
await UpdateInternalAsync(projectPath, includePreviews, switchToStable);
if (checkAll)
{
await UpdateInternalAsync(projectPath, includePreviews, switchToStable);
}
else
{
var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core");
var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core");
var fileContent = File.ReadAllText(projectPath);
var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable, latestVersionFromNuget, latestVersionFromMyGet);
File.WriteAllText(projectPath, updatedContent);
}
}
protected virtual async Task UpdateInternalAsync(string projectPath, bool includePreviews = false, bool switchToStable = false)
@ -47,7 +76,7 @@ namespace Volo.Abp.Cli.ProjectModification
File.WriteAllText(projectPath, updatedContent);
}
private async Task<string> UpdateVoloPackagesAsync(string content, bool includePreviews = false, bool switchToStable = false)
private async Task<string> UpdateVoloPackagesAsync(string content, bool includePreviews = false, bool switchToStable = false, SemanticVersion latestNugetVersion = null, string latestMyGetVersion = null)
{
string packageId = null;
@ -81,7 +110,7 @@ namespace Volo.Abp.Cli.ProjectModification
if (includePreviews || (currentVersion.Contains("-preview") && !switchToStable))
{
var latestVersion = await GetLatestVersionFromMyGet(packageId);
var latestVersion = latestMyGetVersion ?? await GetLatestVersionFromMyGet(packageId);
if (currentVersion != latestVersion)
{
@ -95,7 +124,7 @@ namespace Volo.Abp.Cli.ProjectModification
}
else
{
var latestVersion = await _nuGetService.GetLatestVersionOrNullAsync(packageId);
var latestVersion = latestNugetVersion ?? await _nuGetService.GetLatestVersionOrNullAsync(packageId);
if (latestVersion != null && (currentVersion.Contains("-preview") || currentSemanticVersion < latestVersion))
{
@ -109,7 +138,7 @@ namespace Volo.Abp.Cli.ProjectModification
}
}
return await Task.FromResult(doc.OuterXml);
return doc.OuterXml;
}
}
catch (Exception ex)

10
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs

@ -359,5 +359,15 @@ namespace Volo.Abp.Reflection
? type.GenericTypeArguments[0]
: type;
}
public static bool IsDefaultValue([CanBeNull] object obj)
{
if (obj == null)
{
return true;
}
return obj.Equals(GetDefaultValue(obj.GetType()));
}
}
}

45
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Domain.Entities
{
@ -16,6 +14,11 @@ namespace Volo.Abp.Domain.Entities
}
public abstract object[] GetKeys();
public bool EntityEquals(IEntity other)
{
return EntityHelper.EntityEquals(this, other);
}
}
/// <inheritdoc cref="IEntity{TKey}" />
@ -35,44 +38,6 @@ namespace Volo.Abp.Domain.Entities
Id = id;
}
public bool EntityEquals(object obj)
{
if (obj == null || !(obj is Entity<TKey>))
{
return false;
}
//Same instances must be considered as equal
if (ReferenceEquals(this, obj))
{
return true;
}
//Transient objects are not considered as equal
var other = (Entity<TKey>)obj;
if (EntityHelper.HasDefaultId(this) && EntityHelper.HasDefaultId(other))
{
return false;
}
//Must have a IS-A relation of types or must be same type
var typeOfThis = GetType().GetTypeInfo();
var typeOfOther = other.GetType().GetTypeInfo();
if (!typeOfThis.IsAssignableFrom(typeOfOther) && !typeOfOther.IsAssignableFrom(typeOfThis))
{
return false;
}
//Different tenants may have an entity with same Id.
if (this is IMultiTenant && other is IMultiTenant &&
this.As<IMultiTenant>().TenantId != other.As<IMultiTenant>().TenantId)
{
return false;
}
return Id.Equals(other.Id);
}
public override object[] GetKeys()
{
return new object[] {Id};

143
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs

@ -5,6 +5,8 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
namespace Volo.Abp.Domain.Entities
{
@ -16,6 +18,98 @@ namespace Volo.Abp.Domain.Entities
private static readonly ConcurrentDictionary<string, PropertyInfo> CachedIdProperties =
new ConcurrentDictionary<string, PropertyInfo>();
public static bool EntityEquals(IEntity entity1, IEntity entity2)
{
if (entity1 == null || entity2 == null)
{
return false;
}
//Same instances must be considered as equal
if (ReferenceEquals(entity1, entity2))
{
return true;
}
//Must have a IS-A relation of types or must be same type
var typeOfEntity1 = entity1.GetType();
var typeOfEntity2 = entity2.GetType();
if (!typeOfEntity1.IsAssignableFrom(typeOfEntity2) && !typeOfEntity2.IsAssignableFrom(typeOfEntity1))
{
return false;
}
//Different tenants may have an entity with same Id.
if (entity1 is IMultiTenant && entity2 is IMultiTenant)
{
var tenant1Id = ((IMultiTenant) entity1).TenantId;
var tenant2Id = ((IMultiTenant) entity2).TenantId;
if (tenant1Id != tenant2Id)
{
if (tenant1Id == null || tenant2Id == null)
{
return false;
}
if (!tenant1Id.Equals(tenant2Id))
{
return false;
}
}
}
//Transient objects are not considered as equal
if (HasDefaultKeys(entity1) && HasDefaultKeys(entity2))
{
return false;
}
var entity1Keys = entity1.GetKeys();
var entity2Keys = entity2.GetKeys();
if (entity1Keys.Length != entity2Keys.Length)
{
return false;
}
for (var i = 0; i < entity1Keys.Length; i++)
{
var entity1Key = entity1Keys[i];
var entity2Key = entity2Keys[i];
if (entity1Key == null)
{
if (entity2Key == null)
{
//Both null, so considered as equals
continue;
}
//entity2Key is not null!
return false;
}
if (entity2Key == null)
{
//entity1Key was not null!
return false;
}
if (TypeHelper.IsDefaultValue(entity1Key) && TypeHelper.IsDefaultValue(entity2Key))
{
return false;
}
if (!entity1Key.Equals(entity2Key))
{
return false;
}
}
return true;
}
public static bool IsEntity([NotNull] Type type)
{
return typeof(IEntity).IsAssignableFrom(type);
@ -25,7 +119,8 @@ namespace Volo.Abp.Domain.Entities
{
foreach (var interfaceType in type.GetInterfaces())
{
if (interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEntity<>))
if (interfaceType.GetTypeInfo().IsGenericType &&
interfaceType.GetGenericTypeDefinition() == typeof(IEntity<>))
{
return true;
}
@ -55,6 +150,44 @@ namespace Volo.Abp.Domain.Entities
return false;
}
private static bool IsDefaultKeyValue(object value)
{
if (value == null)
{
return true;
}
var type = value.GetType();
//Workaround for EF Core since it sets int/long to min value when attaching to DbContext
if (type == typeof(int))
{
return Convert.ToInt32(value) <= 0;
}
if (type == typeof(long))
{
return Convert.ToInt64(value) <= 0;
}
return TypeHelper.IsDefaultValue(value);
}
public static bool HasDefaultKeys([NotNull] IEntity entity)
{
Check.NotNull(entity, nameof(entity));
foreach (var key in entity.GetKeys())
{
if (!IsDefaultKeyValue(key))
{
return false;
}
}
return true;
}
/// <summary>
/// Tries to find the primary key type of the given entity type.
/// May return null if given type does not implement <see cref="IEntity{TKey}"/>
@ -75,12 +208,14 @@ namespace Volo.Abp.Domain.Entities
{
if (!typeof(IEntity).IsAssignableFrom(entityType))
{
throw new AbpException($"Given {nameof(entityType)} is not an entity. It should implement {typeof(IEntity).AssemblyQualifiedName}!");
throw new AbpException(
$"Given {nameof(entityType)} is not an entity. It should implement {typeof(IEntity).AssemblyQualifiedName}!");
}
foreach (var interfaceType in entityType.GetTypeInfo().GetInterfaces())
{
if (interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEntity<>))
if (interfaceType.GetTypeInfo().IsGenericType &&
interfaceType.GetGenericTypeDefinition() == typeof(IEntity<>))
{
return interfaceType.GenericTypeArguments[0];
}
@ -132,4 +267,4 @@ namespace Volo.Abp.Domain.Entities
property?.SetValue(entity, idFactory());
}
}
}
}

5
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorList.cs

@ -4,9 +4,6 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public class AutoEntityDistributedEventSelectorList : List<NamedTypeSelector>, IAutoEntityDistributedEventSelectorList
{
public bool RemoveByName(string name)
{
return RemoveAll(s => s.Name == name) > 0;
}
}
}

39
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@ -69,6 +70,44 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed
);
}
public static void Add(
[NotNull] this IAutoEntityDistributedEventSelectorList selectors,
string selectorName,
Func<Type, bool> predicate)
{
Check.NotNull(selectors, nameof(selectors));
if (selectors.Any(s => s.Name == selectorName))
{
throw new AbpException($"There is already a selector added before with the same name: {selectorName}");
}
selectors.Add(
new NamedTypeSelector(
selectorName,
predicate
)
);
}
public static void Add(
[NotNull] this IAutoEntityDistributedEventSelectorList selectors,
Func<Type, bool> predicate)
{
selectors.Add(Guid.NewGuid().ToString("N"), predicate);
}
public static bool RemoveByName(
[NotNull] this IAutoEntityDistributedEventSelectorList selectors,
[NotNull] string name)
{
Check.NotNull(selectors, nameof(selectors));
Check.NotNull(name, nameof(name));
return selectors.RemoveAll(s => s.Name == name).Count > 0;
}
public static bool IsMatch([NotNull] this IAutoEntityDistributedEventSelectorList selectors, Type entityType)
{
Check.NotNull(selectors, nameof(selectors));

4
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IAutoEntityDistributedEventSelectorList.cs

@ -1,9 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public interface IAutoEntityDistributedEventSelectorList : IList<NamedTypeSelector>
{
bool RemoveByName(string name);
}
}

133
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs

@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
@ -18,6 +21,7 @@ namespace Volo.Abp.Domain.Entities.Events
/// </summary>
public class EntityChangeEventHelper : IEntityChangeEventHelper, ITransientDependency
{
public ILogger<EntityChangeEventHelper> Logger { get; set; }
public ILocalEventBus LocalEventBus { get; set; }
public IDistributedEventBus DistributedEventBus { get; set; }
@ -36,6 +40,7 @@ namespace Volo.Abp.Domain.Entities.Events
LocalEventBus = NullLocalEventBus.Instance;
DistributedEventBus = NullDistributedEventBus.Instance;
Logger = NullLogger<EntityChangeEventHelper>.Instance;
}
public async Task TriggerEventsAsync(EntityChangeReport changeReport)
@ -56,6 +61,7 @@ namespace Volo.Abp.Domain.Entities.Events
LocalEventBus,
typeof(EntityCreatingEventData<>),
entity,
entity,
true
);
}
@ -66,6 +72,7 @@ namespace Volo.Abp.Domain.Entities.Events
LocalEventBus,
typeof(EntityCreatedEventData<>),
entity,
entity,
false
);
@ -78,6 +85,7 @@ namespace Volo.Abp.Domain.Entities.Events
DistributedEventBus,
typeof(EntityCreatedEto<>),
eto,
entity,
false
);
}
@ -101,6 +109,7 @@ namespace Volo.Abp.Domain.Entities.Events
LocalEventBus,
typeof(EntityUpdatingEventData<>),
entity,
entity,
true
);
}
@ -111,6 +120,7 @@ namespace Volo.Abp.Domain.Entities.Events
LocalEventBus,
typeof(EntityUpdatedEventData<>),
entity,
entity,
false
);
@ -123,6 +133,7 @@ namespace Volo.Abp.Domain.Entities.Events
DistributedEventBus,
typeof(EntityUpdatedEto<>),
eto,
entity,
false
);
}
@ -135,6 +146,7 @@ namespace Volo.Abp.Domain.Entities.Events
LocalEventBus,
typeof(EntityDeletingEventData<>),
entity,
entity,
true
);
}
@ -145,6 +157,7 @@ namespace Volo.Abp.Domain.Entities.Events
LocalEventBus,
typeof(EntityDeletedEventData<>),
entity,
entity,
false
);
@ -157,6 +170,7 @@ namespace Volo.Abp.Domain.Entities.Events
DistributedEventBus,
typeof(EntityDeletedEto<>),
eto,
entity,
false
);
}
@ -206,22 +220,129 @@ namespace Volo.Abp.Domain.Entities.Events
{
foreach (var distributedEvent in distributedEvents)
{
await DistributedEventBus.PublishAsync(distributedEvent.EventData.GetType(), distributedEvent.EventData);
await DistributedEventBus.PublishAsync(distributedEvent.EventData.GetType(),
distributedEvent.EventData);
}
}
protected virtual async Task TriggerEventWithEntity(IEventBus eventPublisher, Type genericEventType, object entity, bool triggerInCurrentUnitOfWork)
protected virtual async Task TriggerEventWithEntity(
IEventBus eventPublisher,
Type genericEventType,
object entityOrEto,
object originalEntity,
bool triggerInCurrentUnitOfWork)
{
var entityType = ProxyHelper.UnProxy(entity).GetType();
var entityType = ProxyHelper.UnProxy(entityOrEto).GetType();
var eventType = genericEventType.MakeGenericType(entityType);
var currentUow = UnitOfWorkManager.Current;
if (triggerInCurrentUnitOfWork || UnitOfWorkManager.Current == null)
if (triggerInCurrentUnitOfWork || currentUow == null)
{
await eventPublisher.PublishAsync(eventType, Activator.CreateInstance(eventType, entity));
await eventPublisher.PublishAsync(
eventType,
Activator.CreateInstance(eventType, entityOrEto)
);
return;
}
UnitOfWorkManager.Current.OnCompleted(() => eventPublisher.PublishAsync(eventType, Activator.CreateInstance(eventType, entity)));
var eventList = GetEventList(currentUow);
var isFirstEvent = !eventList.Any();
eventList.AddUniqueEvent(eventPublisher, eventType, entityOrEto, originalEntity);
/* Register to OnCompleted if this is the first item.
* Other items will already be in the list once the UOW completes.
*/
if (isFirstEvent)
{
currentUow.OnCompleted(
async () =>
{
foreach (var eventEntry in eventList)
{
try
{
await eventPublisher.PublishAsync(
eventEntry.EventType,
Activator.CreateInstance(eventEntry.EventType, eventEntry.EntityOrEto)
);
}
catch (Exception ex)
{
Logger.LogError(
$"Caught an exception while publishing the event '{eventType.FullName}' for the entity '{entityOrEto}'");
Logger.LogException(ex);
}
}
}
);
}
}
private EntityChangeEventList GetEventList(IUnitOfWork currentUow)
{
return (EntityChangeEventList) currentUow.Items.GetOrAdd(
"AbpEntityChangeEventList",
() => new EntityChangeEventList()
);
}
private class EntityChangeEventList : List<EntityChangeEventEntry>
{
public void AddUniqueEvent(IEventBus eventBus, Type eventType, object entityOrEto, object originalEntity)
{
var newEntry = new EntityChangeEventEntry(eventBus, eventType, entityOrEto, originalEntity);
//Latest "same" event overrides the previous events.
for (var i = 0; i < Count; i++)
{
if (this[i].IsSameEvent(newEntry))
{
this[i] = newEntry;
return;
}
}
//If this is a "new" event, add to the end
Add(newEntry);
}
}
private class EntityChangeEventEntry
{
public IEventBus EventBus { get; }
public Type EventType { get; }
public object EntityOrEto { get; }
public object OriginalEntity { get; }
public EntityChangeEventEntry(IEventBus eventBus, Type eventType, object entityOrEto, object originalEntity)
{
EventType = eventType;
EntityOrEto = entityOrEto;
OriginalEntity = originalEntity;
EventBus = eventBus;
}
public bool IsSameEvent(EntityChangeEventEntry otherEntry)
{
if (EventBus != otherEntry.EventBus || EventType != otherEntry.EventType)
{
return false;
}
var originalEntityRef = OriginalEntity as IEntity;
var otherOriginalEntityRef = otherEntry.OriginalEntity as IEntity;
if (originalEntityRef == null || otherOriginalEntityRef == null)
{
return false;
}
return EntityHelper.EntityEquals(originalEntityRef, otherOriginalEntityRef);
}
}
}
}

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

@ -0,0 +1,7 @@
{
"culture": "tr",
"texts": {
"DisplayName:Abp.Localization.DefaultLanguage": "Varsayılan dil",
"Description:Abp.Localization.DefaultLanguage": "Varsayılan uygulama dili."
}
}

14
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json

@ -13,18 +13,22 @@
"DefaultErrorMessage404": "未找到資源!",
"DefaultErrorMessage404Detail": "未在服務中找到請求的資源!",
"EntityNotFoundErrorMessage": "實體 {0} 不存在,id = {1}!",
"Languages": "語系",
"Error": "錯誤",
"AreYouSure": "你確定嗎?",
"Cancel": "取消",
"Yes": "是",
"No": "否",
"Ok": "好",
"Close": "關閉",
"Save": "保存",
"SavingWithThreeDot": "保存中...",
"Actions": "操作",
"Delete": "刪除",
"Edit": "修改",
"Refresh": "刷新",
"Refresh": "更新",
"Language": "語言",
"LoadMore": "讀更多",
"ProcessingWithThreeDot": "處理中...",
"LoadingWithThreeDot": "載入中...",
"Welcome": "歡迎",
@ -43,11 +47,13 @@
"PagerInfoEmpty": "顯示0個紀錄中的0到0",
"PagerInfoFiltered": "(從 _MAX_ 所有紀錄中過濾掉)",
"NoDataAvailableInDatatable": "資料表中沒有資料",
"Total": "總共",
"Selected": "已選擇",
"PagerShowMenuEntries": "顯示 _MENU_ 實體",
"DatatableActionDropdownDefaultText": "操作",
"ChangePassword": "修改密碼",
"PersonalInfo": "個人資料",
"AreYouSureYouWantToCancelEditingWarningMessage": "你有未保存的更改.",
"AreYouSureYouWantToCancelEditingWarningMessage": "你有未保存的更改",
"UnhandledException": "未處理的異常!",
"401Message": "未授權",
"403Message": "禁止訪問",
@ -55,6 +61,6 @@
"500Message": "內部伺服器錯誤",
"GoHomePage": "返回首頁",
"GoBack": "返回",
"Search": "搜索"
"Search": "查詢"
}
}
}

139
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Entity_Tests.cs

@ -0,0 +1,139 @@
using System;
using Shouldly;
using Volo.Abp.MultiTenancy;
using Xunit;
namespace Volo.Abp.Domain.Entities
{
public class Entity_Tests
{
[Fact]
public void EntityEquals_Should_Return_True_For_Same_Keys()
{
var idValue1 = Guid.NewGuid();
var idValue2 = Guid.NewGuid();
new Person(idValue1).EntityEquals(new Person(idValue1)).ShouldBeTrue();
new Car(42).EntityEquals(new Car(42)).ShouldBeTrue();
new Product("a").EntityEquals(new Product("a")).ShouldBeTrue();
new Phone(idValue1, "123").EntityEquals(new Phone(idValue1, "123")).ShouldBeTrue();
}
[Fact]
public void EntityEquals_Should_Return_False_For_Different_Keys()
{
var idValue1 = Guid.NewGuid();
var idValue2 = Guid.NewGuid();
new Person(idValue1).EntityEquals(new Person()).ShouldBeFalse();
new Person(idValue1).EntityEquals(new Person(idValue2)).ShouldBeFalse();
new Car(42).EntityEquals(new Car()).ShouldBeFalse();
new Car(42).EntityEquals(new Car(43)).ShouldBeFalse();
new Product("a").EntityEquals(new Product()).ShouldBeFalse();
new Product("a").EntityEquals(new Product("b")).ShouldBeFalse();
new Phone(idValue1, "123").EntityEquals(new Phone()).ShouldBeFalse();
new Phone(idValue1, "123").EntityEquals(new Phone(idValue1, null)).ShouldBeFalse();
new Phone(idValue1, "123").EntityEquals(new Phone(idValue1, "321")).ShouldBeFalse();
}
[Fact]
public void EntityEquals_Should_Return_False_For_Both_Default_Keys()
{
new Person().EntityEquals(new Person()).ShouldBeFalse();
new Car().EntityEquals(new Car()).ShouldBeFalse();
new Product().EntityEquals(new Product()).ShouldBeFalse();
new Phone().EntityEquals(new Phone()).ShouldBeFalse();
}
[Fact]
public void Different_Tenants_With_Same_Keys_Considered_As_Different_Objects()
{
var tenantId1 = Guid.NewGuid();
var tenantId2 = Guid.NewGuid();
new Car(42, tenantId1).EntityEquals(new Car(42)).ShouldBeFalse();
new Car(42).EntityEquals(new Car(42, tenantId2)).ShouldBeFalse();
new Car(42, tenantId1).EntityEquals(new Car(42, tenantId2)).ShouldBeFalse();
}
[Fact]
public void Same_Tenants_With_Same_Keys_Considered_As_Same_Objects()
{
var tenantId1 = Guid.NewGuid();
new Car(42).EntityEquals(new Car(42)).ShouldBeTrue();
new Car(42, tenantId1).EntityEquals(new Car(42, tenantId1)).ShouldBeTrue();
}
public class Person : Entity<Guid>
{
public Person()
{
}
public Person(Guid id)
: base(id)
{
}
}
public class Car : Entity<int>, IMultiTenant
{
public Guid? TenantId { get; }
public Car()
{
}
public Car(int id, Guid? tenantId = null)
: base(id)
{
TenantId = tenantId;
}
}
public class Product : Entity<string>
{
public Product()
{
}
public Product(string id)
: base(id)
{
}
}
public class Phone : Entity
{
public Guid PersonId { get; set; }
public string Number { get; set; }
public Phone()
{
}
public Phone(Guid personId, string number)
{
PersonId = personId;
Number = number;
}
public override object[] GetKeys()
{
return new Object[] {PersonId, Number};
}
}
}
}

22
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions_Tests.cs

@ -0,0 +1,22 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public class AutoEntityDistributedEventSelectorListExtensions_Tests
{
[Fact]
public void Add_Entity()
{
var selectors = new AutoEntityDistributedEventSelectorList();
selectors.Add<MyEntity>();
selectors.IsMatch(typeof(MyEntity)).ShouldBeTrue();
}
private class MyEntity : Entity<string>
{
}
}
}

6
framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json

@ -0,0 +1,6 @@
{
"culture": "tr",
"texts": {
"hello": "Merhaba"
}
}

2
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/EntityChangeEvents_Tests.cs

@ -4,6 +4,6 @@ namespace Volo.Abp.EntityFrameworkCore.DomainEvents
{
public class EntityChangeEvents_Tests : EntityChangeEvents_Tests<AbpEntityFrameworkCoreTestModule>
{
}
}

1
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs

@ -1,6 +1,5 @@
using System.Globalization;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Shouldly;
using Volo.Abp.Localization.TestResources.Base.CountryNames;

5
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/tr.json

@ -1,6 +1,7 @@
{
{
"culture": "tr",
"texts": {
"USA": "Amerika Birleşik Devletleri"
"USA": "Amerika Birleşik Devletleri",
"Brazil": "Brezilya"
}
}

7
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/tr.json

@ -0,0 +1,7 @@
{
"culture": "tr",
"texts": {
"ThisFieldIsRequired": "Bu alan zorunludur",
"MaxLenghtErrorMessage": "Bu alan maksimum '{0}' karakter olabilir"
}
}

3
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/tr.json

@ -1,9 +1,10 @@
{
{
"culture": "tr",
"texts": {
"Hello <b>{0}</b>.": "Merhaba <b>{0}</b>.",
"Car": "Araba",
"CarPlural": "Araba",
"MaxLenghtErrorMessage": "Bu alanın uzunluğu maksimum '{0}' karakter olabilir",
"Universe": "Evren"
}
}

6
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/tr.json

@ -0,0 +1,6 @@
{
"culture": "tr",
"texts": {
}
}

39
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/EntityChangeEvents_Tests.cs

@ -84,5 +84,44 @@ namespace Volo.Abp.TestApp.Testing
createdEventTriggered.ShouldBeTrue();
createdEtoTriggered.ShouldBeTrue();
}
[Fact]
public async Task Multiple_Update_Should_Result_With_Single_Updated_Event_In_The_Same_Uow()
{
var personId = Guid.NewGuid();
await PersonRepository.InsertAsync(new Person(personId, Guid.NewGuid().ToString("D"), 42));
var updateEventCount = 0;
var updatedAge = 0;
DistributedEventBus.Subscribe<EntityUpdatedEto<PersonEto>>(eto =>
{
updateEventCount++;
updatedAge = eto.Entity.Age;
return Task.CompletedTask;
});
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin())
{
var person = await PersonRepository.GetAsync(personId);
person.Age = 43;
await PersonRepository.UpdateAsync(person, autoSave: true);
updateEventCount.ShouldBe(0);
person.Age = 44;
await PersonRepository.UpdateAsync(person, autoSave: true);
updateEventCount.ShouldBe(0);
person.Age = 45;
await PersonRepository.UpdateAsync(person, autoSave: true);
updateEventCount.ShouldBe(0);
await uow.CompleteAsync();
}
updateEventCount.ShouldBe(1);
updatedAge.ShouldBe(45);
}
}
}

10
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json

@ -1,4 +1,4 @@
{
{
"culture": "tr",
"texts": {
"UserName": "Kullanıcı adı",
@ -11,10 +11,12 @@
"InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!",
"LoginIsNotAllowed": "Giriş yapamazsınız! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.",
"SelfRegistrationDisabledMessage": "Bu uygulama için kullanıcıların kendi kendilerine kaydolmaları engellenmiştir. Yeni bir kullanıcı kaydetmek için lütfen uygulama yöneticisi ile iletişime geçin.",
"LocalLoginDisabledMessage": "Bu uygulama için local login devre dışı bırakılmıştır.",
"Login": "Giriş yap",
"Cancel": "İptal",
"Register": "Kayıt ol",
"AreYouANewUser": "Yeni bir kullanıcı mısınız?",
"AlreadyRegistered": "Zaten kayıtlı mı?",
"InvalidLoginRequest": "Başarısız giriş isteği",
"ThereAreNoLoginSchemesConfiguredForThisClient": "Bu client için konfigüre edilmiş giriş şeması bulunamadı.",
"LogInUsingYourProviderAccount": "{0} hesabınızla giriş yapın.",
@ -34,6 +36,10 @@
"PasswordChanged": "Şifre değiştirildi",
"NewPasswordConfirmFailed": "Lütfen yeni şifreyi onaylayın.",
"Manage": "Manage",
"ManageYourProfile": "Profilinizi yönetin"
"ManageYourProfile": "Profilinizi yönetin",
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "self-registration etkin mi ?",
"Description:Abp.Account.IsSelfRegistrationEnabled": "Bir kullanıcının hesabı kendisi tarafından kaydedip kaydedememesidir.",
"DisplayName:Abp.Account.EnableLocalLogin": "Yerel bir hesapla kimlik doğrulaması",
"Description:Abp.Account.EnableLocalLogin": "Sunucunun, kullanıcıların yerel bir hesapla kimlik doğrulamasına izin verip vermeyeceğini belirtir."
}
}

4
modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/Models/UserLoginInfo.cs

@ -17,7 +17,5 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers.Models
public string Password { get; set; }
public bool RememberMe { get; set; }
public Guid? TenanId { get; set; }
}
}
}

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

@ -1,4 +1,4 @@
{
{
"culture": "tr",
"texts": {
"Menu:Blogs": "Bloglar",
@ -20,7 +20,6 @@
"SeeAll": "Hepsini Gör",
"PopularTags": "Popüler Etiketler",
"WiewsWithCount": "{0} görüntüleme",
"ShareOnTwitter": "Twitter'da paylaş",
"LastPosts": "Son Yazılar",
"LeaveComment": "Yorum Bırak",
"TagsInThisArticle": "Makalenin Etiketleri",
@ -33,6 +32,7 @@
"AreYouSure": "Emin misiniz?",
"CommentWithCount": "{0} yorum",
"Comment": "Yorum",
"ShareOnTwitter": "Twitter'da paylaş",
"CoverImage": "Kapak resmi",
"CreateANewPost": "Yeni Yazı oluştur",
"CreateANewBlog": "Yeni Blog Ekle",
@ -43,6 +43,7 @@
"Description": "Açıklama",
"Blogs": "Bloglar",
"Tags": "Etiketler",
"ShareOn": "Paylaş"
"ShareOn": "Paylaş",
"TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!"
}
}

3
modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/tr.json

@ -1,6 +1,7 @@
{
{
"culture": "tr",
"texts": {
"DocsTitle": "VoloDocs",
"WelcomeVoloDocs": "VoloDocs Hoşgeldiniz!",
"NoProjectWarning": "Henüz bir proje yok!",
"CreateYourFirstProject": "İlk projenizi oluşturmak için tıklayın",

16
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/tr.json

@ -1,4 +1,4 @@
{
{
"culture": "tr",
"texts": {
"Permission:DocumentManagement": "Döküman yönetimi",
@ -7,6 +7,7 @@
"Permission:Delete": "Sil",
"Permission:Create": "Oluştur",
"Permission:Documents": "Döküman",
"Menu:Documents": "Dokümanlar",
"Menu:DocumentManagement": "Dökümanlar",
"Menu:ProjectManagement": "Projeler",
"CreateANewProject": "Yeni proje oluştur",
@ -29,8 +30,19 @@
"DisplayName:LatestVersionBranchName": "Son versiyon Branch adı",
"DisplayName:GitHubRootUrl": "GitHub kök adresi",
"DisplayName:GitHubAccessToken": "GitHub erişim token",
"DisplayName:GitHubUserAgent": "GitHub kullanıcı temsilcisi",
"DisplayName:All": "Çekme bütün",
"DisplayName:LanguageCode": "Dil kodu",
"DisplayName:Version": "versiyon"
"DisplayName:Version": "versiyon",
"Documents": "Dokümanlar",
"RemoveFromCache": "Önbellekten kaldır",
"Reindex": "Yeniden İndeksle",
"ReindexCompleted": "Yeniden indeksleme tamamlandı",
"RemovedFromCache": "Önbellekten kaldırıldı",
"RemoveFromCacheConfirmation": "Bu maddeyi önbellekten kaldırmak istediğiniz emin misiniz?",
"ReIndexDocumentConfirmation": "Bu maddeyi yeniden indekslemek istediğinize emin misiniz?",
"DeleteDocumentFromDbConfirmation": "Bu maddeyi veritabanından silmek istediğinize emin misiniz?",
"DeleteFromDatabase": "Veritabanından sil",
"Deleted": "Silindi"
}
}

17
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/tr.json

@ -1,4 +1,4 @@
{
{
"culture": "tr",
"texts": {
"Documents": "Dökümanlar",
@ -9,17 +9,30 @@
"Edit": "Düzenle",
"LastEditTime": "Son Düzenleme",
"Delete": "Sil",
"ClearCache": "Önbelleği temizle",
"ClearCacheConfirmationMessage": "\"{0}\" Projesinin tüm önbelliğini temizlemek istediğinize emin misiniz? ",
"ReIndexAllProjects": "Tüm projeleri yeniden indexle",
"ReIndexProject": "Projeyi yeniden indeksle",
"ReIndexProjectConfirmationMessage": "\"{0}\" Projesini yeniden insdekslemek istediğinize emin misiniz?",
"SuccessfullyReIndexProject": "\"{0}\" Projesi başarıyla yeniden indekslendi",
"ReIndexAllProjectConfirmationMessage": "Tüm projeleri yeniden indekslemek istediğinize emin misiniz?",
"SuccessfullyReIndexAllProject": "Tüm projeler başarıyla yeniden indekslendi",
"InThisDocument": "Bu dökümanda",
"GoToTop": "En üste çık",
"Projects": "Proje(ler)",
"NoProjectWarning": "Hiç proje yok!",
"DocumentNotFound": "Aradığınız döküman bulunamadı!",
"ProjectNotFound": "Talep edilen proje bulunamadı!",
"NavigationDocumentNotFound": "Bu döküman için menü bulunamadı!",
"DocumentNotFoundInSelectedLanguage": "İstediğiniz dilde belge bulunamadı. Varsayılan dilde belge gösterilir.",
"FilterTopics": "Konuları Filtrele",
"FullSearch": "Dokümanlarda Ara",
"Volo.Docs.Domain:010001": "Elastic search etkin değil",
"MultipleVersionDocumentInfo": "Bu dökümanın birden çok versiyonu bulunmaktadır. Sizin için en uygun olan seçenekleri seçiniz.",
"New": "Yeni",
"Upd": "Günc",
"NewExplanation": "Son iki hafta içinde oluşturuldu.",
"UpdatedExplanation": "Son iki hafta içinde güncellendi."
"UpdatedExplanation": "Son iki hafta içinde güncellendi.",
"Volo.Docs.Domain:010002": "ShortName {ShortName} zaten var."
}
}

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

@ -131,10 +131,10 @@
<div class="version-select">
<div class="input-group">
@*<div class="input-group-prepend">
<label class="input-group-text">
<i class="fa fa-globe" aria-hidden="true" data-toggle="tooltip" title="@L["Language"]"></i>
</label>
</div>*@
<label class="input-group-text">
<i class="fa fa-globe" aria-hidden="true" data-toggle="tooltip" title="@L["Language"]"></i>
</label>
</div>*@
<select asp-items="Model.LanguageSelectListItems"
class="form-control"
onchange="window.location.replace(this.value)">
@ -163,25 +163,6 @@
</div>
</div>
@if (Model.FullSearchEnabled)
{
<div class="docs-version mt-2 mb-4">
<div class="version-select">
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text"><i class="fa fa-search"></i></label>
</div>
<input class="form-control"
id="fullsearch"
type="search"
data-fullsearch-url="/search/@Model.LanguageCode/@Model.ProjectName/@Model.Version/"
placeholder="@L["FullSearch"].Value"
aria-label="Filter">
</div>
</div>
</div>
}
@if (Model.Navigation == null || !Model.Navigation.HasChildItems)
{
@ -209,48 +190,60 @@
{
<div class="col-md-7 docs-content bg-white">
<div class="docs-link-btns">
<div class="float-left">
@(L["ShareOn"].Value + " :")
<a href="#" target="_blank" class="share-button twitter" id="TwitterShareLink" title="Twitter">
<i class="fa fa-twitter"></i>
</a>
<a href="#" target="_blank" class="share-button linkedin" id="LinkedinShareLink" title="LinkedIn">
<i class="fa fa-linkedin"></i>
</a>
<a href="#" target="_blank" class="share-button email" id="EmailShareLink" title="E-mail">
<i class="fa fa-envelope-square"></i>
</a>
</div>
<div class="float-right">
@if (!string.IsNullOrEmpty(Model.Document.EditLink))
{
<a href="@Model.Document.EditLink" target="_blank">
<i class="fa fa-edit"></i>
@(L["Edit"])
<span class="for-desktop">(@L["LastEditTime"]: @Model.Document.LastUpdatedTime.ToShortDateString())</span>
</a>
}
</div>
<div class="row">
<div class="col">
@if (Model.FullSearchEnabled)
{
<div class="search-area">
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text"><i class="fa fa-search"></i></label>
</div>
<div class="float-right mr-3">
@if (Model.Document.Contributors != null && Model.Document.Contributors.Count > 0)
{
<span class="for-desktop">
@(L["Contributors"].Value + " :") </span>
@foreach (var contributor in Model.Document.Contributors)
<input class="form-control"
id="fullsearch"
type="search"
data-fullsearch-url="/search/@Model.LanguageCode/@Model.ProjectName/@Model.Version/"
placeholder="@L["FullSearch"].Value"
aria-label="Filter">
</div>
</div>
}
</div>
<div class="col-auto">
<div class="cont-container">
@if (Model.Document.Contributors != null && Model.Document.Contributors.Count > 0)
{
<span class="for-desktop mr-3">
@(L["Contributors"].Value)
</span>
@foreach (var contributor in Model.Document.Contributors)
{
<a href="@contributor.UserProfileUrl" target="_blank" class="cont-avatar">
<img src="@contributor.AvatarUrl"
class="rounded-circle"
alt="Avatar"
height="21"
width="21"
title="@contributor.Username" />
</a>
}
}
</div>
</div>
<div class="col-auto">
@if (!string.IsNullOrEmpty(Model.Document.EditLink))
{
<a href="@contributor.UserProfileUrl" target="_blank">
<img src="@contributor.AvatarUrl"
class="rounded-circle"
alt="Avatar"
height="21"
width="21"
title="@contributor.Username" />
<a href="@Model.Document.EditLink" target="_blank">
<i class="fa fa-edit"></i>
@(L["Edit"])
<span class="for-desktop text-muted">@L["LastEditTime"]: @Model.Document.LastUpdatedTime.ToShortDateString()</span>
</a>
}
}
</div>
</div>
</div>
</div>
@ -318,8 +311,20 @@
</div>
</div>
</div>
<!-- Right Bar -->
<div class="col-md-2 docs-page-index position-relative bg-light">
<div class="doc-social-btns">
@(L["ShareOn"].Value + " :")
<a href="#" target="_blank" class="share-button twitter" id="TwitterShareLink" title="Twitter">
<i class="fa fa-twitter"></i>
</a>
<a href="#" target="_blank" class="share-button linkedin" id="LinkedinShareLink" title="LinkedIn">
<i class="fa fa-linkedin"></i>
</a>
<a href="#" target="_blank" class="share-button email" id="EmailShareLink" title="E-mail">
<i class="fa fa-envelope-square"></i>
</a>
</div>
<div id="scroll-index" class="docs-inner-anchors mt-2">
<h5>@L["InThisDocument"]</h5>
<nav id="docs-sticky-index" class="navbar index-scroll">
@ -350,17 +355,17 @@
else
{
if (!Model.ProjectFound)
{
@(await Component.InvokeAsync<ErrorViewComponent>(new
{
model = new ErrorPageModel
{
RedirectUrl = Model.DocumentsUrlPrefix,
ErrorCode = "404",
ErrorMessage = L.GetString("ProjectNotFound")
}
}))
}
{
@(await Component.InvokeAsync<ErrorViewComponent>(new
{
model = new ErrorPageModel
{
RedirectUrl = Model.DocumentsUrlPrefix,
ErrorCode = "404",
ErrorMessage = L.GetString("ProjectNotFound")
}
}))
}
if (!Model.DocumentFound)
{
@ -370,9 +375,9 @@ else
{
RedirectUrl = Model.DocumentsUrlPrefix + Model.LanguageCode + "/" + Model.ProjectName + "/"
+ (Model.LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Model.Version),
ErrorCode = "404",
ErrorCode = "404",
ErrorMessage = L.GetString("DocumentNotFound"),
AutoRedirect = !Model.DocumentName.IsNullOrWhiteSpace()
AutoRedirect = !Model.DocumentName.IsNullOrWhiteSpace()
}
}))
}

6
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js

@ -109,11 +109,13 @@
var wWidth = $(window).width();
if (wWidth > 766) {
$("#sidebar-scroll").mCustomScrollbar({
theme: "minimal"
theme: "minimal",
alwaysShowScrollbar: 0,
});
$("#scroll-index").mCustomScrollbar({
theme: "minimal-dark"
theme: "minimal-dark",
alwaysShowScrollbar: 0,
});
}
}

89
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css

@ -65,10 +65,12 @@
height: 100vh; }
.docs-page .docs-sidebar .docs-sidebar-wrapper .docs-tree-list > ul {
display: block;
height: calc(100vh - 320px);
overflow-y: auto; }
height: calc(100vh - 230px);
overflow-y: auto;
margin-right: 12px !important;
margin-top: 20px !important; }
.docs-page .docs-sidebar .docs-sidebar-wrapper .docs-tree-list ul {
font-size: .935em;
font-size: .9em;
list-style: none;
padding: 0 1rem;
margin: 0; }
@ -179,26 +181,34 @@
.docs-page .docs-content .contributors a:hover {
text-decoration: none; }
.docs-page .docs-content .docs-link-btns {
border-bottom: 1px solid #f4f5f7;
background: #fdfdfd;
padding: 10px 20px;
background: #f5f7fa;
padding: 15px 30px;
margin: 0 -15px;
text-align: right;
font-size: .8em;
min-height: 40px; }
min-height: 48px; }
.docs-page .docs-content .docs-link-btns a {
color: #999; }
color: #222; }
.docs-page .docs-content .docs-link-btns a .text-muted {
color: #ccc !important; }
.docs-page .docs-content .docs-link-btns a:hover {
color: #444;
color: #000;
text-decoration: none; }
.docs-page .docs-content .docs-link-btns .twitter {
color: #00ACEE; }
.docs-page .docs-content .docs-link-btns .linkedin {
color: #0077B5; }
.docs-page .docs-content .docs-link-btns .email {
color: #5a5a5a; }
.docs-page .docs-content .docs-link-btns .share-button {
margin-left: 10px; }
.docs-page .docs-content .docs-link-btns .search-area {
margin: -5px 0 -5px -10px;
box-shadow: 0 0 10px #e8e8e8; }
.docs-page .docs-content .docs-link-btns .search-area .input-group-text {
background: white;
border: 0;
color: #000;
font-size: 1em; }
.docs-page .docs-content .docs-link-btns .search-area .form-control {
background: #fff;
border: 0;
font-size: 1em;
padding-left: 0;
outline: 0;
box-shadow: none; }
.docs-page .docs-content .docs-text-field {
padding: 2rem; }
.docs-page .docs-content article.docs-body {
@ -259,13 +269,46 @@
background-color: #f6f8fa; }
.docs-page .docs-content article.docs-body table img {
background-color: initial; }
.docs-page .doc-social-btns {
margin: 0 -15px 0;
font-size: .8em;
background: #e9ecf0;
height: 53px;
padding: 15px 20px;
position: fixed;
top: 0px;
width: 100%; }
.docs-page .doc-social-btns a:hover {
text-decoration: none; }
.docs-page .doc-social-btns .twitter {
color: #00ACEE; }
.docs-page .doc-social-btns .linkedin {
color: #0077B5; }
.docs-page .doc-social-btns .email {
color: #5a5a5a; }
.docs-page .doc-social-btns .share-button {
margin-left: 10px; }
.docs-page .cont-container a.cont-avatar {
position: relative; }
.docs-page .cont-container a.cont-avatar img {
width: 24px;
height: 24px;
box-shadow: 0 0 8px #c1bbbb;
border: 2px solid #fff;
margin-left: -10px;
display: inline-block;
margin-top: -2px;
transition: .2s; }
.docs-page .cont-container:hover a.cont-avatar img {
margin-left: -2px; }
.docs-page .docs-page-index {
min-height: 96vh; }
min-height: 90vh;
background-color: #f5f7fa !important; }
.docs-page .docs-page-index #scroll-index {
max-height: 96vh; }
max-height: 90vh; }
.docs-page .docs-page-index .docs-inner-anchors {
position: fixed;
top: 0px;
top: 50px;
padding: 10px;
font-size: .90em; }
.docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills {
@ -617,3 +660,9 @@ div.code-toolbar > .toolbar a {
margin-left: -36px; } }
.scrolledMore .alert-criteria p.alert-p {
display: none; }
.mCS-autoHide > .mCustomScrollBox > .mCSB_scrollTools,
.mCS-autoHide > .mCustomScrollBox ~ .mCSB_scrollTools {
opacity: 1 !important;
filter: "alpha(opacity=1)";
-ms-filter: "alpha(opacity=1)"; }

2
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.min.css

File diff suppressed because one or more lines are too long

131
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss

@ -26,7 +26,7 @@ body {
left: 0;
position: fixed;
background: #1d1d1d;
.input-group {
border-radius: 5px;
overflow: hidden;
@ -103,12 +103,14 @@ body {
> ul {
display: block;
height: calc(100vh - 320px);
height: calc(100vh - 230px);
overflow-y: auto;
margin-right: 12px !important;
margin-top: 20px !important;
}
ul {
font-size: .935em;
font-size: .9em;
list-style: none;
padding: 0 1rem;
margin: 0;
@ -298,37 +300,45 @@ body {
}
.docs-link-btns {
border-bottom: 1px solid #f4f5f7;
background: #fdfdfd;
padding: 10px 20px;
background: #f5f7fa;
padding: 15px 30px;
margin: 0 -15px;
text-align: right;
font-size: .8em;
min-height: 40px;
min-height: 48px;
a {
color: #999;
color: #222;
.text-muted {
color: #ccc !important;
}
&:hover {
color: #444;
color: #000;
text-decoration: none;
}
}
.twitter {
color: #00ACEE;
}
.linkedin {
color: #0077B5;
}
.search-area {
margin: -5px 0 -5px -10px;
box-shadow: 0 0 10px #e8e8e8;
.email {
color: #5a5a5a;
}
.input-group-text {
background: white;
border: 0;
color: #000;
font-size: 1em;
}
.share-button {
margin-left: 10px;
.form-control {
background: #fff;
border: 0;
font-size: 1em;
padding-left: 0;
outline: 0;
box-shadow: none;
}
}
}
@ -432,16 +442,73 @@ body {
}
}
.doc-social-btns {
margin: 0 -15px 0;
font-size: .8em;
background: #e9ecf0;
height: 53px;
padding: 15px 20px;
position: fixed;
top: 0px;
width: 100%;
a:hover {
text-decoration: none;
}
.twitter {
color: #00ACEE;
}
.linkedin {
color: #0077B5;
}
.email {
color: #5a5a5a;
}
.share-button {
margin-left: 10px;
}
}
.cont-container {
a.cont-avatar {
position: relative;
img {
width: 24px;
height: 24px;
box-shadow: 0 0 8px #c1bbbb;
border: 2px solid #fff;
margin-left: -10px;
display: inline-block;
margin-top: -2px;
transition: .2s;
}
}
&:hover {
a.cont-avatar {
img {
margin-left: -2px;
}
}
}
}
.docs-page-index {
min-height: 96vh;
min-height: 90vh;
background-color: #f5f7fa !important;
#scroll-index {
max-height: 96vh;
max-height: 90vh;
}
.docs-inner-anchors {
position: fixed;
top: 0px;
top: 50px;
padding: 10px;
font-size: .90em;
@ -517,11 +584,11 @@ body {
.docs-page {
.docs-sidebar {
.docs-sidebar {
.docs-sidebar-wrapper {
.docs-version {
.version-select {
.version-select {
select.form-control, input.form-control {
background: #000000;
@ -696,7 +763,6 @@ body {
.container {
max-width: 1080px;
}
}
@media (min-width: 1366px) {
@ -960,7 +1026,7 @@ div.code-toolbar > .toolbar a {
@media screen and (max-width: 767px) {
.navbar-logo {
.navbar-brand {
display: inline-block;
display: inline-block;
margin: 0 auto !important;
max-width: 70%;
overflow: hidden;
@ -1012,6 +1078,7 @@ div.code-toolbar > .toolbar a {
}
}
}
.scrolledMore {
padding-top: 107px;
@ -1034,4 +1101,10 @@ div.code-toolbar > .toolbar a {
}
}
}
.mCS-autoHide > .mCustomScrollBox > .mCSB_scrollTools,
.mCS-autoHide > .mCustomScrollBox ~ .mCSB_scrollTools {
opacity: 1 !important;
filter: "alpha(opacity=1)";
-ms-filter: "alpha(opacity=1)";
}

14
modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs

@ -9,13 +9,17 @@ namespace Volo.Abp.Identity.AspNetCore
)]
public class AbpIdentityAspNetCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services
.GetObject<IdentityBuilder>()
.AddDefaultTokenProviders()
.AddSignInManager();
PreConfigure<IdentityBuilder>(builder =>
{
builder.AddDefaultTokenProviders();
builder.AddSignInManager();
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
//(TODO: Extract an extension method like IdentityBuilder.AddAbpSecurityStampValidator())
context.Services.AddScoped<AbpSecurityStampValidator>();
context.Services.AddScoped(typeof(SecurityStampValidator<IdentityUser>), provider => provider.GetService(typeof(AbpSecurityStampValidator)));

12
modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/tr.json

@ -1,6 +1,14 @@
{
"culture": "tr",
"texts": {
"VirtualFileExplorer": "Sanal dosya gezgini",
"VirtualFileType": "Sanal dosya tipi",
"Menu:VirtualFileExplorer": "Sanal dosya gezgini ",
"LastUpdateTime": "Son güncelleme zamanı",
"VirtualFileName": "Sanal dosya adı",
"FileContent": "Dosyaa içeriği",
"Size": "Boyut",
"BackToRoot": "Kök'e dön",
"EmptyFileInfoList": "Sanal dosyalar yok"
}
}
}

10
modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hant.json

@ -1,6 +1,14 @@
{
"culture": "zh-Hant",
"texts": {
"VirtualFileExplorer" : "虛擬檔案總管",
"VirtualFileType" : "虛擬檔案類型",
"Menu:VirtualFileExplorer" : "虛擬檔案瀏覽器",
"LastUpdateTime" : "最後更新時間",
"VirtualFileName" : "虛擬檔案名稱",
"FileContent" : "檔案內容",
"Size" : "檔案大小",
"BackToRoot" : "返回根目錄",
"EmptyFileInfoList" : "無任何虛擬檔案"
}
}

14
npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts

@ -6,7 +6,7 @@ import { ABP } from '../models';
import { ReplaceableComponents } from '../models/replaceable-components';
import { RoutesService } from '../services/routes.service';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
import { getRoutePath } from '../utils/route-utils';
import { findRoute, getRoutePath } from '../utils/route-utils';
import { takeUntilDestroy } from '../utils/rxjs-utils';
import { TreeNode } from '../utils/tree-utils';
@ -25,14 +25,13 @@ export class DynamicLayoutComponent implements OnDestroy {
constructor(
injector: Injector,
private route: ActivatedRoute,
private routes: RoutesService,
private store: Store,
@Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent,
) {
if (dynamicLayoutComponent) return;
const route = injector.get(ActivatedRoute);
const router = injector.get(Router);
const routes = injector.get(RoutesService);
const layouts = {
application: this.getComponent('Theme.ApplicationLayoutComponent'),
account: this.getComponent('Theme.AccountLayoutComponent'),
@ -41,11 +40,12 @@ export class DynamicLayoutComponent implements OnDestroy {
router.events.pipe(takeUntilDestroy(this)).subscribe(event => {
if (event instanceof NavigationEnd) {
let expectedLayout = (this.route.snapshot.data || {}).layout;
const path = getRoutePath(router);
let expectedLayout = (route.snapshot.data || {}).layout;
if (!expectedLayout) {
let node = { parent: this.routes.search({ path }) } as TreeNode<ABP.Route>;
let node = findRoute(routes, getRoutePath(router));
node = { parent: node } as TreeNode<ABP.Route>;
while (node.parent) {
node = node.parent;

7
npm/ng-packs/packages/core/src/lib/core.module.ts

@ -21,6 +21,7 @@ import { PermissionDirective } from './directives/permission.directive';
import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive';
import { StopPropagationDirective } from './directives/stop-propagation.directive';
import { VisibilityDirective } from './directives/visibility.directive';
import { RoutesHandler } from './handlers/routes.handler';
import { ApiInterceptor } from './interceptors/api.interceptor';
import { LocalizationModule } from './localization.module';
import { ABP } from './models/common';
@ -196,6 +197,12 @@ export class CoreModule {
deps: [LocalizationService],
useFactory: noop,
},
{
provide: APP_INITIALIZER,
multi: true,
deps: [RoutesHandler],
useFactory: noop,
},
{ provide: OAuthStorage, useFactory: storageFactory },
],
};

1
npm/ng-packs/packages/core/src/lib/handlers/index.ts

@ -0,0 +1 @@
export * from './routes.handler';

45
npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts

@ -0,0 +1,45 @@
import { Injectable, Optional } from '@angular/core';
import { Router } from '@angular/router';
import { ABP } from '../models';
import { RoutesService } from '../services/routes.service';
@Injectable({
providedIn: 'root',
})
export class RoutesHandler {
constructor(private routes: RoutesService, @Optional() private router: Router) {
this.addRoutes();
}
addRoutes() {
this.router?.config?.forEach(({ path = '', data }) => {
if (!data?.routes) return;
if (Array.isArray(data.routes)) {
this.routes.add(data.routes);
return;
}
const routes = flatRoutes([{ ...data.routes, path }], { path: '' });
this.routes.add(routes);
});
}
}
function flatRoutes(routes: RouteDef[], parent: any) {
if (!routes) return [];
return routes.reduce((acc, route) => {
const current = {
...route,
parentName: parent.name,
path: parent.path + '/' + route.path,
};
acc.push(current, ...flatRoutes(current.children, current));
return acc;
}, []);
}
type RouteDef = ABP.Route & { children: RouteDef[] };

9
npm/ng-packs/packages/core/src/lib/models/common.ts

@ -30,19 +30,16 @@ export namespace ABP {
maxResultCount?: number;
}
export interface Node {
export interface Nav {
name: string;
parentName?: string;
requiredPolicy?: string;
order?: number;
invisible?: boolean;
}
export interface Nav extends Node {
path: string;
requiredPolicy?: string;
}
export interface Route extends Nav {
path: string;
layout?: eLayoutType;
iconClass?: string;
}

67
npm/ng-packs/packages/core/src/lib/services/routes.service.ts

@ -1,8 +1,10 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { Injectable, OnDestroy } from '@angular/core';
import { Actions, ofActionSuccessful, Store } from '@ngxs/store';
import { BehaviorSubject, Observable } from 'rxjs';
import { GetAppConfiguration } from '../actions/config.actions';
import { ABP } from '../models/common';
import { ConfigState } from '../states/config.state';
import { takeUntilDestroy } from '../utils/rxjs-utils';
import { pushValueTo } from '../utils/array-utils';
import { BaseTreeNode, createTreeFromList, TreeNode } from '../utils/tree-utils';
@ -75,6 +77,13 @@ export abstract class AbstractTreeService<T extends object> {
return this.publish(flatItems, visibleItems);
}
find(predicate: (item: TreeNode<T>) => boolean, tree = this.tree): TreeNode<T> | null {
return tree.reduce(
(acc, node) => (acc ? acc : predicate(node) ? node : this.find(predicate, node.children)),
null,
);
}
patch(identifier: string, props: Partial<T>): T[] | false {
const flatItems = this._flat$.value;
const index = flatItems.findIndex(item => item[this.id] === identifier);
@ -88,6 +97,10 @@ export abstract class AbstractTreeService<T extends object> {
return this.publish(flatItems, visibleItems);
}
refresh(): T[] {
return this.add([]);
}
remove(identifiers: string[]): T[] {
const set = new Set<string>();
identifiers.forEach(id => set.add(id));
@ -98,7 +111,7 @@ export abstract class AbstractTreeService<T extends object> {
return this.publish(flatItems, visibleItems);
}
search(params: Partial<T>, tree = this.tree): TreeNode<T> {
search(params: Partial<T>, tree = this.tree): TreeNode<T> | null {
const searchKeys = Object.keys(params);
return tree.reduce(
@ -107,38 +120,42 @@ export abstract class AbstractTreeService<T extends object> {
? acc
: searchKeys.every(key => node[key] === params[key])
? node
: node.children
? this.search(params, node.children)
: acc,
: this.search(params, node.children),
null,
);
}
}
@Injectable({
providedIn: 'root',
})
export class RoutesService extends AbstractTreeService<ABP.Route> {
export abstract class AbstractNavTreeService<T extends ABP.Nav> extends AbstractTreeService<T>
implements OnDestroy {
readonly id = 'name';
readonly parentId = 'parentName';
readonly hide = (item: ABP.Route) => item.invisible;
readonly sort = (a: ABP.Route, b: ABP.Route) => a.order - b.order;
}
readonly hide = (item: T) => item.invisible || !this.isGranted(item);
readonly sort = (a: T, b: T) => a.order - b.order;
@Injectable({
providedIn: 'root',
})
export class SettingTabsService extends AbstractTreeService<ABP.Tab> {
readonly id = 'name';
readonly parentId = 'parentName';
readonly hide = (setting: ABP.Tab) => setting.invisible || !this.isGranted(setting);
readonly sort = (a: ABP.Tab, b: ABP.Tab) => a.order - b.order;
constructor(private store: Store) {
constructor(protected actions: Actions, protected store: Store) {
super();
this.actions
.pipe(takeUntilDestroy(this), ofActionSuccessful(GetAppConfiguration))
.subscribe(() => this.refresh());
}
protected isGranted({ requiredPolicy }: T): boolean {
return this.store.selectSnapshot(ConfigState.getGrantedPolicy(requiredPolicy));
}
private isGranted(setting: ABP.Tab): boolean {
return this.store.selectSnapshot(ConfigState.getGrantedPolicy(setting.requiredPolicy));
hasInvisibleChild(identifier: string): boolean {
const node = this.find(item => item[this.id] === identifier);
return node?.children?.some(child => child.invisible);
}
/* istanbul ignore next */
ngOnDestroy() {}
}
@Injectable({ providedIn: 'root' })
export class RoutesService extends AbstractNavTreeService<ABP.Route> {}
@Injectable({ providedIn: 'root' })
export class SettingTabsService extends AbstractNavTreeService<ABP.Tab> {}

15
npm/ng-packs/packages/core/src/lib/states/config.state.ts

@ -93,23 +93,24 @@ export class ConfigState {
static getGrantedPolicy(key: string) {
const selector = createSelector([ConfigState], (state: Config.State): boolean => {
if (!key) return true;
const getPolicy = k => snq(() => state.auth.grantedPolicies[k], false);
const getPolicy = (k: string) => snq(() => state.auth.grantedPolicies[k], false);
const orRegexp = /\|\|/g;
const andRegexp = /&&/g;
// TODO: Allow combination of ANDs & ORs
if (orRegexp.test(key)) {
const keys = key.split('||').filter(k => !!k);
const keys = key.split('||').filter(Boolean);
if (keys.length !== 2) return false;
if (keys.length < 2) return false;
return getPolicy(keys[0].trim()) || getPolicy(keys[1].trim());
return keys.some(k => getPolicy(k.trim()));
} else if (andRegexp.test(key)) {
const keys = key.split('&&').filter(k => !!k);
const keys = key.split('&&').filter(Boolean);
if (keys.length !== 2) return false;
if (keys.length < 2) return false;
return getPolicy(keys[0].trim()) && getPolicy(keys[1].trim());
return keys.every(k => getPolicy(k.trim()));
}
return getPolicy(key);

16
npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts

@ -2,7 +2,8 @@ import { HttpClient } from '@angular/common/http';
import { Component, NgModule } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
import { NgxsModule, Store } from '@ngxs/store';
import { Actions, NgxsModule, Store } from '@ngxs/store';
import { NEVER } from 'rxjs';
import { DynamicLayoutComponent, RouterOutletComponent } from '../components';
import { eLayoutType } from '../enums/common';
import { ABP } from '../models';
@ -97,11 +98,24 @@ const storeData = {
};
describe('DynamicLayoutComponent', () => {
const mockActions: Actions = NEVER;
const mockStore = ({
selectSnapshot() {
return true;
},
} as unknown) as Store;
const createComponent = createRoutingFactory({
component: RouterOutletComponent,
stubsEnabled: false,
declarations: [DummyComponent, DynamicLayoutComponent],
mocks: [ApplicationConfigurationService, HttpClient],
providers: [
{
provide: RoutesService,
useFactory: () => new RoutesService(mockActions, mockStore),
},
],
imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot([ReplaceableComponentsState])],
routes: [
{ path: '', component: RouterOutletComponent },

24
npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts

@ -2,13 +2,35 @@ import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
import { RouterOutletComponent } from '../components';
import { getRoutePath } from '../utils/route-utils';
import { RoutesService } from '../services/routes.service';
import { findRoute, getRoutePath } from '../utils/route-utils';
// tslint:disable-next-line
@Component({ template: '' })
class DummyComponent {}
describe('Route Utils', () => {
describe('#findRoute', () => {
const node = { path: '/foo' };
test.each`
path | expected | count
${'/foo/bar/baz'} | ${node} | ${3}
${'/foo/bar'} | ${node} | ${2}
${'/foo'} | ${node} | ${1}
${'/'} | ${null} | ${1}
`(
'should find $expected in $count turns when path is $path',
async ({ path, expected, count }) => {
const find = jest.fn(cb => (cb(node) ? node : null));
const routes = ({ find } as any) as RoutesService;
const route = findRoute(routes, path);
expect(route).toBe(expected);
expect(find).toHaveBeenCalledTimes(count);
},
);
});
describe('#getRoutePath', () => {
let spectator: SpectatorRouting<RouterOutletComponent>;
const createRouting = createRoutingFactory({

40
npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts

@ -0,0 +1,40 @@
import { Router } from '@angular/router';
import { RoutesHandler } from '../handlers';
import { RoutesService } from '../services';
describe('Routes Handler', () => {
describe('#add', () => {
it('should add routes from router config', () => {
const config = [
{ path: 'x' },
{ path: 'y', data: {} },
{ path: '', data: { routes: { name: 'Foo' } } },
{ path: 'bar', data: { routes: { name: 'Bar' } } },
{ data: { routes: [{ path: '/baz', name: 'Baz' }] } },
];
const foo = [{ path: '/', name: 'Foo' }];
const bar = [{ path: '/bar', name: 'Bar' }];
const baz = [{ path: '/baz', name: 'Baz' }];
const routes = [];
const add = jest.fn(routes.push.bind(routes));
const mockRoutesService = ({ add } as unknown) as RoutesService;
const mockRouter = ({ config } as unknown) as Router;
const handler = new RoutesHandler(mockRoutesService, mockRouter);
expect(add).toHaveBeenCalledTimes(3);
expect(routes).toEqual([foo, bar, baz]);
});
it('should not add routes when there is no router', () => {
const routes = [];
const add = jest.fn(routes.push.bind(routes));
const mockRoutesService = ({ add } as unknown) as RoutesService;
const handler = new RoutesHandler(mockRoutesService, null);
expect(add).not.toHaveBeenCalled();
});
});
});

86
npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts

@ -1,17 +1,31 @@
import { Store } from '@ngxs/store';
import { Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import { GetAppConfiguration } from '../actions';
import { RoutesService } from '../services';
const routes = [
{ path: '/foo', name: 'foo' },
{ path: '/foo/bar', name: 'bar', parentName: 'foo', invisible: true, order: 2 },
{ path: '/foo/bar/baz', name: 'baz', parentName: 'bar', order: 1 },
{ path: '/foo/x', name: 'x', parentName: 'foo', order: 1 },
];
describe('Routes Service', () => {
let service: RoutesService;
const routes = [
{ path: '/foo', name: 'foo' },
{ path: '/foo/bar', name: 'bar', parentName: 'foo', invisible: true, order: 2 },
{ path: '/foo/bar/baz', name: 'baz', parentName: 'bar', order: 1 },
{ path: '/foo/x', name: 'x', parentName: 'foo', order: 1 },
];
const mockActions = new Subject();
const mockStore = ({
selectSnapshot() {
return true;
},
} as unknown) as Store;
beforeEach(() => {
service = new RoutesService(mockActions, mockStore);
});
describe('#add', () => {
it('should add given routes as flat$, tree$, and visible$', async () => {
const service = new RoutesService();
service.add(routes);
const flat = await service.flat$.pipe(take(1)).toPromise();
@ -38,9 +52,34 @@ describe('Routes Service', () => {
});
});
describe('#find', () => {
it('should return node found based on query', () => {
service.add(routes);
const result = service.find(route => route.invisible);
expect(result.name).toBe('bar');
expect(result.children.length).toBe(1);
expect(result.children[0].name).toBe('baz');
});
it('should return null when query is not found', () => {
service.add(routes);
const result = service.find(route => route.requiredPolicy === 'X');
expect(result).toBe(null);
});
});
describe('#hasInvisibleChild', () => {
it('should return if node has invisible child', () => {
service.add(routes);
expect(service.hasInvisibleChild('foo')).toBe(true);
expect(service.hasInvisibleChild('bar')).toBe(false);
expect(service.hasInvisibleChild('baz')).toBe(false);
});
});
describe('#remove', () => {
it('should remove routes based on given routeNames', () => {
const service = new RoutesService();
service.add(routes);
service.remove(['bar']);
@ -66,9 +105,9 @@ describe('Routes Service', () => {
describe('#patch', () => {
it('should patch propeties of routes based on given routeNames', () => {
const service = new RoutesService();
service['isGranted'] = jest.fn(route => route.requiredPolicy !== 'X');
service.add(routes);
service.patch('x', { invisible: true });
service.patch('x', { requiredPolicy: 'X' });
const flat = service.flat;
const tree = service.tree;
@ -93,21 +132,40 @@ describe('Routes Service', () => {
});
it('should return false when route name is not found', () => {
const service = new RoutesService();
service.add(routes);
const result = service.patch('A man has no name.', { invisible: true });
expect(result).toBe(false);
});
});
describe('#refresh', () => {
it('should call add once with empty array', () => {
const add = jest.spyOn(service, 'add');
service.refresh();
expect(add).toHaveBeenCalledTimes(1);
expect(add).toHaveBeenCalledWith([]);
});
it('should be called upon successful GetAppConfiguration action', () => {
const refresh = jest.spyOn(service, 'refresh');
mockActions.next({ action: new GetAppConfiguration(), status: 'SUCCESSFUL' });
expect(refresh).toHaveBeenCalledTimes(1);
});
});
describe('#search', () => {
it('should return node found when route name is not found', () => {
const service = new RoutesService();
it('should return node found based on query', () => {
service.add(routes);
const result = service.search({ invisible: true });
expect(result.name).toBe('bar');
expect(result.children.length).toBe(1);
expect(result.children[0].name).toBe('baz');
});
it('should return null when query is not found', () => {
service.add(routes);
const result = service.search({ requiredPolicy: 'X' });
expect(result).toBe(null);
});
});
});

17
npm/ng-packs/packages/core/src/lib/utils/route-utils.ts

@ -1,4 +1,21 @@
import { PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router';
import { ABP } from '../models/common';
import { RoutesService } from '../services/routes.service';
import { TreeNode } from './tree-utils';
export function findRoute(routes: RoutesService, path: string): TreeNode<ABP.Route> {
const node = routes.find(route => route.path === path);
return node || path === '/'
? node
: findRoute(
routes,
path
.split('/')
.slice(0, -1)
.join('/'),
);
}
export function getRoutePath(router: Router) {
const emptyGroup = { segments: [] } as UrlSegmentGroup;

1
npm/ng-packs/packages/identity/config/src/enums/index.ts

@ -1 +1,2 @@
export * from './policy-names';
export * from './route-names';

5
npm/ng-packs/packages/identity/config/src/enums/policy-names.ts

@ -0,0 +1,5 @@
export const enum eIdentityPolicyNames {
IdentityManagement = 'AbpIdentity.Roles || AbpIdentity.Users',
Roles = 'AbpIdentity.Roles',
Users = 'AbpIdentity.Users',
}

6
npm/ng-packs/packages/identity/config/src/providers/route.provider.ts

@ -1,6 +1,7 @@
import { eLayoutType, RoutesService } from '@abp/ng.core';
import { eThemeSharedRouteNames } from '@abp/ng.theme.shared';
import { APP_INITIALIZER } from '@angular/core';
import { eIdentityPolicyNames } from '../enums/policy-names';
import { eIdentityRouteNames } from '../enums/route-names';
export const IDENTITY_ROUTE_PROVIDERS = [
@ -14,6 +15,7 @@ export function configureRoutes(routes: RoutesService) {
path: '/identity',
name: eIdentityRouteNames.IdentityManagement,
parentName: eThemeSharedRouteNames.Administration,
requiredPolicy: eIdentityPolicyNames.IdentityManagement,
iconClass: 'fa fa-id-card-o',
layout: eLayoutType.application,
order: 1,
@ -22,14 +24,14 @@ export function configureRoutes(routes: RoutesService) {
path: '/identity/roles',
name: eIdentityRouteNames.Roles,
parentName: eIdentityRouteNames.IdentityManagement,
requiredPolicy: 'AbpIdentity.Roles',
requiredPolicy: eIdentityPolicyNames.Roles,
order: 1,
},
{
path: '/identity/users',
name: eIdentityRouteNames.Users,
parentName: eIdentityRouteNames.IdentityManagement,
requiredPolicy: 'AbpIdentity.Users',
requiredPolicy: eIdentityPolicyNames.Users,
order: 2,
},
]);

1
npm/ng-packs/packages/setting-management/config/src/enums/index.ts

@ -1 +1,2 @@
export * from './policy-names';
export * from './route-names';

3
npm/ng-packs/packages/setting-management/config/src/enums/policy-names.ts

@ -0,0 +1,3 @@
export const enum eSettingManagementPolicyNames {
Settings = 'AbpAccount.SettingManagement',
}

5
npm/ng-packs/packages/setting-management/config/src/providers/route.provider.ts

@ -2,6 +2,7 @@ import { eLayoutType, RoutesService, SettingTabsService } from '@abp/ng.core';
import { eThemeSharedRouteNames } from '@abp/ng.theme.shared';
import { APP_INITIALIZER } from '@angular/core';
import { debounceTime, map } from 'rxjs/operators';
import { eSettingManagementPolicyNames } from '../enums/policy-names';
import { eSettingManagementRouteNames } from '../enums/route-names';
export const SETTING_MANAGEMENT_ROUTE_PROVIDERS = [
@ -21,7 +22,7 @@ export function configureRoutes(routes: RoutesService) {
name: eSettingManagementRouteNames.Settings,
path: '/setting-management',
parentName: eThemeSharedRouteNames.Administration,
requiredPolicy: 'AbpAccount.SettingManagement',
requiredPolicy: eSettingManagementPolicyNames.Settings,
layout: eLayoutType.application,
order: 6,
iconClass: 'fa fa-cog',
@ -37,6 +38,6 @@ export function hideRoutes(routes: RoutesService, tabs: SettingTabsService) {
debounceTime(0),
map(nodes => !nodes.length),
)
.subscribe(invisible => routes.patch('AbpSettingManagement::Settings', { invisible }));
.subscribe(invisible => routes.patch(eSettingManagementRouteNames.Settings, { invisible }));
};
}

1
npm/ng-packs/packages/tenant-management/config/src/enums/index.ts

@ -1 +1,2 @@
export * from './policy-names';
export * from './route-names';

4
npm/ng-packs/packages/tenant-management/config/src/enums/policy-names.ts

@ -0,0 +1,4 @@
export const enum eTenantManagementPolicyNames {
TenantManagement = 'AbpTenantManagement.Tenants',
Tenants = 'AbpTenantManagement.Tenants',
}

4
npm/ng-packs/packages/tenant-management/config/src/providers/route.provider.ts

@ -1,6 +1,7 @@
import { eLayoutType, RoutesService } from '@abp/ng.core';
import { eThemeSharedRouteNames } from '@abp/ng.theme.shared';
import { APP_INITIALIZER } from '@angular/core';
import { eTenantManagementPolicyNames } from '../enums/policy-names';
import { eTenantManagementRouteNames } from '../enums/route-names';
export const TENANT_MANAGEMENT_ROUTE_PROVIDERS = [
@ -14,6 +15,7 @@ export function configureRoutes(routes: RoutesService) {
path: '/tenant-management',
name: eTenantManagementRouteNames.TenantManagement,
parentName: eThemeSharedRouteNames.Administration,
requiredPolicy: eTenantManagementPolicyNames.TenantManagement,
layout: eLayoutType.application,
iconClass: 'fa fa-users',
order: 2,
@ -22,7 +24,7 @@ export function configureRoutes(routes: RoutesService) {
path: '/tenant-management/tenants',
name: eTenantManagementRouteNames.Tenants,
parentName: eTenantManagementRouteNames.TenantManagement,
requiredPolicy: 'AbpTenantManagement.Tenants',
requiredPolicy: eTenantManagementPolicyNames.Tenants,
order: 1,
},
]);

2
npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html

@ -1,7 +1,7 @@
<ul class="navbar-nav" *ngIf="isShow">
<ng-container
*ngFor="let route of routes.visible$ | async; trackBy: trackByFn"
[ngTemplateOutlet]="route?.children?.length ? dropdownLink : defaultLink"
[ngTemplateOutlet]="isDropdown(route) ? dropdownLink : defaultLink"
[ngTemplateOutletContext]="{ $implicit: route }"
>
</ng-container>

25
npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts

@ -1,29 +1,18 @@
import { ABP, ConfigState, RoutesService, takeUntilDestroy, TreeNode } from '@abp/ng.core';
import { Component, Input, OnDestroy, TrackByFunction } from '@angular/core';
import { Store } from '@ngxs/store';
import compare from 'just-compare';
import { distinctUntilChanged, skip } from 'rxjs/operators';
import { ABP, RoutesService, TreeNode } from '@abp/ng.core';
import { Component, Input, TrackByFunction } from '@angular/core';
@Component({
selector: 'abp-routes',
templateUrl: 'routes.component.html',
})
export class RoutesComponent implements OnDestroy {
export class RoutesComponent {
@Input() smallScreen: boolean;
isShow = true;
trackByFn: TrackByFunction<TreeNode<ABP.Route>> = (_, item) => item.name;
constructor(public readonly routes: RoutesService, private store: Store) {
this.store
.select(ConfigState.getDeep('auth.grantedPolicies'))
.pipe(distinctUntilChanged(compare), skip(1), takeUntilDestroy(this))
.subscribe(() => {
this.isShow = false;
setTimeout(() => (this.isShow = true), 0);
});
}
constructor(public readonly routes: RoutesService) {}
ngOnDestroy() {}
isDropdown(node: TreeNode<ABP.Route>) {
return !node.isLeaf || this.routes.hasInvisibleChild(node.name);
}
}

7
npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts

@ -8,6 +8,7 @@ import {
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { filter, map, startWith } from 'rxjs/operators';
import { eThemeSharedRouteNames } from '../../enums';
@Component({
selector: 'abp-breadcrumb',
@ -42,7 +43,7 @@ export class BreadcrumbComponent implements OnDestroy, OnInit {
while (node.parent) {
node = node.parent;
const { parent, children, isLeaf, ...segment } = node;
this.segments.unshift(segment);
if (!isAdministration(segment)) this.segments.unshift(segment);
}
this.cdRef.detectChanges();
@ -50,3 +51,7 @@ export class BreadcrumbComponent implements OnDestroy, OnInit {
});
}
}
function isAdministration(route: ABP.Route) {
return route.name === eThemeSharedRouteNames.Administration;
}

2
npm/ng-packs/packages/theme-shared/src/lib/providers/route.provider.ts

@ -10,7 +10,7 @@ export function configureRoutes(routes: RoutesService) {
return () => {
routes.add([
{
path: '',
path: '/',
name: eThemeSharedRouteNames.Administration,
iconClass: 'fa fa-wrench',
order: 1,

13
npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts

@ -2,8 +2,15 @@ import { ABP, LocalizationPipe, RouterOutletComponent, RoutesService } from '@ab
import { RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting, SpyObject } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { Subject } from 'rxjs';
import { BreadcrumbComponent } from '../components/breadcrumb/breadcrumb.component';
const mockActions = new Subject();
const mockStore = ({
selectSnapshot() {
return true;
},
} as unknown) as Store;
const mockRoutes: ABP.Route[] = [
{ name: 'Identity', path: '/identity' },
{ name: 'Users', path: '/identity/users', parentName: 'Identity' },
@ -19,6 +26,12 @@ describe('BreadcrumbComponent', () => {
stubsEnabled: false,
detectChanges: false,
mocks: [Store],
providers: [
{
provide: RoutesService,
useFactory: () => new RoutesService(mockActions, mockStore),
},
],
declarations: [LocalizationPipe, BreadcrumbComponent],
imports: [RouterModule],
routes: [

10
samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/tr.json

@ -0,0 +1,10 @@
{
"culture": "tr",
"texts": {
"Permission:ProductManagement": "Ürün Yönetimi",
"Permission:Products": "Ürünler",
"Permission:Edit": "Güncelle",
"Permission:Delete": "Sil",
"Permission:Create": "Oluştur"
}
}

16
samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/tr.json

@ -0,0 +1,16 @@
{
"culture": "tr",
"texts": {
"Menu:ProductManagement": "Ürün Yönetimi",
"Menu:Products": "Ürünler",
"ProductManagement": "Ürün Yönetimi",
"CreateANewProduct": "Yeni Bir Ürün Oluştur",
"Products": "Ürünler",
"StockCount": "Stok Sayısı",
"Code": "Kod",
"Name": "İsim",
"Price": "Fiyat",
"ImageName": "Fotoğraf İsmi",
"ProductDeletionWarningMessage": "Bu ürünü silmek istediğinize emin misiniz?"
}
}
Loading…
Cancel
Save