mirror of https://github.com/abpframework/abp.git
851 changed files with 21127 additions and 10052 deletions
@ -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ı" |
|||
} |
|||
} |
|||
@ -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,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. |
|||
@ -1,3 +1,299 @@ |
|||
# Distributed Event Bus |
|||
|
|||
TODO |
|||
Distributed Event bus system allows to **publish** and **subscribe** to events that can be **transferred across application/service boundaries**. You can use the distributed event bus to asynchronously send and receive messages between **microservices** or **applications**. |
|||
|
|||
## Providers |
|||
|
|||
Distributed event bus system provides an **abstraction** that can be implemented by any vendor/provider. There are two providers implemented out of the box: |
|||
|
|||
* `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider. |
|||
* `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it. |
|||
|
|||
Using a local event bus as default has a few important advantages. The most important one is that: It allows you to write your code compatible to distributed architecture. You can write a monolithic application now that can be split into microservices later. It is a good practice to communicate between bounded contexts (or between application modules) via distributed events instead of local events. |
|||
|
|||
For example, [pre-built application modules](Modules/Index.md) is designed to work as a service in a distributed system while they can also work as a module in a monolithic application without depending an external message broker. |
|||
|
|||
## Publishing Events |
|||
|
|||
There are two ways of publishing distributed events explained in the following sections. |
|||
|
|||
### IDistributedEventBus |
|||
|
|||
`IDistributedEventBus` can be [injected](Dependency-Injection.md) and used to publish a distributed event. |
|||
|
|||
**Example: Publish a distributed event when the stock count of a product changes** |
|||
|
|||
````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` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class: |
|||
|
|||
````csharp |
|||
using System; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
[EventName("MyApp.Product.StockChange")] |
|||
public class StockCountChangedEto |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
|
|||
public int NewCount { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). |
|||
|
|||
> `Eto` is a suffix for **E**vent **T**ransfer **O**bjects we use by convention. While it is not required, we find it useful to identify such event classes (just like [DTOs](Data-Transfer-Objects.md) on the application layer). |
|||
|
|||
#### Event Name |
|||
|
|||
`EventName` attribute is optional, but suggested. If you don't declare it, the event name will be the full name of the event class, `AbpDemo.StockCountChangedEto` in this case. |
|||
|
|||
#### About Serialization for the Event Objects |
|||
|
|||
Event transfer objects **must be serializable** since they will be serialized/deserialized to JSON or other format when it is transferred to out of the process. |
|||
|
|||
Avoid circular references, polymorphism, private setters and provide default (empty) constructors if you have any other constructor as a good practice (while some serializers may tolerate it), just like the DTOs. |
|||
|
|||
### Inside Entity / Aggregate Root Classes |
|||
|
|||
[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish distributed events inside entity / aggregate root classes. |
|||
|
|||
**Example: Publish a distributed event inside an aggregate root method** |
|||
|
|||
````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` class defines the `AddDistributedEvent` to add a new distributed event, that is published when the aggregate root object is saved (created, updated or deleted) into the database. |
|||
|
|||
> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. |
|||
|
|||
#### IGeneratesDomainEvents Interface |
|||
|
|||
Actually, adding distributed events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you. |
|||
|
|||
> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example. |
|||
|
|||
#### How It Was Implemented? |
|||
|
|||
Calling the `AddDistributedEvent` doesn't immediately publish the event. The event is published when you save changes to the database; |
|||
|
|||
* For EF Core, it is published on `DbContext.SaveChanges`. |
|||
* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). |
|||
|
|||
## Subscribing to Events |
|||
|
|||
A service can implement the `IDistributedEventHandler<TEvent>` to handle the event. |
|||
|
|||
**Example: Handle the `StockCountChangedEto` defined above** |
|||
|
|||
````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; |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
That's all. |
|||
|
|||
* `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEto` event occurs. |
|||
* If you are using a distributed message broker, like RabbitMQ, ABP automatically **subscribes to the event on the message broker**, gets the message, executes the handler. |
|||
* It sends **confirmation (ACK)** to the message broker if the event handler was successfully executed (did not throw any exception). |
|||
|
|||
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. |
|||
@ -1,3 +1,10 @@ |
|||
# Event Bus |
|||
|
|||
TODO |
|||
An event bus is a mediator that transfers a message from a sender to a receiver. In this way, it provides a loosely coupled communication way between objects, services and applications. |
|||
|
|||
## Event Bus Types |
|||
|
|||
ABP Framework provides two type of event buses; |
|||
|
|||
* **[Local Event Bus](Local-Event-Bus.md)** is suitable for in-process messaging. |
|||
* **[Distributed Event Bus](Distributed-Event-Bus.md)** is suitable for inter-process messaging, like microservices publishing and subscribing to distributed events. |
|||
@ -1,3 +1,227 @@ |
|||
# Local Event Bus |
|||
|
|||
TODO |
|||
The Local Event Bus allows services to publish and subscribe to **in-process events**. That means it is suitable if two services (publisher and subscriber) are running in the same process. |
|||
|
|||
## Publishing Events |
|||
|
|||
There are two ways of publishing local events explained in the following sections. |
|||
|
|||
### ILocalEventBus |
|||
|
|||
`ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event. |
|||
|
|||
**Example: Publish a local event when the stock count of a product changes** |
|||
|
|||
````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` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class: |
|||
|
|||
````csharp |
|||
using System; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class StockCountChangedEvent |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
|
|||
public int NewCount { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). |
|||
|
|||
### Inside Entity / Aggregate Root Classes |
|||
|
|||
[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish local events inside entity / aggregate root classes. |
|||
|
|||
**Example: Publish a local event inside an aggregate root method** |
|||
|
|||
````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` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created, updated or deleted) into the database. |
|||
|
|||
> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. |
|||
|
|||
#### IGeneratesDomainEvents Interface |
|||
|
|||
Actually, adding local events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you. |
|||
|
|||
> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example. |
|||
|
|||
#### How It Was Implemented? |
|||
|
|||
Calling the `AddLocalEvent` doesn't immediately publish the event. The event is published when you save changes to the database; |
|||
|
|||
* For EF Core, it is published on `DbContext.SaveChanges`. |
|||
* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). |
|||
|
|||
## Subscribing to Events |
|||
|
|||
A service can implement the `ILocalEventHandler<TEvent>` to handle the event. |
|||
|
|||
**Example: Handle the `StockCountChangedEvent` defined above** |
|||
|
|||
````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 |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic here. |
|||
|
|||
* **Zero or more handlers** can subscribe to the same event. |
|||
* A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler<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. |
|||
|
|||
## Transaction & Exception Behavior |
|||
|
|||
When an event published, subscribed event handlers are immediately executed. So; |
|||
|
|||
* If a handler **throws an exception**, it effects the code that published the event. That means it gets the exception on the `PublishAsync` call. So, **use try-catch yourself** in the event handler if you want to hide the error. |
|||
* If the event publishing code is being executed inside a [Unit Of Work](Unit-Of-Work.md) scope, the event handlers also covered by the unit of work. That means if your UOW is transactional and a handler throws an exception, the transaction is rolled back. |
|||
|
|||
## Pre-Built Events |
|||
|
|||
It is very common to **publish events on entity create, update and delete** operations. ABP Framework **automatically** publish these events for all entities. You can just subscribe to the related event. |
|||
|
|||
**Example: Subscribe to an event that published when a user was created** |
|||
|
|||
````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; |
|||
//... |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This class subscribes to the `EntityCreatedEventData<IdentityUser>`, which is published just after a user was created. You may want to send a "Welcome" email to the new user. |
|||
|
|||
There are two types of these events: events with past tense and events with continuous tense. |
|||
|
|||
### Events with Past Tense |
|||
|
|||
Events with past tense are published when the related unit of work completed and the entity change successfully saved to the database. If you throw an exception on these event handlers, it **can not rollback** the transaction since it was already committed. |
|||
|
|||
The event types are; |
|||
|
|||
* `EntityCreatedEventData<T>` is published just after an entity was successfully created. |
|||
* `EntityUpdatedEventData<T>` is published just after an entity was successfully updated. |
|||
* `EntityDeletedEventData<T>` is published just after an entity was successfully deleted. |
|||
* `EntityChangedEventData<T>` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. |
|||
|
|||
### Events with Continuous Tense |
|||
|
|||
Events with continuous tense are published before completing the transaction (if database transaction is supported by the database provider being used). If you throw an exception on these event handlers, it **can rollback** the transaction since it is not completed yet and the change is not saved to the database. |
|||
|
|||
The event types are; |
|||
|
|||
* `EntityCreatingEventData<T>` is published just before saving a new entity to the database. |
|||
* `EntityUpdatingEventData<T>` is published just before an existing entity is being updated. |
|||
* `EntityDeletingEventData<T>` is published just before an entity is being deleted. |
|||
* `EntityChangingEventData<T>` is published just before an entity is being created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. |
|||
|
|||
#### How It Was Implemented? |
|||
|
|||
Pre-build events are published when you save changes to the database; |
|||
|
|||
* For EF Core, they are published on `DbContext.SaveChanges`. |
|||
* For MongoDB, they are published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). |
|||
@ -0,0 +1,3 @@ |
|||
# RabbitMQ |
|||
|
|||
TODO! |
|||
@ -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` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值. |
|||
@ -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文档了解更多选项. |
|||
@ -1,3 +1,10 @@ |
|||
# Event Bus |
|||
# 事件总线 |
|||
|
|||
TODO |
|||
事件总线是将消息从发送方传输到接收方的中介. 它在对象,服务和应用程序之间提供了一种松散耦合的通信方式. |
|||
|
|||
## 事件总线类型 |
|||
|
|||
ABP框架提供了两种事件总线类型; |
|||
|
|||
* **[本地事件总线](Local-Event-Bus.md)** 适合进程内消息传递. |
|||
* **[分布式事件总线](Distributed-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没有更改追踪系统). |
|||
@ -0,0 +1,15 @@ |
|||
namespace Volo.Abp.Domain.Entities.Events.Distributed |
|||
{ |
|||
public class AbpDistributedEntityEventOptions |
|||
{ |
|||
public IAutoEntityDistributedEventSelectorList AutoEventSelectors { get; } |
|||
|
|||
public EtoMappingDictionary EtoMappings { get; set; } |
|||
|
|||
public AbpDistributedEntityEventOptions() |
|||
{ |
|||
AutoEventSelectors = new AutoEntityDistributedEventSelectorList(); |
|||
EtoMappings = new EtoMappingDictionary(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Domain.Entities.Events.Distributed |
|||
{ |
|||
public class AutoEntityDistributedEventSelectorList : List<NamedTypeSelector>, IAutoEntityDistributedEventSelectorList |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Domain.Entities.Events.Distributed |
|||
{ |
|||
public static class AutoEntityDistributedEventSelectorListExtensions |
|||
{ |
|||
public const string AllEntitiesSelectorName = "All"; |
|||
|
|||
public static void AddNamespace([NotNull] this IAutoEntityDistributedEventSelectorList selectors, [NotNull] string namespaceName) |
|||
{ |
|||
Check.NotNull(selectors, nameof(selectors)); |
|||
|
|||
var selectorName = "Namespace:" + namespaceName; |
|||
if (selectors.Any(s => s.Name == selectorName)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
selectors.Add( |
|||
new NamedTypeSelector( |
|||
selectorName, |
|||
t => t.FullName?.StartsWith(namespaceName) ?? false |
|||
) |
|||
); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a specific entity type and the types derived from that entity type.
|
|||
/// </summary>
|
|||
/// <typeparam name="TEntity">Type of the entity</typeparam>
|
|||
public static void Add<TEntity>([NotNull] this IAutoEntityDistributedEventSelectorList selectors) |
|||
where TEntity : IEntity |
|||
{ |
|||
Check.NotNull(selectors, nameof(selectors)); |
|||
|
|||
var selectorName = "Entity:" + typeof(TEntity).FullName; |
|||
if (selectors.Any(s => s.Name == selectorName)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
selectors.Add( |
|||
new NamedTypeSelector( |
|||
selectorName, |
|||
t => typeof(TEntity).IsAssignableFrom(t) |
|||
) |
|||
); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds all entity types.
|
|||
/// </summary>
|
|||
public static void AddAll([NotNull] this IAutoEntityDistributedEventSelectorList selectors) |
|||
{ |
|||
Check.NotNull(selectors, nameof(selectors)); |
|||
|
|||
if (selectors.Any(s => s.Name == AllEntitiesSelectorName)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
selectors.Add( |
|||
new NamedTypeSelector( |
|||
AllEntitiesSelectorName, |
|||
t => typeof(IEntity).IsAssignableFrom(t) |
|||
) |
|||
); |
|||
} |
|||
|
|||
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)); |
|||
return selectors.Any(s => s.Predicate(entityType)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,7 +1,8 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace Volo.Abp.EventBus.Distributed |
|||
namespace Volo.Abp.Domain.Entities.Events.Distributed |
|||
{ |
|||
public class EtoMappingDictionary : Dictionary<Type, EtoMappingDictionaryItem> |
|||
{ |
|||
@ -1,6 +1,6 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.EventBus.Distributed |
|||
namespace Volo.Abp.Domain.Entities.Events.Distributed |
|||
{ |
|||
public class EtoMappingDictionaryItem |
|||
{ |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Domain.Entities.Events.Distributed |
|||
{ |
|||
public interface IAutoEntityDistributedEventSelectorList : IList<NamedTypeSelector> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
"DisplayName:Abp.Localization.DefaultLanguage": "Varsayılan dil", |
|||
"Description:Abp.Localization.DefaultLanguage": "Varsayılan uygulama dili." |
|||
} |
|||
} |
|||
@ -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}; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
"hello": "Merhaba" |
|||
} |
|||
} |
|||
@ -1,6 +1,7 @@ |
|||
{ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
"USA": "Amerika Birleşik Devletleri" |
|||
"USA": "Amerika Birleşik Devletleri", |
|||
"Brazil": "Brezilya" |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
"ThisFieldIsRequired": "Bu alan zorunludur", |
|||
"MaxLenghtErrorMessage": "Bu alan maksimum '{0}' karakter olabilir" |
|||
} |
|||
} |
|||
@ -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" |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
|
|||
} |
|||
} |
|||
Binary file not shown.
|
Before Width: | Height: | Size: 684 KiB After Width: | Height: | Size: 699 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 823 KiB After Width: | Height: | Size: 876 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,4 @@ |
|||
/*! jQuery Validation Plugin - v1.19.2 - 5/23/2020 |
|||
* https://jqueryvalidation.org/
|
|||
* Copyright (c) 2020 Jörn Zaefferer; Licensed MIT */ |
|||
!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"هذا الحقل إلزامي",remote:"يرجى تصحيح هذا الحقل للمتابعة",email:"رجاء إدخال عنوان بريد إلكتروني صحيح",url:"رجاء إدخال عنوان موقع إلكتروني صحيح",date:"رجاء إدخال تاريخ صحيح",dateISO:"رجاء إدخال تاريخ صحيح (ISO)",number:"رجاء إدخال عدد بطريقة صحيحة",digits:"رجاء إدخال أرقام فقط",creditcard:"رجاء إدخال رقم بطاقة ائتمان صحيح",equalTo:"رجاء إدخال نفس القيمة",extension:"رجاء إدخال ملف بامتداد موافق عليه",maxlength:a.validator.format("الحد الأقصى لعدد الحروف هو {0}"),minlength:a.validator.format("الحد الأدنى لعدد الحروف هو {0}"),rangelength:a.validator.format("عدد الحروف يجب أن يكون بين {0} و {1}"),range:a.validator.format("رجاء إدخال عدد قيمته بين {0} و {1}"),max:a.validator.format("رجاء إدخال عدد أقل من أو يساوي {0}"),min:a.validator.format("رجاء إدخال عدد أكبر من أو يساوي {0}")}),a}); |
|||
@ -0,0 +1,4 @@ |
|||
/*! jQuery Validation Plugin - v1.19.2 - 5/23/2020 |
|||
* https://jqueryvalidation.org/
|
|||
* Copyright (c) 2020 Jörn Zaefferer; Licensed MIT */ |
|||
!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Bu xana mütləq doldurulmalıdır.",remote:"Zəhmət olmasa, düzgün məna daxil edin.",email:"Zəhmət olmasa, düzgün elektron poçt daxil edin.",url:"Zəhmət olmasa, düzgün URL daxil edin.",date:"Zəhmət olmasa, düzgün tarix daxil edin.",dateISO:"Zəhmət olmasa, düzgün ISO formatlı tarix daxil edin.",number:"Zəhmət olmasa, düzgün rəqəm daxil edin.",digits:"Zəhmət olmasa, yalnız rəqəm daxil edin.",creditcard:"Zəhmət olmasa, düzgün kredit kart nömrəsini daxil edin.",equalTo:"Zəhmət olmasa, eyni mənanı bir daha daxil edin.",extension:"Zəhmət olmasa, düzgün genişlənməyə malik faylı seçin.",maxlength:a.validator.format("Zəhmət olmasa, {0} simvoldan çox olmayaraq daxil edin."),minlength:a.validator.format("Zəhmət olmasa, {0} simvoldan az olmayaraq daxil edin."),rangelength:a.validator.format("Zəhmət olmasa, {0} - {1} aralığında uzunluğa malik simvol daxil edin."),range:a.validator.format("Zəhmət olmasa, {0} - {1} aralığında rəqəm daxil edin."),max:a.validator.format("Zəhmət olmasa, {0} və ondan kiçik rəqəm daxil edin."),min:a.validator.format("Zəhmət olmasa, {0} və ondan böyük rəqəm daxil edin")}),a}); |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue