diff --git a/.github/workflows/angular.yml b/.github/workflows/angular.yml index e42f3e58dd..ee66fbcf2c 100644 --- a/.github/workflows/angular.yml +++ b/.github/workflows/angular.yml @@ -10,11 +10,17 @@ on: branches: - 'rel-*' - 'dev' + types: + - opened + - synchronize + - reopened + - ready_for_review permissions: contents: read jobs: build-test-lint: + if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b88900019b..4a993352ba 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -31,12 +31,18 @@ on: - 'templates/**/*.cshtml' - 'templates/**/*.csproj' - 'templates/**/*.razor' + types: + - opened + - synchronize + - reopened + - ready_for_review permissions: contents: read jobs: build-test: runs-on: windows-latest + if: ${{ !github.event.pull_request.draft }} steps: - uses: actions/checkout@v2 - uses: actions/setup-dotnet@master diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e3127f21b1..d1f6c0c503 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,26 +9,32 @@ on: push: branches: [dev, rel-*] paths: - - 'abp/**/*.js' - - 'abp/**/*.cs' - - 'abp/**/*.cshtml' - - 'abp/**/*.csproj' - - 'abp/**/*.razor' + - "abp/**/*.js" + - "abp/**/*.cs" + - "abp/**/*.cshtml" + - "abp/**/*.csproj" + - "abp/**/*.razor" pull_request: # The branches below must be a subset of the branches above branches: [dev] paths: - - 'abp/**/*.js' - - 'abp/**/*.cs' - - 'abp/**/*.cshtml' - - 'abp/**/*.csproj' - - 'abp/**/*.razor' + - "abp/**/*.js" + - "abp/**/*.cs" + - "abp/**/*.cshtml" + - "abp/**/*.csproj" + - "abp/**/*.razor" + types: + - opened + - synchronize + - reopened + - ready_for_review permissions: contents: read jobs: analyze: + if: ${{ !github.event.pull_request.draft }} permissions: actions: read # for github/codeql-action/init to get workflow details contents: read # for actions/checkout to fetch code @@ -41,48 +47,48 @@ jobs: matrix: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['csharp', 'javascript'] + language: ["csharp", "javascript"] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/image-compression.yml b/.github/workflows/image-compression.yml index 9cbaa00d90..4556622e4a 100644 --- a/.github/workflows/image-compression.yml +++ b/.github/workflows/image-compression.yml @@ -1,21 +1,26 @@ -name: Compress Images -on: - pull_request: - paths: - - '**.jpg' - - '**.jpeg' - - '**.png' - - '**.webp' -jobs: - build: - if: github.event.pull_request.head.repo.full_name == github.repository - name: calibreapp/image-actions - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v2 - - - name: Compress Images - uses: calibreapp/image-actions@main - with: - githubToken: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file +name: Compress Images +on: + pull_request: + paths: + - "**.jpg" + - "**.jpeg" + - "**.png" + - "**.webp" + types: + - opened + - synchronize + - reopened + - ready_for_review +jobs: + build: + if: github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft + name: calibreapp/image-actions + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + + - name: Compress Images + uses: calibreapp/image-actions@main + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index f24ba57949..5c464400cc 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -2,8 +2,12 @@ name: Pull request labeler on: schedule: - cron: '0 12 */1 * *' +permissions: + contents: read jobs: labeler: + permissions: + pull-requests: write runs-on: ubuntu-latest steps: - uses: paulfantom/periodic-labeler@master diff --git a/.gitignore b/.gitignore index 3f8d0ff6cb..ccb98f81a0 100644 --- a/.gitignore +++ b/.gitignore @@ -323,3 +323,7 @@ deploy/npm-auth-token.txt deploy/ssh-password.txt deploy/github-api-key.txt deploy/_run_all_log.txt + + +# No commit yarn.lock files in the subfolders of templates directory +templates/**/yarn.lock \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/hu.json index 4738183af4..2cb142bb68 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/hu.json @@ -11,6 +11,6 @@ "CommercialSupportWebSite": "Kereskedelmi támogatási webhely", "CommunityWebSite": "ABP közösségi webhely", "ManageAccount": "Saját fiók | ABP.IO", - "ManageYourAccount": "Fiók kezelése" + "ManageYourProfile": "Profilod kezelése" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 7a064a8ce7..5db5ba6e27 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -346,7 +346,7 @@ "AdditionalDeveloperCount": "Additional developer count", "LicensePrice": "License price", "PurchaseDate": "Purchase date", - "IsAbpBookDownloaded": "ABP book downloaded", + "IsAbpBookDownloaded": "Mastering ABP Book downloaded?", "IsMasteringAbpBookDownloadEnabled": "ABP Book download enabled", "Permission:Accounting:CustomPaymentLinkGenerator": "Custom Payment Link", "CustomPaymentLink": "Custom Payment Link", @@ -381,7 +381,7 @@ "PurchaseItems": "Purchase Items", "SuccessfullyUpdated": "Successfully updated", "SuccessfullyAdded": "Successfully added", - "PurchaseState": "Purchase State", + "PurchaseState": "Purchase status", "ShowBetweenDayCount": "Show Between Days", "PurchaseOrder": "Purchase Order", "ShowCreateInvoiceOfOrganization": "Create Invoice", @@ -399,6 +399,7 @@ "AllowFeatureUpgradeOnLicenseExpire": "Allow feature upgrade on license expire", "Deleted{0}": "[Deleted {0}]", "Tags": "Tags", - "SetTagsInfo": "Tags should be comma-separated. Eg: CSharp, Entity Framework" + "SetTagsInfo": "Tags should be comma-separated. Eg: CSharp, Entity Framework", + "RejectTrialLicenseWarningMessage": "Are you sure you want to reject this trial license request?" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hu.json index e84bc09995..e006543933 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hu.json @@ -316,14 +316,12 @@ "TrialLicenseStatusFilter": "Állapot", "TrialLicenseStartDateFilter": "Kezdő dátum", "TrialLicenseEndDateFilter": "Befejezés dátuma", - "FirsName": "Keresztnév", + "FirstName": "Keresztnév", "LastName": "Vezetéknév", "StartDate": "Kezdő dátum", "EndDate": "Befejezés dátuma", "PurchasedDate": "Vásárlás dátuma", "OrganizationDetail": "Szervezet részletei", - "SendActivationMail": "Aktiváló e-mail küldése", - "ActivationMailSentSuccessfully": "Az aktiváló levél sikeresen elküldve!", "TrialLicenseStatus": "Próbaengedély állapota", "TrialLicenseDetail": "A próbaengedély részletei", "AcceptsMarketingCommunications": "Marketing kommunikáció", @@ -337,17 +335,70 @@ "Expired": "Lejárt", "TrialLicenseDeletionWarningMessage": "Biztosan törölni szeretné a próbalicencet? A próbaengedély, a szervezet, a támogatási fiókok törlésre kerülnek!", "LicenseCategoryFilter": "Licenc kategória", - "Volo.AbpIo.Commercial:030000": "Már felhasználta a próbaidőszakot.", - "Volo.AbpIo.Commercial:030001": "Ez a szervezetnév már létezik.", - "Volo.AbpIo.Commercial:030002": "Az aktiválás után a próbalicenc nem állítható kérésre!", - "Volo.AbpIo.Commercial:030003": "Nincs ilyen állapot!", - "Volo.AbpIo.Commercial:030004": "Váratlan hiba miatt az állapot nem módosítható!", - "Volo.AbpIo.Commercial:030005": "A kezdő és befejező dátum akkor frissíthető, ha a próbalicenc -aktivált- státuszban van!", - "Volo.AbpIo.Commercial:030006": "A befejező dátumnak mindig nagyobbnak kell lennie, mint a kezdő dátum!", - "Volo.AbpIo.Commercial:030007": "Ezt a próbalicencet már egyszer aktiválták!", - "Volo.AbpIo.Commercial:030008": "A vásárlás dátuma csak Vásárolt állapot esetén állítható be!", - "Volo.AbpIo.Commercial:030009": "Felhasználó nem található!", - "Volo.AbpIo.Commercial:030010": "A próbalicenc megvásárlásához először aktiválnia kell a próbalicencet!", - "Volo.AbpIo.Commercial:030011": "A próbalicenc megvásárlásakor nem törölhető!" + "Permission:SendWelcomeEmail": "Üdvözlő e-mail küldése", + "SendWelcomeEmail": "Üdvözlő e-mail küldése", + "SendWelcomeEmailWarningMessage": "Biztosan üdvözlő e-mailt szeretne küldeni a szervezet tagjainak?", + "SendWelcomeEmailSuccessMessage": "Az üdvözlő e-mail sikeresen elküldve!", + "Activate": "Aktiválás", + "ActivateTrialLicenseWarningMessage": "A próbalicensz aktiválásakor egy üdvözlő e-mailt küldünk a felhasználónak. Szeretné aktiválni?", + "ActivateTrialLicenseSuccessMessage": "Sikeres aktiválás. Az üdvözlő e-mailt elküldtük a szervezet tagjainak.", + "PaymentRequestId": "Fizetési kérelem azonosítója", + "AdditionalDeveloperCount": "További fejlesztők száma", + "LicensePrice": "licensz ára", + "PurchaseDate": "Vásárlás időpontja", + "IsAbpBookDownloaded": "ABP könyv letöltve", + "IsMasteringAbpBookDownloadEnabled": "Az ABP könyv letöltése engedélyezve", + "Permission:Accounting:CustomPaymentLinkGenerator": "Egyéni fizetési link", + "CustomPaymentLink": "Egyéni fizetési link", + "Menu:CustomPaymentLink": "Egyéni fizetési link", + "Amount": "Összeg", + "GenerateCustomPaymentLink": "Egyéni fizetési link létrehozása", + "GeneratedPaymentLink": "Létrehozott fizetési link", + "CopyText": "Szöveg másolása", + "Permission:CommunityEvents": "Események", + "Menu:Events": "Események", + "Events": "Események", + "EventType": "Esemény típus", + "Number": "Szám", + "RegistrationURL": "Regisztrációs URL", + "URL": "URL", + "EventDeletionConfirmationMessage": "Biztosan törli ezt az eseményt?", + "Enum:EventType:0": "Közösségi beszélgetések", + "CreateAnEvent": "Hozzon létre egy eseményt", + "Permission:CommunitySpeakers": "Előadók", + "CreateASpeaker": "Hozzon létre egy hangszórót", + "Speakers": "Előadók", + "Image": "Kép", + "GithubURL": "Github URL", + "SpeakerDeletionConfirmationMessage": "Biztosan törli ezt a hangszórót?", + "Menu:Speakers": "Előadók", + "ChooseSpeakerImage": "Válasszon egy előadó képet...", + "SpeakerImage": "Előadó képe", + "AddSpeaker": "Előadó hozzáadása", + "ShowPurchaseItemsOfOrganizations": "Tételek vásárlása", + "Enum:OrganizationPurchaseState:0": "Nem kézbesített", + "Enum:OrganizationPurchaseState:1": "Szállítva", + "PurchaseItems": "Tételek vásárlása", + "SuccessfullyUpdated": "Sikeresen frissítve", + "SuccessfullyAdded": "Sikeresen hozzáadva", + "PurchaseState": "Vásárlási állapot", + "ShowBetweenDayCount": "Megjelenítés a napok között", + "PurchaseOrder": "Rendelés", + "ShowCreateInvoiceOfOrganization": "Számla létrehozása", + "ShowCreateQuotationOfOrganization": "Árajánlat létrehozása", + "BookDiscounts": "Könyvkedvezmények", + "Permission:BookDiscount": "Könyvkedvezmény", + "Menu:BookDiscounts": "Könyvkedvezmények", + "BookType": "Könyv típusa", + "PurchasePlatform": "Vásárlási platform", + "StartTime": "Kezdési idő", + "EndTime": "Idő vége", + "CreateABookDiscount": "Hozzon létre könyvkedvezményt", + "BookDiscountDeletionConfirmationMessage": "Biztosan törli ezt a könyvkedvezményt?", + "CustomPaymentFlexSwitchDescription": "Licenszel", + "AllowFeatureUpgradeOnLicenseExpire": "A funkció frissítésének engedélyezése a licensz lejártakor", + "Deleted{0}": "[Törölve {0}]", + "Tags": "Címkék", + "SetTagsInfo": "A címkéket vesszővel kell elválasztani. Pl.: CSharp, Entity Framework" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json index 7fb4d64a76..8fb4eeb4e1 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json @@ -354,6 +354,64 @@ "SendWelcomeEmailWarningMessage": "Organizasyon üyelerine hoşgeldin emaili göndermek istediğinden emin misin?", "SendWelcomeEmailSuccessMessage": "Hoşgeldin emaili başarıyla gönderilmiştir!", "AdditionalDeveloperCount": "Ekstra geliştirici sayısı", - "LicensePrice": "Lisans ücreti" + "LicensePrice": "Lisans ücreti", + "FirstName": "Adı", + "Activate": "Aktif", + "ActivateTrialLicenseWarningMessage": "Bir deneme lisansını etkinleştirdiğinizde, kullanıcıya bir karşılama e-postası gönderilecektir. Etkinleştirmek istiyor musunuz?", + "ActivateTrialLicenseSuccessMessage": "Deneme lisansı başarıyla etkinleştirildi!", + "PaymentRequestId": "Ödeme talep kimliği", + "PurchaseDate": "Satın alma tarihi", + "IsAbpBookDownloaded": "Mastering ABP kitabı indirildi mi?", + "IsMasteringAbpBookDownloadEnabled": "Mastering ABP kitabı indirme etkinleştirildi", + "Permission:Accounting:CustomPaymentLinkGenerator": "Özel Ödeme Bağlantısı", + "CustomPaymentLink": "Özel Ödeme Bağlantısı", + "Menu:CustomPaymentLink": "Özel Ödeme Bağlantısı", + "Amount": "Miktar", + "GenerateCustomPaymentLink": "Özel Ödeme Bağlantısı Oluştur", + "GeneratedPaymentLink": "Oluşturulan Ödeme Bağlantısı", + "CopyText": "Metni kopyala", + "Permission:CommunityEvents": "Etkinlikler", + "Menu:Events": "Etkinlikler", + "Events": "Etkinlikler", + "EventType": "Etkinlik türü", + "Number": "Numara", + "RegistrationURL": "Kayıt URL", + "URL": "URL", + "EventDeletionConfirmationMessage": "Etkinliği silmek istediğinizden emin misiniz?", + "Enum:EventType:0": "Topluluk Konuşmaları", + "CreateAnEvent": "Etkinlik Oluştur", + "Permission:CommunitySpeakers": "Konuşmacılar", + "CreateASpeaker": "Konuşmacı Oluştur", + "Speakers": "Konuşmacılar", + "Image": "Resim", + "GithubURL": "Github URL", + "SpeakerDeletionConfirmationMessage": "Konuşmacıyı silmek istediğinizden emin misiniz?", + "Menu:Speakers": "Konuşmacılar", + "ChooseSpeakerImage": "Konuşmacı resmi seç ...", + "SpeakerImage": "Konuşmacı resmi", + "AddSpeaker": "Konuşmacı Ekle", + "ShowPurchaseItemsOfOrganizations": "Satın alınan öğeler", + "Enum:OrganizationPurchaseState:0": "Teslim edilmedi", + "Enum:OrganizationPurchaseState:1": "Teslim edildi", + "PurchaseItems": "Satın alınan öğeler", + "SuccessfullyUpdated": "Başarıyla güncellendi", + "SuccessfullyAdded": "Başarıyla eklendi", + "PurchaseState": "Satın alma durumu", + "ShowBetweenDayCount": "Gün sayısını göster", + "PurchaseOrder": "Satın alma siparişi", + "ShowCreateInvoiceOfOrganization": "Fatura oluştur", + "ShowCreateQuotationOfOrganization": "Teklif oluştur", + "BookDiscounts": "Kitap indirimleri", + "Menu:BookDiscounts": "Kitap indirimleri", + "BookType": "Kitap türü", + "PurchasePlatform": "Satın alma platformu", + "StartTime": "Başlangıç zamanı", + "EndTime": "Bitiş zamanı", + "CreateABookDiscount": "Kitap indirimi oluştur", + "BookDiscountDeletionConfirmationMessage": "Kitap indirimini silmek istediğinizden emin misiniz?", + "CustomPaymentFlexSwitchDescription": "Lisanslı", + "AllowFeatureUpgradeOnLicenseExpire": "Lisans süresi dolunca özellik yükseltmesine izin ver", + "Deleted{0}": "[{0} silindi]", + "Permission:BookDiscount": "Kitap indirimleri" } -} \ No newline at end of file +} diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json index bb2e4077cc..b363aa0c64 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json @@ -316,14 +316,12 @@ "TrialLicenseStatusFilter": "地位", "TrialLicenseStartDateFilter": "开始日期", "TrialLicenseEndDateFilter": "结束日期", - "FirsName": "名", + "FirstName": "名字", "LastName": "姓", "StartDate": "开始日期", "EndDate": "结束日期", "PurchasedDate": "购买日期", "OrganizationDetail": "组织详情", - "SendActivationMail": "发送激活邮件", - "ActivationMailSentSuccessfully": "激活邮件发送成功!", "TrialLicenseStatus": "试用许可证状态", "TrialLicenseDetail": "试用许可证详情", "AcceptsMarketingCommunications": "营销传播", @@ -337,17 +335,68 @@ "Expired": "已到期", "TrialLicenseDeletionWarningMessage": "您确定要删除试用许可证吗?试用许可证、组织、支持帐户将被删除!", "LicenseCategoryFilter": "执照类别", - "Volo.AbpIo.Commercial:030000": "您已经使用了试用期。", - "Volo.AbpIo.Commercial:030001": "此组织名称已存在。", - "Volo.AbpIo.Commercial:030002": "一旦激活,试用许可证不能设置为请求!", - "Volo.AbpIo.Commercial:030003": "没有这种状态!", - "Volo.AbpIo.Commercial:030004": "由于意外错误,无法更改状态!", - "Volo.AbpIo.Commercial:030005": "当试用许可证处于 -activated- 状态时,可以更新开始和结束日期!", - "Volo.AbpIo.Commercial:030006": "结束日期必须始终大于开始日期!", - "Volo.AbpIo.Commercial:030007": "此试用许可证已激活一次!", - "Volo.AbpIo.Commercial:030008": "购买日期只能在状态为已购买时设置!", - "Volo.AbpIo.Commercial:030009": "未找到用户!", - "Volo.AbpIo.Commercial:030010": "要购买试用许可证,首先您需要激活您的试用许可证!", - "Volo.AbpIo.Commercial:030011": "购买后,您无法删除试用许可证!" + "Permission:SendWelcomeEmail": "发送欢迎邮件", + "SendWelcomeEmail": "发送欢迎邮件", + "SendWelcomeEmailWarningMessage": "你确定要发送欢迎邮件给组织成员吗?", + "SendWelcomeEmailSuccessMessage": "欢迎邮件发送成功!", + "Activate": "激活", + "ActivateTrialLicenseWarningMessage": "激活试用版权限后,将发送欢迎邮件给用户。你确定要激活吗?", + "ActivateTrialLicenseSuccessMessage": "激活成功,欢迎邮件已发送给组织成员。", + "PaymentRequestId": "付款请求编号", + "AdditionalDeveloperCount": "额外开发者数量", + "LicensePrice": "版权价格", + "PurchaseDate": "购买日期", + "IsAbpBookDownloaded": "ABP书籍已下载", + "IsMasteringAbpBookDownloadEnabled": "ABP书籍下载已启用", + "Permission:Accounting:CustomPaymentLinkGenerator": "自定义付款链接", + "CustomPaymentLink": "自定义付款链接", + "Menu:CustomPaymentLink": "自定义付款链接", + "Amount": "金额", + "GenerateCustomPaymentLink": "生成自定义付款链接", + "GeneratedPaymentLink": "生成的付款链接", + "CopyText": "复制文本", + "Permission:CommunityEvents": "活动", + "Menu:Events": "活动", + "Events": "活动", + "EventType": "活动类型", + "Number": "数量", + "RegistrationURL": "报名网址", + "URL": "网址", + "EventDeletionConfirmationMessage": "你确定要删除这个活动吗?", + "Enum:EventType:0": "社区讲话", + "CreateAnEvent": "创建一个活动", + "Permission:CommunitySpeakers": "演讲者", + "CreateASpeaker": "创建一个演讲者", + "Speakers": "演讲者", + "Image": "图片", + "GithubURL": "Github网址", + "SpeakerDeletionConfirmationMessage": "你确定要删除这个演讲者吗?", + "Menu:Speakers": "演讲者", + "ChooseSpeakerImage": "选择一个演讲者图片...", + "SpeakerImage": "演讲者图片", + "AddSpeaker": "添加演讲者", + "ShowPurchaseItemsOfOrganizations": "购买项目", + "Enum:OrganizationPurchaseState:0": "未送达", + "Enum:OrganizationPurchaseState:1": "已送达", + "PurchaseItems": "购买项目", + "SuccessfullyUpdated": "成功更新", + "SuccessfullyAdded": "成功添加", + "PurchaseState": "购买状态", + "ShowBetweenDayCount": "显示之间的天数", + "PurchaseOrder": "购买订单", + "ShowCreateInvoiceOfOrganization": "创建发票", + "ShowCreateQuotationOfOrganization": "创建报价单", + "BookDiscounts": "书籍折扣", + "Permission:BookDiscount": "书籍折扣", + "Menu:BookDiscounts": "书籍折扣", + "BookType": "书籍类型", + "PurchasePlatform": "购买平台", + "StartTime": "开始时间", + "EndTime": "结束时间", + "CreateABookDiscount": "创建一个书籍折扣", + "BookDiscountDeletionConfirmationMessage": "你确定要删除这个书籍折扣吗?", + "CustomPaymentFlexSwitchDescription": "授权", + "AllowFeatureUpgradeOnLicenseExpire": "允许在授权过期时进行功能升级", + "Deleted{0}": "[已删除 {0}]" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index 6583ae731f..ae5444be35 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -73,6 +73,7 @@ "DeveloperFocused": "Developer Focused", "ShareYourExperiences": "Share your experiences with the ABP Framework", "LatestPosts": "Latest Posts", + "LatestVideos": "Latest Videos", "Views": "Views", "LearnLatestNewsAboutABPFramework": "Get information about happenings in ABP like new releases, free sources, posts, and more.", "DeveloperTools": "Developer Tools", @@ -98,6 +99,7 @@ "Logout": "Logout", "Home": "Home", "Posts": "Posts", + "Videos": "Videos", "JoinTheABPCommunity": "Join the ABP Community", "SubmitYourPost": "Submit Your Post", "Modules": "Modules", @@ -140,7 +142,7 @@ "DomainDrivenDesign": "Domain Driven Design", "CrossCuttingConcerns": "Cross Cutting Concerns", "AbpCommunity": "ABP Community", - "Footer_GithubStarCount": "{0} Star on GitHub", + "Footer_GithubStarCount": "{0} Stars on GitHub", "Footer_NugetDownloadCount": "{0} Downloads on NuGet", "AbpDescription": "ABP is an open source application framework focused on AspNet Core based web application development. Don't repeat yourself, focus on your own business code.", "Layout_AbpFramework_MetaTitle": "ABP Framework - Open Source Web Application Framework", @@ -171,6 +173,12 @@ "BuyOrRenewLicenseToGetExtra2Months": "Buy or Renew License Now and Get 2 Extra Months! HURRY UP! ⏰ Last Day: {0}", "HurryUp": "HURRY UP!", "LastDay": "Last Day: {0}", - "BuyNewLicenseBetweenDatesToGetBenefit": "Buy a new license between {0} and {1} to get benefit for extra 2 months!" + "BuyNewLicenseBetweenDatesToGetBenefit": "Buy a new license between {0} and {1} to get benefit for extra 2 months!", + "CheckAllCommunityTalks": "Check All Community Posts", + "ReadMore": "Read More", + "Post": "Post", + "ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "Explore the contents created by the core ABP team and the ABP community.", + "WelcomeFallCampaign": "Welcome Fall Campaign!", + "GiveAwayForNewPurchases": "Application Development Classroom Training will be given away for the new purchases!" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json index 079132d1e8..21eb33ac23 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json @@ -14,6 +14,20 @@ "Volo.AbpIo.Domain:020002": "Nem sikerült törölni ezt az NPM-csomagot, mert a \"{Modules}\" modulok ezt a csomagot használják.", "Volo.AbpIo.Domain:020003": "Nem sikerült törölni ezt az NPM-csomagot, mert a \"{Modules}\" modulok ezt a csomagot használják, és a \"{NugetPackages}\" Nuget-csomagok ettől a csomagtól függenek.", "Volo.AbpIo.Domain:020004": "Nem sikerült törölni ezt a Nuget-csomagot, mert a \"{Modules}\" modulok ezt a csomagot használják.", + "Volo.AbpIo.Domain:030000": "Már befejezte a próbaidőszakot.", + "Volo.AbpIo.Domain:030001": "Ez a szervezetnév már létezik.", + "Volo.AbpIo.Domain:030002": "Az aktiválás után a próbalicensz nem váltható át -igényelt- állapotra!", + "Volo.AbpIo.Domain:030003": "Nincs ilyen állapot!", + "Volo.AbpIo.Domain:030004": "Az állapot váratlan hiba miatt nem változtatható meg!", + "Volo.AbpIo.Domain:030005": "A kezdő és befejező dátum akkor frissíthető, ha a próbalicensz -aktivált- státuszban van!", + "Volo.AbpIo.Domain:030006": "A befejező dátumnak nagyobbnak kell lennie, mint a kezdő dátum!", + "Volo.AbpIo.Domain:030007": "Ez a próbalicensz már aktiválva van!", + "Volo.AbpIo.Domain:030008": "A vásárlás dátuma csak -megvásárolt- státusz esetén állítható be!", + "Volo.AbpIo.Domain:030009": "Felhasználó nem található!", + "Volo.AbpIo.Domain:030010": "A próbalicensz megvásárlásához először aktiválnia kell a próbalicenszet!", + "Volo.AbpIo.Domain:030011": "A próbalicensz megvásárlásakor nem törölhető!", + "Volo.AbpIo.Domain:070000": "A szervezet neve csak latin betűket, számokat, pontokat és kötőjeleket tartalmazhat!", + "Volo.AbpIo.Domain:070001": "A cégnév csak latin betűket, számokat, pontokat, szóközt és kötőjelet tartalmazhat!", "WantToLearn?": "Tanulni akar?", "ReadyToGetStarted?": "Készen áll az indulásra?", "JoinOurCommunity": "Csatlakozz a közösségünkhöz", @@ -39,6 +53,132 @@ "TrialLicensePeriodHasExpired": "A próbalicenc időszaka {0} napja lejárt.", "TrialLicensePeriodWillExpire": "A próbalicenc időszaka {0} napon belül lejár.", "TrialLicensePeriodExpireToday": "A próbalicenc ideje ma lejár.", - "PurchaseNow": "Vásároljon most!" + "PurchaseNow": "Vásároljon most!", + "LatestReleaseLogs": "Legújabb kiadási naplók", + "RoadMap": "Útiterv", + "FAQ": "GYIK", + "SourceCode": "Forráskód", + "SeeAllPosts": "Az összes bejegyzés megtekintése", + "Contribute": "Hozzájárulás", + "LiveDemo": "Élő Demo", + "GetLicense": "Licensz beszerzése", + "OpenSource": "Nyílt forráskód", + "WebApplication": "Webalkalmazás", + "MeetTheABP": "Ismerje meg az ABP-t", + "CompleteWebDevelopment": "Teljes körű webfejlesztés", + "Platform": "Felület", + "ABPDescription": "Az ABP Framework egy komplett infrastruktúra modern webalkalmazások létrehozásához, követve a szoftverfejlesztés legjobb gyakorlatait és konvencióit.", + "StrongInfrastructure": "Erős infrastruktúra", + "CompleteArchitecture": "Teljes arhitektúra", + "DeveloperFocused": "Fejlesztőközpontú", + "ShareYourExperiences": "Ossza meg tapasztalatait az ABP keretrendszerrel", + "LatestPosts": "Legutóbbi bejegyzések", + "LatestVideos": "Legújabb videók", + "Views": "Nézetek", + "LearnLatestNewsAboutABPFramework": "Információkat kaphat az ABP eseményeiről, például új kiadásokról, ingyenes forrásokról, bejegyzésekről és egyebekről.", + "DeveloperTools": "Fejlesztői eszközök", + "StartupTemplates": "Indítási sablonok", + "ApplicationModules": "Alkalmazási modulok", + "UI": "UI", + "Themes": "Témák", + "Premium": "Prémium", + "PrivacyPolicy": "Adatvédelmi irányelvek", + "TermsAndConditions": "Felhasználási feltételek", + "WouldLikeToReceiveMarketingMaterials": "Szeretnék marketing anyagokat kapni, például termékajánlatokat és különleges ajánlatokat.", + "JoinOurMarketingNewsletter": "Csatlakozzon marketing hírlevelünkhöz", + "CommunityPrivacyPolicyConfirmation": "Elfogadom az Általános Szerződési Feltételeket és az Adatvédelmi szabályzatot.", + "WouldLikeToReceiveNotification": "Szeretném megkapni a legfrissebb híreket az abp.io webhelyekről.", + "CommercialNewsletterConfirmationMessage": "Elfogadom az Általános Szerződési Feltételeket és az Adatvédelmi szabályzatot .", + "FreeDDDEBook": "Ingyenes DDD e-könyv", + "AdditionalServices": "További szolgáltatások", + "Learn": "Tanulás", + "AccountOverview": "Fiók Áttekintés", + "MyOrganizations": "Szervezeteim", + "MySupportQuestions": "Támogatási kérdéseim", + "MyProfile": "A profilom", + "Logout": "Kijelentkezés", + "Home": "Kezdőlap", + "Posts": "Hozzászólások", + "Videos": "Videók", + "JoinTheABPCommunity": "Csatlakozz az ABP közösséghez", + "SubmitYourPost": "Küldje be bejegyzését", + "Modules": "Modulok", + "Tools": "Eszközök", + "Pricing": "Árazás", + "ChangeLogs": "Változásnaplók", + "SubscribeToNewsletter": "Feliratkozás a Hírlevélre", + "SubscribeToNewsletterDescription": "Információkat kaphat az ABP eseményeiről, például új kiadásokról, ingyenes forrásokról, bejegyzésekről és egyebekről.", + "EmailAddress": "Email cím", + "Subscribe": "Iratkozz fel", + "WelcomeToABP": "Üdvözöljük az ABP-ben", + "EULA": "EULA", + "ABPCommercialIntroductionMessage": "Előre beépített alkalmazásmodulok, fejlett indítási sablonok, gyors alkalmazásfejlesztési eszközök, professzionális felhasználói felületi témák és prémium támogatás.", + "MasteringAbpFrameworkEBook": "Az ABP Framework elsajátítása", + "MasteringTheABPFrameworkExplanation": "Ez a könyv, amelyet az ABP-keretrendszer alkotója írt, segít a keretrendszer és a modern webalkalmazás-fejlesztési technikák teljes megértésében.", + "Speakers": "Előadók", + "PreviousEvents": "Korábbi események", + "WatchTheEvent": "Nézze meg az Eseményt", + "RegisterNow": "Regisztrálj most", + "ThereIsNoEvent": "Nincs esemény.", + "Events": "Események", + "Volo.AbpIo.Domain:080000": "Már van egy \"{Name}\" nevű vásárlási tétel", + "MasteringAbpFrameworkBook": "Könyv: Az ABP-keretrendszer elsajátítása", + "ABPIO-CommonPreferenceDefinition": "Szerezze meg a legfrissebb híreket az ABP Platformról, például új bejegyzésekről, eseményekről és egyebekről.", + "BuiltOn": "Beépített", + "AbpFramework": "ABP-keretrendszer", + "Volo.AbpIo.Domain:080001": "A kezdési idő nem lehet nagyobb, mint a befejezési idő", + "Enum:BookType:0": "Az ABP Framework elsajátítása", + "Enum:PurchasePlatform:0": "Amazon", + "Enum:PurchasePlatform:1": "Csomagolt", + "Copied": "Másolva!", + "CouldNotCopy": "Nem sikerült másolni!", + "CopyNotSupportByYourBrowser": "Ez a funkció nem működik az Ön által használt böngészőben.", + "City": "Város", + "ZipCode": "Irányítószám", + "Address": "Cím", + "Homepage": "Kezdőlap", + "Year": "Év", + "Copyright": "Copyright © {1}", + "DomainDrivenDesign": "Domainvezérelt tervezés", + "CrossCuttingConcerns": "Cross Cutting Concerns", + "AbpCommunity": "ABP közösség", + "Footer_GithubStarCount": "{0} csillagok a GitHubon", + "Footer_NugetDownloadCount": "{0} Letöltések a NuGeten", + "AbpDescription": "Az ABP egy nyílt forráskódú alkalmazás keretrendszer, amely az AspNet Core alapú webalkalmazások fejlesztésére összpontosít. Ne ismételje magát, összpontosítson saját üzleti kódjára.", + "Layout_AbpFramework_MetaTitle": "ABP Framework – Nyílt forráskódú webalkalmazás-keretrendszer", + "CommunityTalks_CountdownDays": "Napok", + "CommunityTalks_CountdownHours": "óra", + "CommunityTalks_CountdownMinutes": "Perc", + "CommunityTalks_CountdownSeconds": "mp", + "SeePreviousEvents": "Lásd: Korábbi események", + "CookieConsent_Accept": "Elfogad", + "CookieConsent_Explanation_1": "Cookie-kat használunk, hogy a legjobb élményt nyújtsuk weboldalunkon.", + "CookieConsent_Explanation_2": "Ha folytatja a böngészést, elfogadja adatvédelmi szabályzatunkat és cookie-kra vonatkozó szabályzatunkat. .", + "Error_Page_400_Title": "Hiba történt a kért oldal kiszolgálása során.", + "Error_Page_400_Description_1": "Ez általában azt jelenti, hogy a kérés feldolgozása során váratlan hiba történt.", + "Error_Page_400_Description_2": "Ha a probléma továbbra is fennáll, vegye fel velünk a kapcsolatot az info@abp.io címen , és mi segítünk az úton.", + "GoToHomepage": "Menj a főoldalra", + "Error_Page_404_Title": "Az oldal nem található!", + "Error_Page_404_Description_1": "Ez nem az a weboldal, amit keres.", + "Error_Page_500_Title": "Úgy tűnik, valami elromlott!", + "Error_Page_500_Description_1": "Ezeket a hibákat automatikusan nyomon követjük, de ha a probléma továbbra is fennáll, nyugodtan megteheti
lépjen kapcsolatba velünk. Addig is próbáljon frissíteni.", + "Error_Page_500_Description_2": "Vegye fel velünk a kapcsolatot az info@abp.io címen .", + "Books": "Könyvek", + "ABPDiscordServer": "ABP Discord szerver", + "ABPCommunityTalks": "ABP közösségi beszélgetések", + "ABPCommunityPosts": "ABP közösségi bejegyzések", + "BuyAndGetMonths": "VÁSÁROLJON 12 HÓNAPOT, 14 HÓNAPOT KAP!", + "GetYourDeal": "Szerezze meg az ajánlatát", + "BuyOrRenewLicense": "Vásároljon vagy újítson meg licencet most, és 2 további hónapot kap!", + "BuyOrRenewLicenseToGetExtra2Months": "Vásároljon vagy újítson meg licencet most, és 2 további hónapot kap! SIESS! ⏰ Utolsó nap: {0}", + "HurryUp": "SIESS!", + "LastDay": "Utolsó nap: {0}", + "BuyNewLicenseBetweenDatesToGetBenefit": "Vásároljon új licencet {0} és {1} között, és további 2 hónapra juthat!", + "CheckAllCommunityTalks": "Jelölje be az Összes közösségi bejegyzést", + "ReadMore": "Olvass tovább", + "Post": "Hozzászólás", + "ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "Fedezze fel a központi ABP csapat és az ABP közösség által létrehozott tartalmakat.", + "WelcomeFallCampaign": "Üdvözöljük az őszi kampányban!", + "GiveAwayForNewPurchases": "Az új vásárlásokhoz az Alkalmazásfejlesztési Tantermi Képzést ajándékba adjuk!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json index 818b4cab5e..dee01c8589 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json @@ -39,6 +39,133 @@ "TrialLicensePeriodHasExpired": "Deneme lisansınızın süresi {0} gün önce sona erdi.", "TrialLicensePeriodWillExpire": "Deneme lisansınızın süresi {0} gün içinde dolacak.", "TrialLicensePeriodExpireToday": "Deneme lisans süreniz bugün sona erecek.", - "PurchaseNow": "Şimdi satın al!" + "PurchaseNow": "Şimdi satın al!", + "Volo.AbpIo.Domain:030000": "Deneme sürenizi çoktan tamamladınız.", + "Volo.AbpIo.Domain:030001": "Bu kuruluş adı zaten mevcut.", + "Volo.AbpIo.Domain:030002": "Bir kez etkinleştirildiğinde, deneme lisansını -requested- duruma değiştiremezsiniz!", + "Volo.AbpIo.Domain:030003": "Böyle bir durum yok!", + "Volo.AbpIo.Domain:030004": "Beklenmeyen bir hata nedeniyle durum değiştirilemedi!", + "Volo.AbpIo.Domain:030005": "Deneme lisansı -activated- durumundayken başlangıç ve bitiş tarihi güncellenebilir!", + "Volo.AbpIo.Domain:030006": "Bitiş tarihi, başlangıç tarihinden büyük olmalıdır!", + "Volo.AbpIo.Domain:030007": "Bu deneme lisansı zaten etkinleştirildi!", + "Volo.AbpIo.Domain:030008": "Satın alma tarihi ancak durum -purchased- olduğunda ayarlanabilir!", + "Volo.AbpIo.Domain:030009": "Kullanıcı bulunamadı!", + "Volo.AbpIo.Domain:030010": "Deneme lisansını satın almak için önce deneme lisansınızı etkinleştirmeniz gerekir!", + "Volo.AbpIo.Domain:030011": "Satın alınan bir deneme lisansını silemezsiniz!", + "Volo.AbpIo.Domain:070000": "Kuruluş adı yalnızca latin harfleri, sayıları, noktaları ve kısa çizgileri içerebilir!", + "Volo.AbpIo.Domain:070001": "Şirket adı yalnızca latin harfleri, sayıları, noktaları, boşlukları ve kısa çizgileri içerebilir!", + "LatestReleaseLogs": "Son Sürüm Logları", + "RoadMap": "Yol Haritası", + "FAQ": "SSS", + "SourceCode": "Kaynak Kodu", + "SeeAllPosts": "Tüm Gönderileri Gör", + "Contribute": "Katkıda Bulun", + "LiveDemo": "Canlı Demo", + "GetLicense": "Lisans Al", + "OpenSource": "Açık Kaynak", + "WebApplication": "Web Uygulaması", + "MeetTheABP": "ABP'yi Tanıyın", + "CompleteWebDevelopment": "Eksiksiz Bir Web Geliştirme", + "Platform": "Platform", + "ABPDescription": "ABP Framework, en iyi yazılım geliştirme uygulamalarını ve kurallarını takip ederek modern web uygulamaları oluşturmak için eksiksiz bir altyapıdır.", + "StrongInfrastructure": "Güçlü Altyapı", + "CompleteArchitecture": "Eksiksiz Mimari", + "DeveloperFocused": "Geliştirici Odaklı", + "ShareYourExperiences": "ABP Çerçevesi ile ilgili deneyimlerinizi paylaşın", + "LatestPosts": "Son Gönderiler", + "Views": "Görüntülenme", + "LearnLatestNewsAboutABPFramework": "Yeni sürümler, ücretsiz kaynaklar, gönderiler ve daha fazlası gibi ABP'de olup bitenler hakkında bilgi alın.", + "DeveloperTools": "Geliştirici Araçları", + "StartupTemplates": "Başlangıç Şablonları", + "ApplicationModules": "Uygulama Modülleri", + "UI": "Kullanıcı Arayüzü", + "Themes": "Temalar", + "Premium": "Premium", + "PrivacyPolicy": "Gizlilik Politikası", + "TermsAndConditions": "Şartlar ve Koşullar", + "WouldLikeToReceiveMarketingMaterials": "Ürün fırsatları ve özel teklifler gibi pazarlama materyalleri almak istiyorum.", + "JoinOurMarketingNewsletter": "Pazarlama bültenimize katılın", + "CommunityPrivacyPolicyConfirmation": "Şartları, Koşulları ve Gizlilik Politikasını kabul ediyorum.", + "WouldLikeToReceiveNotification": "abp.io web sitelerinden en son haberleri almak istiyorum.", + "CommercialNewsletterConfirmationMessage": "Şartlar ve Koşulları ve Gizlilik Politikasını kabul ediyorum.", + "FreeDDDEBook": "Ücretsiz DDD E-Kitabı", + "AdditionalServices": "Ek Hizmetler", + "Learn": "Öğren", + "AccountOverview": "Hesap Özeti", + "MyOrganizations": "Kuruluşlarım", + "MySupportQuestions": "Destek Sorularım", + "MyProfile": "Profilim", + "Logout": "Çıkış Yap", + "Home": "Ev", + "Posts": "Gönderiler", + "JoinTheABPCommunity": "ABP Topluluğuna Katılın", + "SubmitYourPost": "Gönderinizi Gönderin", + "Modules": "Modüller", + "Tools": "Araçlar", + "Pricing": "Fiyatlandırma", + "ChangeLogs": "Değişiklik Logları", + "SubscribeToNewsletter": "Bültenimize Abone Olun", + "SubscribeToNewsletterDescription": "Yeni sürümler, ücretsiz kaynaklar, gönderiler ve daha fazlası gibi ABP'de olup bitenler hakkında bilgi alın.", + "EmailAddress": "E-posta Adresi", + "Subscribe": "Abone Ol", + "WelcomeToABP": "ABP'ye Hoşgeldiniz", + "EULA": "SKLA", + "ABPCommercialIntroductionMessage": "Önceden oluşturulmuş uygulama modülleri, gelişmiş başlangıç şablonları, hızlı uygulama geliştirme araçları, profesyonel kullanıcı arayüzü temaları ve premium destek.", + "MasteringAbpFrameworkEBook": "Mastering ABP Framework", + "MasteringTheABPFrameworkExplanation": "ABP Çerçevesinin yaratıcısı tarafından yazılan bu kitap, çerçeveyi ve modern web uygulaması geliştirme tekniklerini tam olarak anlamanıza yardımcı olacaktır.", + "Speakers": "Konuşmacılar", + "PreviousEvents": "Önceki Etkinlikler", + "WatchTheEvent": "Etkinliği İzle", + "RegisterNow": "Şimdi Kaydol", + "ThereIsNoEvent": "Etkinlik yok", + "Events": "Etkinlikler", + "Volo.AbpIo.Domain:080000": "\"{name}\" adlı bir satın alma öğesi zaten var", + "MasteringAbpFrameworkBook": "Kitap: Mastering ABP Framework", + "ABPIO-CommonPreferenceDefinition": "Yeni gönderiler, etkinlikler ve daha fazlası gibi ABP Platformu hakkındaki en son haberleri alın.", + "BuiltOn": "Üzerinde inşa edildi", + "AbpFramework": "ABP Çerçevesi", + "Volo.AbpIo.Domain:080001": "Başlangıç Zamanı Bitiş Zamanından büyük olamaz", + "Enum:BookType:0": "Mastering ABP Framework", + "Enum:PurchasePlatform:0": "Amazon", + "Enum:PurchasePlatform:1": "Paket", + "Copied": "Kopyalandı!", + "CouldNotCopy": "Kopyalanamadı!", + "CopyNotSupportByYourBrowser": "Tarayıcınız kopyalamayı desteklemiyor", + "City": "Şehir", + "ZipCode": "Posta Kodu", + "Address": "Adres", + "Homepage": "Anasayfa", + "Year": "Yıl", + "Copyright": "Telif hakkı © {1}", + "DomainDrivenDesign": "Alan Odaklı Tasarım", + "CrossCuttingConcerns": "Cross Cutting Concerns", + "AbpCommunity": "ABP Topluluğu", + "Footer_GithubStarCount": "Github'da {0} Yıldız", + "Footer_NugetDownloadCount": "{0} NuGet indirme", + "AbpDescription": "ABP, AspNet Core tabanlı web uygulaması geliştirmeye odaklanan açık kaynaklı bir uygulama çerçevesidir. Kendinizi tekrar etmeyin, kendi iş kodunuza odaklanın.", + "Layout_AbpFramework_MetaTitle": "ABP Framework - Açık Kaynak Web Uygulama Çerçevesi", + "CommunityTalks_CountdownDays": "Gün", + "CommunityTalks_CountdownHours": "Saat", + "CommunityTalks_CountdownMinutes": "Dk", + "CommunityTalks_CountdownSeconds": "Sn", + "SeePreviousEvents": "Önceki Etkinlikleri Gör", + "CookieConsent_Accept": "Kabul Et", + "CookieConsent_Explanation_1": "Web sitemizde size en iyi deneyimi sunmak için çerezleri kullanıyoruz.", + "CookieConsent_Explanation_2": "Göz atmaya devam ederseniz, Gizlilik politikamızı ve çerez politikamızı kabul etmiş olursunuz..", + "Error_Page_400_Title": "İstenen sayfa sunulurken bir sorun oluştu.", + "Error_Page_400_Description_1": "Genellikle bu, isteğiniz işlenirken beklenmedik bir hata oluştuğu anlamına gelir.", + "Error_Page_400_Description_2": "Sorun devam ederse, info@abp.io adresinden bizimle iletişime geçin, size yardımcı olalım.", + "GoToHomepage": "Anasayfaya git", + "Error_Page_404_Title": "Sayfa bulunamadı!", + "Error_Page_404_Description_1": "Aradığınız web sayfası bu değil.", + "Error_Page_500_Title": "Görünüşe göre bir şeyler ters gitti!", + "Error_Page_500_Description_1": "Bu hataları otomatik olarak takip ediyoruz, ancak sorun devam ederse
bizimle iletişime geçmekten çekinmeyin. Bu arada, yenilemeyi deneyin.", + "Error_Page_500_Description_2": "Bizimle info@abp.io adresinden iletişime geçin.", + "Books": "Kitaplar", + "ABPDiscordServer": "ABP Discord Sunucusu", + "ABPCommunityTalks": "ABP Topluluk Konuşmaları", + "ABPCommunityPosts": "ABP Topluluk Gönderileri", + "WelcomeFallCampaign": "Hoş Geldin Sonbahar Kampanyası!", + "GiveAwayForNewPurchases": "Yeni alımlar için Uygulama Geliştirme Sınıfı Eğitimi hediye edilecektir!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json index c4aa5f4926..4b883b430c 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json @@ -73,6 +73,7 @@ "DeveloperFocused": "以开发者为中心", "ShareYourExperiences": "分享您使用 ABP 框架的经验", "LatestPosts": "最新的帖子", + "LatestVideos": "最新的视频", "Views": "意见", "LearnLatestNewsAboutABPFramework": "获取有关 ABP 的最新相关信息,例如新版本、免费资源、帖子等。", "DeveloperTools": "开发者工具", @@ -98,6 +99,7 @@ "Logout": "登出", "Home": "主页", "Posts": "帖子", + "Videos": "视频", "JoinTheABPCommunity": "加入 ABP 社区", "SubmitYourPost": "提交您的帖子", "Modules": "模块", @@ -140,7 +142,7 @@ "DomainDrivenDesign": "领域驱动设计", "CrossCuttingConcerns": "横切关注点", "AbpCommunity": "ABP 社区", - "Footer_GithubStarCount": "{0} GitHub Stars", + "Footer_GithubStarCount": "{0} GitHub 上的星星", "Footer_NugetDownloadCount": "{0} NuGet 下载量", "AbpDescription": "ABP 是一个开源应用程序框架,专注于基于 AspNet Core 的 Web 应用程序开发。 Don't repeat yourself,专注于自己的业务代码。", "Layout_AbpFramework_MetaTitle": "ABP 框架 - 开源 Web 应用程序框架", @@ -162,12 +164,21 @@ "Error_Page_500_Description_1": "我们会自动跟踪这些错误,但如果问题仍然存在,请随时
联系我们。 与此同时,尝试刷新。", "Error_Page_500_Description_2": "通过 info@abp.io 与我们联系。", "Books": "书籍", + "ABPDiscordServer": "ABP Discord 服务器", + "ABPCommunityTalks": "ABP社区讲话", + "ABPCommunityPosts": "ABP社区文章", "BuyAndGetMonths": "购买 12 个月,获得 14 个月!", "GetYourDeal": "得到你的交易", "BuyOrRenewLicense": "立即购买或续订许可证并额外获得 2 个月!", "BuyOrRenewLicenseToGetExtra2Months": "立即购买或续订 ABP 商业许可证(适用于所有版本)并额外获得 2 个月!", "HurryUp": "赶快下单!", "LastDay": "活动截止日期: {0}", - "BuyNewLicenseBetweenDatesToGetBenefit": "在 {0} 和 {1} 之间购买一个新的许可证以获得额外 2 个月的收益!" + "BuyNewLicenseBetweenDatesToGetBenefit": "在 {0} 和 {1} 之间购买一个新的许可证以获得额外 2 个月的收益!", + "CheckAllCommunityTalks": "检查所有社区帖子", + "ReadMore": "阅读更多", + "Post": "邮政", + "ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "探索核心 ABP 团队和 ABP 社区创建的内容。", + "WelcomeFallCampaign": "欢迎秋季活动!", + "GiveAwayForNewPurchases": "新购买将赠送应用程序开发课堂培训!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hant.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hant.json index dbf04ae25b..f55f4a7f85 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hant.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hant.json @@ -39,6 +39,8 @@ "TrialLicensePeriodHasExpired": "您的試用許可期限已於 {0} 天前到期。", "TrialLicensePeriodWillExpire": "您的試用許可期限將在 {0} 天后到期。", "TrialLicensePeriodExpireToday": "您的試用許可期將於今天到期。", - "PurchaseNow": "現在買!" + "PurchaseNow": "現在買!", + "WelcomeFallCampaign": "欢迎秋季活动!", + "GiveAwayForNewPurchases": "新购买将赠送应用程序开发课堂培训!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/hu.json new file mode 100644 index 0000000000..0cd40d0d5a --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/hu.json @@ -0,0 +1,7 @@ +{ + "culture": "hu", + "texts": { + "AbpTitle": "ABP Framework – Nyílt forráskódú webalkalmazás-keretrendszer", + "AbpDescription": "Az ABP egy nyílt forráskódú alkalmazáskeret, amely az AspNet Core alapú webalkalmazások fejlesztésére összpontosít. Ne ismételje magát, összpontosítson saját üzleti kódjára." + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/tr.json index 5af6a13c50..c5f72d9fbe 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/tr.json @@ -1,5 +1,7 @@ { "culture": "tr", "texts": { + "AbpTitle": "ABP Framework - Açık Kaynak Web Uygulama Çerçevesi", + "AbpDescription": "ABP, AspNet Core tabanlı web uygulaması geliştirmeye odaklanan açık kaynaklı bir uygulama çerçevesidir. Kendinizi tekrar etmeyin, kendi iş kodunuza odaklanın." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 64cefef9af..8f9a6759d8 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -268,7 +268,7 @@ "SeeABPSuiteDocument": "Check out the ABP Suite document to learn the usage of ABP Suite.", "AskQuestionsOnSupport": "You can ask questions on ABP Commercial Support.", "Documentation": "Documentation", - "SeeModulesDocument": "Check out the modules document for a list of all the commercial(pro) modules and their documents.", + "SeeModulesDocument": "See the modules page for a list of all the PRO modules.", "Pricing": "Pricing", "PricingExplanation": "Choose the features and functionality your business needs today. Easily upgrade as your business grows.", "Team": "Team", @@ -370,8 +370,8 @@ "PurposeOfUsage": "Purpose of usage", "Industry": "Industry", "Choose": "- Choose -", - "CompanyOrganizationName": "Company / Organization Name", - "CompanySize": "Company Size", + "CompanyOrganizationName": "Company / Organization name", + "CompanySize": "Company size", "Next": "Next", "StartTrial": "Start My Free Trial", "ContactUsQuestions": "Contact us if you have any questions", @@ -548,7 +548,7 @@ "MyOrganizations_Detail_OwnerRightInfo": "You are using {0} of your {1} owners rights.", "MyOrganizations_Detail_CopyApiKey": "Copy the Key", "MyOrganizations_Detail_ApiKeyDescription": "The API Key is the token of PRO packages hosted on {1}.", - "MyOrganizations_Detail_YourPrivateNugetSource": "Your private NuGet source is {0}", + "MyOrganizations_Detail_YourPrivateNugetSource": "Your private NuGet source is {0}", "MyOrganizations_Detail_PrivateNugetSourceWarning": "This is automatically added as a feed to your NuGet.Config in your ABP solution. Do not share your private key with unauthorized users!", "MyOrganizations_Detail_DeveloperSeatInfo": "You are using {0} of your {1} developer seats.", "NeedMoreSeatsForYourTeam": "Need more seats for your team?", @@ -660,7 +660,7 @@ "Landing_Page_PreBuiltApplicationModules": "Pre-Built Application Modules which include most common web application requirements.", "Landing_Page_ChatModule": "Chat", "Landing_Page_DocsModule": "Docs", - "Landing_Page_FileManagementModule": "Docs", + "Landing_Page_FileManagementModule": "File Management", "Landing_Page_CustomerStory_1": "ABP Commercial allowed SC Ventures to deliver a bank-grade multi-tenant silo-database SaaS platform in 9 months to support the accounts receivable / accounts payable supply chain financing of significant value invoices from multiple integrated anchors. The modularity of ABP made it possible for the team to deliver in record time, pass all VAPT, and deploy the containerized microservices stack via full CI/CD and pipelines into production.", "Landing_Page_CustomerStory_2": "We are seeing the value of using ABP Commercial to reduce the overhead of custom development projects. And the team is able to unify the code pattern in different project streams. We see more potential in the framework for us to build new features faster than before. We trust we will be constantly seeing the value of leveraging ABP Commercial.", "Landing_Page_CustomerStory_3": "We love ABP. We don't have to write everything from scratch. We start from out-of-the-box features and just focus on what we really need to write. Also, ABP is well-architected and the code is high quality with fewer bugs. If we would have to write everything we needed on our own, we might have to spend years. Once more things we like is that the new version, or issue fixing, or improvement come out very soon\n every other week. We don't wait too long.", @@ -743,6 +743,8 @@ "Testimonial_ShortDescription_3": "We start from out-of-the-box features and just focus on what we really need to write.", "Testimonial_ShortDescription_4": "ABP Commercial was the best fit for our needs.", "OnlineReviewersOnAbpCommercial": "Online Reviews on ABP Commercial", - "SeeWhatToldAboutAbpCommercial": "See what has been told about ABP Commercial and write your thoughts if you want." + "SeeWhatToldAboutAbpCommercial": "See what has been told about ABP Commercial and write your thoughts if you want.", + "BlazoriseLicense": "Do we need to buy Blazorise license?", + "BlazoriseLicenseExplanation": "We have an agreement between Volosoft and Megabit, with this agreement Blazorise license is bundled with ABP Commercial products therefore our customers do not need to purchase an extra Blazorise license." } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json index b6028bb0aa..0de24176ed 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json @@ -9,15 +9,18 @@ "QuestionCount": "Fennmaradó/összes kérdés", "Unlimited": "Korlátlan", "Owners": "Tulajdonosok", + "Owner": "Tulajdonos", "AddMember": "Tag hozzáadása lehetőségre", - "AddOwner": "Tulajdonos hozzáadása", - "AddDeveloper": "Fejlesztő hozzáadása", + "AddNewOwner": "Új tulajdonos hozzáadása", + "AddNewDeveloper": "Új fejlesztő hozzáadása", "UserName": "Felhasználónév", "Name": "Név", "EmailAddress": "Email cím", "Developers": "Fejlesztők", "LicenseType": "Jogosítvány típus", "Manage": "Kezelése", + "SetDefault": "Beállítás alapértelmezettként", + "DefaultOrganization": "Alapértelmezett", "StartDate": "Kezdő dátum", "EndDate": "Befejezés dátuma", "Modules": "Modulok", @@ -60,8 +63,6 @@ "Themes": "Témák", "JoinOurNewsletter": "Csatlakozzon hírlevelünkhöz", "Send": "Küld", - "Learn": "Tanul", - "AdditionalServices": "További szolgáltatások", "WhatIsABPFramework": "MI AZ ABP KERET?", "OpenSourceBaseFramework": "Nyílt forráskódú alapkeretrendszer", "ABPFrameworkExplanation": "

Az ABP Commercial az ABP-keretrendszeren alapul, amely egy nyílt forráskódú és közösségvezérelt webalkalmazás-keretrendszer az ASP.NET Core számára.

Az ABP-keretrendszer kiváló infrastruktúrát biztosít karbantartható, bővíthető íráshoz. és tesztelhető kód a bevált gyakorlatokkal.

Beépített és integrált népszerű eszközök, amelyeket már ismer. Alacsony tanulási görbe, könnyű alkalmazkodás, kényelmes fejlődés.

", @@ -127,6 +128,8 @@ "TellUsWhatYouNeed": "Mondja el, mire van szüksége.", "YourMessage": "Az üzeneted", "YourFullName": "A teljes neved", + "FirstNameField": "Keresztnév", + "LastNameField": "Vezetéknév", "EmailField": "Email cím", "YourEmailAddress": "Az email címed", "HowMayWeHelpYou": "Hogyan segíthetünk?", @@ -157,6 +160,8 @@ "SearchQuestionPlaceholder": "Keressen a gyakran ismételt kérdések között", "WhatIsTheABPCommercial": "Mi az az ABP Commercial?", "WhatAreDifferencesThanAbpFramework": "Mi a különbség a nyílt forráskódú ABP Framework és az ABP Commercial között?", + "AbpCommercialMetaTitle": "ABP Commercial – Teljes webfejlesztési platform: {0} | ABP Commercial", + "AbpCommercialMetaDescription": "Az ABP Commercial a nyílt forráskódú ABP keretrendszerre épülő előre beépített alkalmazásmodulok, gyorsfejlesztő eszközök, UI témák és szolgáltatások készlete.", "ABPCommercialExplanation": "Az ABP Commercial prémium modulok, eszközök, témák és szolgáltatások készlete a nyílt forráskódú ABP keretrendszerre épül fel. Az ABP Commercial-t ugyanaz a csapat fejleszti és támogatja az ABP keretrendszer mögött.", "WhatAreDifferencesThanABPFrameworkExplanation": "

Az ABP-keretrendszer egy moduláris, tematikus, mikroszolgáltatásokkal kompatibilis alkalmazásfejlesztési keretrendszer az ASP.NET Core számára. Teljes architektúrát és erős infrastruktúrát biztosít ahhoz, hogy a saját üzleti kódjára összpontosítson, ahelyett, hogy megismételné magát minden új projektnél. A szoftverfejlesztés bevált gyakorlatain és a már ismert népszerű eszközökön alapul.

Az ABP keretrendszer teljesen ingyenes, nyílt forráskódú és közösségvezérelt. Ingyenes témát és néhány előre beépített modult is biztosít (pl. személyazonosság-kezelés és bérlőkezelés).

", "VisitTheFrameworkVSCommercialDocument": "További információért keresse fel a következő linket: {1} ", @@ -178,6 +183,7 @@ "ChangingLicenseType": "Frissíthetem a licenctípusomat később?", "ChangingLicenseTypeExplanation": "Magasabb licencre frissíthet, ha az aktív licencidőszakon belül kifizeti a különbözetet. Ha magasabb licenccsomagra frissít, megkapja az új csomag előnyeit, de a licencfrissítés nem módosítja a licenc lejárati dátumát. Emellett új fejlesztői helyeket is hozzáadhat meglévő licencéhez, lásd \"Hány fejlesztő dolgozhat az ABP Commercialon?\"", "LicenseExtendUpgradeDiff": "Mi a különbség a licenc kiterjesztése és a frissítés között?", + "LicenseExtendUpgradeDiffExplanation": "Meghosszabbítás: A licensz meghosszabbításával/megújításával továbbra is prémium támogatást kap, valamint kisebb-nagyobb frissítéseket kap a modulokhoz és témákhoz. Emellett folytathatja az új projektek létrehozását. És továbbra is használhatja az ABP Suite-ot, amely felgyorsítja a fejlesztést. A licensz meghosszabbításakor 1 év hozzáadódik a licensz lejárati dátumához.
Frissítés: licenszének frissítésével magasabb licenszcsomagra lép fel, amely további előnyöket biztosít. Tekintse meg a licensz-összehasonlító táblázatot , hogy megtekinthesse a licensztervek közötti különbségeket. Másrészt, amikor frissít, a licensz lejárati dátuma nem változik! A licensz lejárati dátumának meghosszabbításához meg kell hosszabbítania a licenszet.", "LicenseRenewalCost": "Mennyibe kerül a licenc megújítása 1 év után?", "LicenseRenewalCostExplanation": "A normál csapatlicenc megújítási (meghosszabbítási) ára {0} USD, a normál üzleti licencé {1} USD, a normál vállalati licencé pedig {2} USD. Ha Ön már ügyfél, jelentkezzen be fiókjába, hogy áttekintse az elérhető megújítási árakat.", "HowDoIRenewMyLicense": "Hogyan újíthatom meg a jogosítványomat?", @@ -189,9 +195,19 @@ "IsSourceCodeIncludedExplanation4": "

Ha egy modul forráskódját belefoglalja a megoldásba, akkor maximális szabadságot biztosít a modul testreszabásához. Ekkor azonban nem lehet automatikusan frissíteni a modult, amikor új verzió jelenik meg.

A licencek egyike sem tartalmazza az ABP Suite forráskódját, amely egy külső eszköz, amely kódot generál Önnek és segít az Ön fejlesztéséhez.

A licenctípusok közötti egyéb különbségekért tekintse meg az árazási oldalt.

", "ChangingDevelopers": "Módosíthatom a szervezetem regisztrált fejlesztőit a jövőben?", "ChangingDevelopersExplanation": "Amellett, hogy új fejlesztőket ad hozzá a licenchez, további költségek nélkül módosíthatja a meglévő fejlesztőket is (eltávolíthat egy fejlesztőt, és hozzáadhat egy újat ugyanarra a helyre).", + "WhatHappensWhenLicenseEnds": "Mi történik, ha lejár a licenszidőm?", + "WhatHappensWhenLicenseEndsExplanation1": "Az ABP Kereskedelmi engedély örök érvényű licensz . A licensz lejárta után folytathatja a projekt fejlesztését. És nem köteles megújítani a jogosítványát. licenszéhez egyéves frissítés és támogatási terv tartozik. Ha továbbra is új funkciókat, teljesítménynöveléseket, hibajavításokat, támogatást szeretne kapni, és továbbra is használni szeretné az ABP Suite szolgáltatást, meg kell újítania a licenszet. Ha a licensze lejár, nem részesül a következő előnyökben:", + "WhatHappensWhenLicenseEndsExplanation2": "Az ABP Commercial használatával nem hozhat létre új megoldásokat, de a meglévő alkalmazásait örökre továbbfejlesztheti.", + "WhatHappensWhenLicenseEndsExplanation3": "Frissítéseket kaphat a MINOR verzión belüli modulokhoz és témákhoz (kivéve az RC vagy Preview verziókat). Például: ha egy modul v3.2.0-s verzióját használja, továbbra is kaphat frissítéseket a modul v3.2.x-hez (v3.2.1, v3.2.5... stb.). De nem kaphat frissítéseket a következő fő- vagy mellékverzióhoz (például v3.3.0, v3.3.3, 4.xx stb.). Például amikor a licensze lejárt, a legutóbbi kiadás a 4.4.3 volt, és később, amikor a 4.4.4-es és a 4.5.0-s verzió is megjelent, akkor hozzáférhet a v4.4.X-hez, de nem elérheti a v4.5.X.", + "WhatHappensWhenLicenseEndsExplanation4": "A licensz lejárta után nem telepíthet új modulokat és témákat az ABP Commercial platformhoz.", + "WhatHappensWhenLicenseEndsExplanation5": "Az ABP Suite nem használható.", + "WhatHappensWhenLicenseEndsExplanation6": "A prémium támogatást már nem kaphatja meg.", + "WhatHappensWhenLicenseEndsExplanation7": "Meghosszabbíthatja (megújíthatja) az engedélyét, ha továbbra is igénybe kívánja venni ezeket az előnyöket. Ha a licensz lejártát követő 1 hónapon belül meghosszabbítja a licenszet, a következő kedvezmények érvényesek: Team License {0}% kedvezmény, Business License {1}% kedvezmény, Enterprise License {2}% kedvezmény.", + "WhatHappensWhenLicenseEndsExplanation8": "Az Ön által generált ABP projekteket nem tároljuk a szervereinken. Ezért az Ön felelőssége a letöltött forráskód megőrzése. Amikor a licensze lejár, nincs mód a generált ABP projekt forráskódjának lekérésére.", "WhenShouldIRenewMyLicense": "Mikor kell megújítanom a jogosítványomat?", "WhenShouldIRenewMyLicenseExplanation": "Ha a licenc lejártát követő 1 hónapon belül megújítja a licencet, a következő kedvezmények érvényesek: Csapatlicenc {0}% kedvezmény, Üzleti licenc {1}% kedvezmény, Vállalati licenc {2}% kedvezmény . Ha megújítja a licencet 1 hónappal a licenc lejárati dátuma után, a megújítási ár megegyezik a licenc vásárlási árával, és nem jár kedvezmény a megújításra.", "TrialPlan": "Van próbaterv?", + "TrialPlanExplanation": "14 napos próbaidővel rendelkezik az ABP Commercial csapat licenszéhez. További információért látogasson el ide . Továbbá a Team licenszekre 30 napos pénz-visszafizetési garanciát biztosítunk. Az első 30 napban csak visszatérítést kérhet. A Business és Enterprise licenszek esetén 30 napon belül 60%-os visszatérítést biztosítunk. Ennek az az oka, hogy a Business és Enterprise licenszek tartalmazzák az összes modul és téma teljes forráskódját.", "DoYouAcceptBankWireTransfer": "Elfogadja a banki átutalást?", "DoYouAcceptBankWireTransferExplanation": "Igen, elfogadunk banki átutalást.
Miután banki átutalással elküldte a licencdíjat, küldje el nekünk e-mailben az accounting@abp.io címre nyugtát és a kért engedély típusát. Nemzetközi bankszámlánk információi:", "HowToUpgrade": "Hogyan lehet frissíteni a meglévő alkalmazásokat, ha új verzió érhető el?", @@ -348,7 +364,6 @@ "WeWillSendYouADownloadLink": "Az e-könyv letöltésére szolgáló linket elküldtük a(z) {0} címre.
Ellenőrizze a beérkező leveleket/levélszemét/spam dobozokat!", "InvalidFormInputs": "Kérjük, írja be az űrlapon megadott érvényes adatokat.", "DDDBookEmailBody": "Köszönöm.
Könyve letöltéséhez kattintson ide.", - "FreeDDDEBook": "Ingyenes DDD e-könyv", "StartFree": "Kezdje ingyen", "FreeTrial": "Ingyenes próbaverzió", "AcceptsMarketingCommunications": " Igen, szeretnék kapni az ABP Commercial marketingkommunikációit.", @@ -359,25 +374,375 @@ "CompanySize": "Cég Méret", "Next": "Következő", "StartTrial": "Indítsa el az ingyenes próbaverziómat", - "ContactUsIssues": "Ha bármilyen problémája van, lépjen kapcsolatba velünk", + "ContactUsQuestions": "Ha bármilyen kérdése van, forduljon hozzánk", "TrialActivatedWarning": "Kedves {0}! Egy felhasználó csak 1 ingyenes próbaidőszakra jogosult. Már felhasználta a próbaidőszakot.", + "ActivationRequirement": "Még egy lépés választja el a próbaidőszak megkezdésétől.
Az adatok ellenőrzése után aktiváljuk a licenszét. A licensz aktiválása után e-mailt küldünk a következő címre: {0} . Ne aggódjon, ez a folyamat nem tart sokáig!", "SaveAndDownload": "Mentés és letöltés", "CompanyNameValidationMessage": "A cég neve túl hosszú!", "AddressValidationMessage": "A cím túl hosszú!", "TaxNoValidationMessage": "Az ADÓ/ÁFA szám túl hosszú!", "NotesValidationMessage": "A megjegyzésmező túl hosszú!", "CheckYourBillingInfo": "Számlát csak egyszer készíthet! A számla elkészítése előtt ellenőrizze számlázási adatait.", - "Volo.AbpIo.Commercial:030000": "Már felhasználta a próbaidőszakot.", - "Volo.AbpIo.Commercial:030001": "Ez a szervezetnév már létezik.", "StartYourFreeTrial": "Indítsa el az ingyenes próbaidőszakát", "TrialLicenseModelInvalidErrorMessage": "A következő mezők egyike érvénytelen: Ország neve, vállalat mérete, iparág vagy felhasználás célja.", "Trial": "Próba", "Purchased": "Vásárolt", - "PurchaseLicense": "Vásárlási engedély", + "PurchaseNow": "Vásároljon most", "PurchaseTrialLicenseMessage": "A licence lejárati dátuma: {0}.
Ha továbbra is használni szeretné az ingyenes próbaidőszak alatt létrehozott projekteket, módosítania kell a licenckulcsokat az appsettings.secrets.json fájlokban. Itt van a licenckulcsod:", "TrialLicenseExpireMessage": "Ön a próbalicencet használja, és a próbalicence a következő napon lejár: {0}.", "TryForFree": "Próbáld ki ingyen", "TrialLicenseExpiredInfo": "A próbalicensz időszaka lejárt!", - "CommercialNewsletterConfirmationMessage": "Elfogadom az Általános Szerződési Feltételeket és az Adatvédelmi irányelveket ." + "DowngradeLicensePlan": "Leválthatok alacsonyabb licenszcsomagra a jövőben?", + "DowngradeLicensePlanExplanation": "Nem módosíthatja a meglévő licenszcsomagot. De vásárolhat egy új alacsonyabb licenszcsomagot, és folytathatja a fejlesztést az új licenszen. Miután megvásárolta az alacsonyabb licenszet, csak be kell jelentkeznie az új licenszcsomagba az ABP CLI paranccsal: ` abp login -o `.", + "LicenseTransfer": "Egy licenszet át lehet adni egyik fejlesztőről a másikra?", + "LicenseTransferExplanation": "Igen! licensz vásárlásakor Ön lesz a licensz tulajdonosa, így hozzáférhet a szervezetkezelési oldalhoz. Egy szervezetnek tulajdonosi és fejlesztői szerepei vannak. A tulajdonosok kezelhetik a fejlesztői helyeket, és fejlesztőket rendelhetnek hozzá. Minden kijelölt fejlesztő az ABP CLI paranccsal jelentkezik be a rendszerbe, és fejlesztési és támogatási jogosultságokkal rendelkezik.", + "UserOwnerDescription": "A szervezet „Tulajdonosa” a fiók adminisztrátora. licenszek vásárlásával és fejlesztők kiosztásával irányítja a szervezetet. A „Tulajdonos” nem írhat kódot az ABP Commercial projektekben, nem töltheti le az ABP mintaprojekteket, és nem tehet fel kérdéseket a támogatási webhelyen. Ha mindezeket meg akarja tenni, fel kell vennie magát fejlesztőként is.", + "UserDeveloperDescription": "A „fejlesztők” kódot írhatnak az ABP Commercial projektekben, letölthetik az ABP mintaprojekteket, és kérdéseket tehetnek fel a támogatási webhelyen. Másrészt a „Fejlesztők” nem kezelhetik ezt a szervezetet.", + "RemoveCurrentUserFromOrganizationWarningMessage": "Ön eltávolítja magát saját szervezetéből. A továbbiakban nem fogja tudni kezelni ezt a szervezetet, megerősíti?", + "RenewExistingOrganizationOrCreateNewOneMessage": "Az alábbi \"Meghosszabbítás most\" gomb(ok)ra kattintva megújíthatja szervezete(i) licenszét, és így 1 évvel meghosszabbíthatja a licensz lejárati dátumát. Ha folytatja a fizetést, új szervezete lesz. Új szervezetnél szeretné folytatni?", + "PurchaseTrialOrganizationOrCreateNewOneMessage": "Van próbaengedélyed. A próbalicensz megvásárlásához kattintson a Vásárlás most gombra. Ha folytatja a fizetést, új szervezete lesz. Új szervezetnél szeretné folytatni?", + "ExtendNow": "Hosszabbítsa meg most", + "CreateNewOrganization": "Hozzon létre egy új szervezetet", + "RenewLicenseEarly": "Ha korán megújítom a jogosítványomat, megkapom a teljes évet?", + "RenewLicenseEarylExplanation": "Ha megújítja a licenszet a licensz lejárati dátuma előtt, 1 év hozzáadódik a licensz lejárati dátumához. Például, ha licensze {0}-06-06-án lejár, és {0}-01-01-én újítja meg, az új licensz lejárati dátuma {1}-06-06.", + "OpenSourceWebApplication": "Nyílt forráskódú webes alkalmazás", + "CompleteWebDevelopment": "Komplett webfejlesztés", + "ABPFrameworkDescription": "Az ABP Framework egy komplett infrastruktúra modern webalkalmazások létrehozásához a szoftverfejlesztés bevált gyakorlatainak és konvencióinak követésével.", + "CommunityDescription": "Ossza meg tapasztalatait az ABP keretrendszerrel!", + "GetStarted": "Fogj neki", + "Views": "nézetek", + "LatestPosts": "Legutóbbi bejegyzések", + "PreBuiltApplication": "Előre beépített alkalmazás", + "DatabaseProviders": "Adatbázis-szolgáltatók", + "UIFrameworks": "UI keretrendszerek", + "UsefulLinks": "Hasznos Linkek", + "Platform": "Felület", + "CoolestCompaniesUseABPCommercial": "A legmenőbb cégek már használják az ABP Commercial-t.", + "UserInterface": "Felhasználói felület", + "APIGateway": "API átjáró", + "Microservice": "Mikroszolgáltatás", + "Database": "Adatbázis", + "Architecture": "Építészet", + "MicroserviceArchitectureExplanation": "Ez egy teljes megoldás-architektúra, amely több alkalmazásból, API-átjáróból, mikroszolgáltatásból és adatbázisokból áll, hogy a legújabb technológiákkal skálázható mikroszolgáltatási megoldást építsenek ki.", + "BusinessLogic": "Üzleti logika", + "DataAccessLayer": "Adatelérési réteg", + "Monolith": "Monolit", + "ModularArchitectureExplanation": "Ez az indítási sablon egy réteges, moduláris és DDD-alapú megoldás-architektúrát kínál tiszta és karbantartható kódbázis létrehozásához.", + "SeeDetails": "Lásd a részleteket", + "SeeDocumentation": "Nézze meg a Dokumentációt", + "Bs5Compatible": "Bootstrap 5 kompatibilis professzionális téma, tökéletes az adminisztrátori webhelyhez.", + "LeptonXTheme": "LeptonX téma", + "LeptonXDark": "LeptonX sötét", + "LeptonXLight": "LeptonX Light", + "LeptonXSemiDark": "LeptonX félsötét", + "BuiltOnBs5Library": "Bootstrap 5 könyvtárra építve", + "FullyCompatibleWithBs5": "100%-ban kompatibilis a Bootstrap 5 HTML struktúrájával és CSS osztályaival", + "ResponsiveAndMobileCompatible": "Reszponzív, mobil kompatibilis, RTL támogatás", + "ProvidesStylesForDatatables": "Stílusokat biztosít az adattáblákhoz", + "MultipleLayoutOptions": "Többféle elrendezési lehetőség", + "EasilyInstallAndUpgrade": "Könnyen telepíthető és frissíthető", + "SupportForum": "Támogatói fórum", + "TrustedBy": "Megbízható", + "OurPricing": "Áraink", + "Plans": "Tervek", + "NameSurname": "Név vezetéknév", + "Unspecified": "Meg nem határozott", + "LicenceType": "Licensz típus", + "LicenseDiscountWarning": "EZ A KEDVEZMÉNYOLDAL ALAPÉRTELMEZETT KEDVEZMÉNYKÓDOT HASZNÁL ÉS A VOLOSOFT FEJLESZTŐK SZÁMÁRA VAN. AZ ALÁBBI VÁSÁRLÁSI LINKEK NEM MŰKÖDNEK.", + "DiscountedLicenseExplanation": "Ezek a licenszárak kis startup vállalkozásokra, egyéni fejlesztőkre, hallgatókra, nonprofit szervezetekre és projektekre vonatkoznak!", + "General": "Általános", + "License": "Licensz", + "Development": "Fejlődés", + "Payment": "Fizetés", + "WatchExplainerVideo": "Találkozzunk! Nézze meg a magyarázó videót", + "LightDarkAndSemiDarkThemes": "Világos, Sötét és Félsötét", + "LeptonXThemeExplanation": "A Lepton Theme módosíthatja a témát a rendszerbeállításoknak megfelelően.", + "PRO": "PRO", + "WelcomeToABPCommercial": "Üdvözöljük az ABP Commercial oldalán!", + "YourAccountDetails": "Fiók adatai", + "OrganizationName": "Szervezet neve", + "AddDevelopers": "Adjon hozzá fejlesztőket", + "StartDevelopment": "Kezdje el a fejlesztést", + "CreateAndRunApplicationUsingStartupTemplate": "Ismerje meg, hogyan hozhat létre és futtathat új webalkalmazást az ABP Commercial indítási sablon használatával.", + "CommunityDescription2": "A Community.abp.io egy olyan hely, ahol az emberek megoszthatnak ABP-vel kapcsolatos cikkeket. Keressen cikkeket, oktatóanyagokat, kódmintákat, esettanulmányokat, és találkozzon Önnel azonos sávban élő emberekkel.", + "UseABPSuiteExplanation": "Az ABP Suite segítségével töltse le a modulok és témák forráskódját.", + "ManageModulesWithSuite": "ABP-moduljait a Suite segítségével is kezelheti.", + "LearnHowToInstallSuite": "Ismerje meg az ABP Suite telepítését és használatát.", + "SeeMore": "Többet látni", + "SeeLess": "Lásd Kevesebb", + "LayeredSolutionStructure": "Réteges megoldás szerkezete", + "LayeredSolutionStructureExplanation": "A megoldás a tartományvezérelt tervezési elvek és minták alapján rétegzett, hogy elkülönítse az üzleti logikát az infrastruktúrától és az integrációktól, és maximalizálja a kód karbantarthatóságát és újrafelhasználhatóságát. Az ABP Framework már tartalmaz absztrakciókat, alaposztályokat és útmutatókat a DDD tényleges megvalósításához az alkalmazásban.", + "MultipleUIOptions": "Több felhasználói felület opció", + "MultipleUIOptionsExplanation": "Szeretjük a felhasználói felület létrehozásának különböző módjait. Ez az indítási megoldás három különböző felhasználói felületi keretrendszert biztosít az üzleti alkalmazás számára.", + "MultipleDatabaseOptions": "Több adatbázis-beállítás", + "MultipleDatabaseOptionsExplanation": "Két adatbázis-szolgáltató lehetősége van (amellett, hogy mindkettőt egyetlen alkalmazásban használhatja). Az Entity Framework Core segítségével bármilyen relációs adatbázissal dolgozhat, és opcionálisan használja a Dappert, ha alacsony szintű lekérdezéseket kell írnia a jobb teljesítmény érdekében. A MongoDB egy másik lehetőség, ha dokumentum alapú NoSQL adatbázist kell használnia. Noha ezek a szolgáltatók jól integráltak, absztraktáltak és előre konfiguráltak, valójában bármilyen adatbázis-rendszerrel kapcsolatba léphet, amelyet a .NET-tel használhat.", + "ModularArchitectureExplanation2": "A a legfőbb szempont az ABP.IO platformon. Az alkalmazás összes funkciója jól elkülönített opcionális modulokra van felosztva. Az indítási megoldás már előre telepítve tartalmazza az alapvető ABP Commercial modulokat . Saját modulokat is létrehozhat, hogy moduláris rendszert építsen fel saját alkalmazásához.", + "MultiTenancyForSaasBusiness": "Többérlős felépítés az Ön SaaS-üzleteihez", + "MultiTenancyForSaasBusinessExplanation": "Az ABP Commercial teljes körű, többbérlős rendszert biztosít SaaS (Software-as-a-Service) rendszereinek létrehozásához. Lehetővé teszi a bérlők számára, hogy megosszák vagy rendelkezzenek saját adatbázisokkal az on-the-fly adatbázis-létrehozó és migrációs rendszerrel.", + "MicroserviceStartupSolution": "Mikroszolgáltatás indítási megoldás", + "MicroserviceArchitectureExplanation2": "Beszerezheti következő mikroszolgáltatási rendszeréhez, hogy kihasználhassa az előre elkészített alapmegoldást és tiszta élményt.", + "PreIntegratedTools": "Előre integrálva a népszerű eszközökbe", + "PreIntegratedToolsExplanation": "A megoldás már integrálva van az ipari szabványos eszközökbe és technológiákba, miközben Ön bármikor módosíthatja azokat, és integrálhatja kedvenc eszközeibe.", + "SingleSignOnAuthenticationServer": "Egyszeri bejelentkezéses hitelesítési kiszolgáló", + "SingleSignOnAuthenticationServerExplanation": "A megoldás rendelkezik egy hitelesítési kiszolgálóalkalmazással, amelyet a többi alkalmazás egyszeri bejelentkezési kiszolgálóként használ API hozzáférés-kezelési szolgáltatásokkal. Az IdentityServeren alapul.", + "WebAppsWithGateways": "2 webalkalmazás 2 API átjáróval", + "WebAppsWithGatewaysExplanation": "A megoldás két webalkalmazást tartalmaz, mindegyik rendelkezik dedikált API-átjáróval (BFF - Backend For Frontend minta).", + "BackOfficeApplication": "Back Office alkalmazás", + "BackOfficeApplicationExplanation": "A rendszer tényleges webalkalmazása, több felhasználói felületi keretbeállítással. Bármilyen üzleti alkalmazást létrehozhat.", + "LandingWebsite": "Landing webhely", + "LandingWebsiteExplanation": "Általános célú/nyilvános webhely, amely többféle célra is használható, például a cég bemutatására, termékei értékesítésére stb.", + "ABPFrameworkEBook": "Mastering ABP Framework e-book", + "MasteringAbpFrameworkEBookDescription": "Tartalmazza az ABP kereskedelmi licenszét", + "FullName": "Teljes név", + "LicenseTypeNotCorrect": "A licensz típusa nem megfelelő!", + "Trainings": "Képzések", + "ChooseTrainingPlaceholder": "Válaszd ki a képzést...", + "DoYouNeedTrainings": "Szüksége van egy ilyen képzésre?", + "DoYouNeedTraining": "Szüksége van a(z) {0} képzésére?", + "GetInTouchUs": "Vegye fel velünk a kapcsolatot", + "ForMoreInformationClickHere": "További információért kattintson ide.", + "IsGetOnboardingTraining": "Szeretnél bevezető és webalkalmazás-fejlesztő tréningen részt venni?", + "OnboardingWebApplicationDevelopmentTrainingMessage": "Képzési naptárának ütemezéséhez a szervezet létrehozása után vegye fel a kapcsolatot a következővel: {0}", + "CustomPurchaseMessage": "A következő lépéshez kattintson a(z) {0} gombra, és lépjen kapcsolatba velünk.", + "Note": "jegyzet", + "AdditionalNote": "Kiegészítő megjegyzés", + "OnboardingTrainingFaqTitle": "Van ABP bevezető képzése?", + "OnboardingTrainingFaqExplanation": "Igen, van ABP képzési szolgáltatásunk, amely segít abban, hogy ABP-projektjét gyorsan elindítsa. Megtanulja az ABP-t az ABP törzscsoport egyik tagjától, és elsajátítja az ABP-projekt megkezdéséhez szükséges készségeket. A bevezető tréningen elmagyarázzuk, hogyan állítsa be fejlesztői környezetét, telepítse a szükséges eszközöket, készítsen egy teljesen működőképes CRUD oldalt. A tréning élőben lesz, a Zoom alkalmazást használjuk, valamint nyitottak vagyunk más online találkozóplatformok használatára is. A képzés nyelve angol lesz. A foglalkozások során felteheti kérdéseit az ABP-vel kapcsolatban is. Mindkét fél számára megfelelő időpontot és dátumot tervezünk. További információért lépjen kapcsolatba velünk az info@abp.io címen .", + "AddBasket": "Kosárba helyez", + "SendTrainingRequest": "Képzési kérelem küldése", + "OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* A dokumentum angol nyelvű változata a legfrissebb, és minden vitában az angol verzió az irányadó.", + "Pricing_Page_Title": "Tervek és árak", + "Pricing_Page_Description": "Válassza ki azokat a szolgáltatásokat és funkciókat, amelyekre ma vállalkozásának szüksége van. Vásároljon ABP kereskedelmi licenszet, és hozzon létre korlátlan számú projektet.", + "Pricing_Page_HurryUp": "Siess!", + "Pricing_Page_BuyLicense": "Vásároljon licenszet 2021-es árakon január 16-ig!", + "Pricing_Page_ValidForExistingCustomers": "Meglévő ügyfelekre és licenszmegújításokra is érvényes.", + "Pricing_Page_Hint1": "A licensz ára bizonyos számú fejlesztői helyet tartalmaz. Ha több fejlesztője van, bármikor vásárolhat további üléseket.", + "Pricing_Page_Hint2": "További fejlesztői licenszeket vásárolhat most vagy a jövőben. A licenszek szék alapúak, így áthelyezhet egy helyet egy fejlesztőtől a másikhoz.", + "Pricing_Page_Hint3": "licenszével korlátlan számú különböző terméket fejleszthet.", + "Pricing_Page_Hint4": "Az ABP Suite egy olyan eszköz, amely segíti a fejlesztést a termelékenység javítása érdekében. Támogatja a CRUD oldalak generálását és új projektek létrehozását.", + "Pricing_Page_Hint5": "Az összes előre beépített modult használhatja alkalmazásaiban.", + "Pricing_Page_Hint6": "Az összes előre elkészített témát használhatja alkalmazásaiban.", + "Pricing_Page_Hint7": "Az indítási sablon a Visual Studiohoz, amellyel azonnal elkezdheti a projektet. Az összes alapvető modul hozzáadásra és előre konfigurálva van az Ön számára.", + "Pricing_Page_Hint8": "Az ABP-keretrendszer elsajátítása című e-könyv elmagyarázza, hogyan lehet .NET-megoldásokat megvalósítani a legjobb gyakorlatokkal. A könyvet az Amazon.com oldalon árusítják, és licensze keretein belül ingyenesen letöltheti a könyvet.", + "Pricing_Page_Hint9": "Bármely modul forráskódja letölthető. Érdemes lehet a forráskódot hozzáadni a megoldáshoz, hogy radikális változtatásokat hajtson végre, vagy biztonsági okokból egyszerűen megtarthatja magának.", + "Pricing_Page_Hint10": "A licenszek életre szólnak. Ez azt jelenti, hogy örökké folytathatja az alkalmazás fejlesztését. A legfrissebb verzióhoz való hozzáférés és a támogatás megszerzése a licensz időtartamán belül biztosított (1 év, ha nem újítja meg).", + "Pricing_Page_Hint11": "Nincs korlátozás a telepítésre! Tetszőleges számú kiszolgálóra telepítheti, beleértve a felhőszolgáltatásokat vagy a helyszíni szolgáltatásokat.", + "Pricing_Page_Hint12": "A modulokat, témákat és eszközöket frissítheti a legújabb verzióra az aktív licenszidőszakon belül. A licensz lejárta után meg kell újítania, hogy továbbra is frissítéseket kaphasson a hibajavításokról, új funkciókról és fejlesztésekről.", + "Pricing_Page_Hint13": "A prémium támogatást egy évre kaphatja meg (a licensz megújításával meghosszabbítható).", + "Pricing_Page_Hint14": "A csapat- és üzleti licenszeknek van incidens/kérdésszám korlátja. Ha további fejlesztői licenszeket vásárol, az incidensek korlátja fejlesztőnként {0}-kal (a csapatlicensz esetén) vagy {1}-kal (az üzleti licensz esetében) nő.", + "Pricing_Page_Hint15": "Csak az Enterprise licensz tartalmaz magántámogatást. Küldhet e-mailt közvetlenül az ABP csapatának, vagy kérdéseket tehet fel a support.abp.io oldalon privát jegy opcióval. A privát jegyek nem láthatók a nyilvánosság számára.", + "Pricing_Page_Hint16": "Letöltheti az összes ABP téma forráskódját. Érdemes lehet a forráskódot hozzáadni a megoldáshoz, hogy radikális változtatásokat hajtson végre, vagy biztonsági okokból egyszerűen megtarthatja magának.", + "Pricing_Page_Testimonial_1": "Az ABP Commercial lehetővé tette az SC Ventures számára, hogy 9 hónapon belül egy banki szintű többbérlős silo-adatbázis SaaS-platformot szállítson a követelések/tartozások ellátási láncának támogatására, jelentős értékű számlák több integrált horgonyból történő finanszírozásához. Az ABP modularitása lehetővé tette a csapat számára, hogy rekordidő alatt szállítsa, átadja az összes VAPT-t, és a konténeres mikroszolgáltatások veremét teljes CI/CD-n és csővezetékeken keresztül üzembe helyezze.", + "Pricing_Page_Testimonial_2": "Látjuk az ABP Commercial használatának értékét az egyedi fejlesztési projektek általános költségeinek csökkentésére. És a csapat képes egységesíteni a kódmintát a különböző projektfolyamokban. Több lehetőséget látunk a keretrendszerben arra, hogy a korábbinál gyorsabban építsünk új funkciókat. Bízunk benne, hogy folyamatosan látni fogjuk az ABP Commercial kihasználásának értékét.", + "Pricing_Page_Testimonial_3": "Szeretjük az ABP-t. Nem kell mindent a nulláról írnunk. A beépített funkciókból indulunk ki, és csak arra koncentrálunk, amit valóban meg kell írnunk. Ezenkívül az ABP jól felépített, és a kód kiváló minőségű, kevesebb hibával. Ha magunknak kellene megírnunk mindent, amire szükségünk van, akkor lehet, hogy éveket kellene töltenünk. Még egyszer, amit szeretünk, az az, hogy az új verzió, a hibajavítás vagy a fejlesztés nagyon hamar, minden második héten megjelenik. Nem várunk túl sokáig.", + "Pricing_Page_Testimonial_4": "Az ABP Commercial egy fantasztikus termék, amelyet ajánlunk. Kereskedelmi termékek, amelyeket ügyfeleink számára egyetlen konfigurálható platformon értékesíthetnek. Az ugrásszerű indítás, amelyet a keret és az eszközök biztosítanak bármely csapat számára, minden centet megér. Az ABP Commercial volt a legjobban megfelelő az igényeinknek.", + "Pricing_Page_Testimonial_5": "Az ABP Framework nem csak keretrendszer, hanem útmutató is a projektfejlesztéshez/menedzsmenthez, mert DDD, GenericRepository, DI, Microservice és Modularity képzést biztosít. Ha magát a keretrendszert nem is használja, fejlesztheti magát a docs.abp.io-val, amely jól és professzionálisan elkészített (OpenIddict, Redis, Quartz stb.) leírás. Mivel sok minden előre be van építve, jelentősen lerövidíti a projektfejlesztési időt (például bejelentkezési oldal, kivételkezelés, adatszűrés, aéapadatok, audit naplózás, lokalizáció, automatikus API vezérlő stb.). Alkalmazásunkból példaként a Helyi Event Bus-t használtam az állomány karbantartására. Így a rendelési mozgásokat eseményekkel tudom a készlethez igazítani. Csodálatos, hogy nem veszítünk időt a CreationTime-ra, a CreatorId-re. Automatikusan feltöltődnek.", + "AbpBookDownloadArea_ClaimYourEBook": "Igényelje Mastering ABP Framework e-könyvét", + "AddMemberModal_Warning_1": "Ha a hozzáadni kívánt felhasználónév nem létezik a rendszerben, kérje meg csapattagját, hogy regisztráljon a (z) {0} webhelyen, és ossza meg fiókja felhasználónevét Önnel.", + "MyOrganizations_Detail_WelcomeMessage": "Üdvözöljük szervezetében, {0}", + "MyOrganizations_Detail_OrganizationManagement": "Szervezetmenedzsment", + "OrganizationDisplayName": "Szervezet megjelenített neve", + "MyOrganizations_Detail_EditDisplayName": "Megjelenítési név szerkesztése", + "MyOrganizations_Detail_UpgradeYourLicense": "Frissítse licenszét", + "MyOrganizations_Detail_LicenseStartAndExpiryDate": "licensz kezdő dátuma – lejárati dátum", + "MyOrganizations_Detail_OwnerRightInfo": "Ön {1} tulajdonosi jogának {0}-át használja.", + "MyOrganizations_Detail_CopyApiKey": "Másolja ki a kulcsot", + "MyOrganizations_Detail_ApiKeyDescription": "Az API-kulcs a(z) {1} webhelyen tárolt PRO-csomagok tokenje.", + "MyOrganizations_Detail_YourPrivateNugetSource": "Az Ön privát NuGet-forrása {0}", + "MyOrganizations_Detail_PrivateNugetSourceWarning": "Ez automatikusan hozzáadódik feedként a NuGet.Config-hoz az ABP-megoldásban. Ne ossza meg privát kulcsát illetéktelen felhasználókkal!", + "MyOrganizations_Detail_DeveloperSeatInfo": "Ön {1} fejlesztői helyéből {0}-ot használ.", + "NeedMoreSeatsForYourTeam": "Több hozzáférésre van szüksége a csapatának?", + "MyOrganizations_Detail_PricePerYear": "{0}/év", + "MyOrganizations_Detail_PurchaseDeveloperSeats": "Vásároljon fejlesztői hozzáféréseket", + "Invoices": "Számlák", + "RequestInvoice": "Kérjen számlát", + "OrderNumber": "Rendelésszám", + "Date": "Dátum", + "Products": "Termékek", + "TotalPrice": "Teljes ár", + "ThereIsNoInvoice": "Nincs számla", + "MyOrganizations_Detail_PaymentProviderInfo": "Ha licenszét a (z) {0} átjárón keresztül vásárolta, az elküldi a PDF számlát az Ön e-mail címére, lásd: {0} számlázás.", + "MyOrganizations_Detail_PayUInfo": "Ha a PayU átjárón keresztül vásárolt, kattintson a \"Számla kérése\" gombra, és töltse ki a számlázási adatokat.", + "MyOrganizations_Detail_ConclusionInfo": "Számlakérését {0} munkanapon belül lezárjuk.", + "ExtendYourLicense": "Hosszabbítsa meg {0} licenszét", + "Continue": "Folytatni", + "PurchaseLicense": "Vásárlási engedély", + "DownloadInvoiceModal_DownloadInvoice": "Számla letöltése", + "DownloadInvoiceModal_SaveInformationOnlyOnce": "Számlázási adatait csak egyszer mentheti el.", + "InvoiceModal_EnterCompanyName": "Adja meg cégének hivatalos nevét...", + "InvoiceModal_EnterCompanyAddress": "Adja meg cége jogi címét...", + "InvoiceModal_EnterTaxNumber": "Adja meg adószámát, ha van...", + "RequestInvoiceModal_EnterNotes": "Írja be a számlával kapcsolatos extra üzenetet...", + "PrePayment_PayWithIyzico": "Iyzico-val fog fizetni", + "ContinueToCheckout": "Tovább a Pénztárhoz", + "PrePayment_IyzicoRedirectionInfo": "A vásárlás biztonságos befejezéséhez átirányítjuk az Iyzico Payment Gateway oldalra.", + "PrePayment_IyzicoAcceptVisaAndMasterCard": "Az Iyzico Visa és MasterCard kártyákat fogad el.", + "Purchase": "Vásárlás", + "AcceptTermsAndConditions": "Elolvastam, megértettem és elfogadom az adatvédelmi szabályzatot , a feltételeket és az EULA-t.", + "AcceptTermsAndConditionsWarningMessage": "Kérjük, fogadja el az adatvédelmi szabályzatot és a feltételeket", + "SelectGatewayToContinue": "Kérjük, válasszon egy átjárót a folytatáshoz!", + "GatewaySelection_SelectGateway": "Válasszon fizetési átjárót", + "GatewaySelection_RedirectionMessage": "Ezután átirányítjuk a kiválasztott fizetési átjáró webhelyére a tranzakcióhoz.", + "PaymentSucceed_PaymentSuccessMessage": "Sikeres fizetés", + "PaymentSucceed_ThanksForPurchase": "Köszönjük a vásárlást!", + "PaymentSucceed_CreateYourOrganization": "Hozd létre a szervezetedet", + "PaymentSucceed_AddMeAsDeveloper": "Én is fejlesztő vagyok, vegyen fel fejlesztőként a szervezetembe.", + "PaymentSucceed_CreateOrganization": "Szervezet létrehozása", + "PaymentSucceed_OrganizationDescription": "Egy szervezet fejlesztőkből és tulajdonosokból áll. A fejlesztők olyan felhasználók, akik kódot írnak az ABP projektben, és részesülnek a {1} webhely előnyeiből. A tulajdonosok olyan felhasználók, akik fejlesztői helyeket osztanak ki és kezelik a licenszelést.", + "PaymentSucceed_ViewOrganization": "Kattintson ide a szervezet megtekintéséhez", + "Purchase_TotalAnnualPrice": "ÖSSZESEN (éves díj)", + "Purchase_TrainingPrice": "Képzési ár", + "Purchase_OnboardingTraining": "ABP Onboarding és webalkalmazás-fejlesztés élő képzés", + "TotalDeveloperPrice": "Teljes fejlesztői ár", + "Purchase_PricePerDeveloper": "{0} {1} fejlesztőnként", + "Purchase_IncludedDeveloperInfo": "{0} {1} tartalmazza.", + "Purchase_LicenseExtraDeveloperPurchaseMessage": "A(z) {0} licensz {1} fejlesztőt tartalmaz. Hozzáadhat további fejlesztőket most vagy később.", + "StartupTemplates_Page_Title": "Az indítási sablonok", + "StartupTemplates_Page_Description": "Az ABP Commercial lehetővé teszi, hogy bármilyen bonyolultságú megoldást készítsen. Két fő előre beépített indítási megoldást kínál. Kiválaszthatja az igényeinek megfelelőt, és ráépítheti saját egyedi megoldását.", + "MicroserviceStartupSolutionForDotnet": "Microservice Startup Solution for .NET", + "MonolithSolutionForDotnet": "Monolith (moduláris) megoldás .NET-hez", + "TrainingDetailsHeaderInfo_TrainingHour": "{0} óra", + "Trainings_Content": "A képzés tartalma", + "Trial_Page_StartYourFreeTrial": "Indítsa el az ingyenes próbaverziót", + "TrialLicenseFeatures": "Élvezheti az ABP összes kereskedelmi funkcióját", + "TrialPeriodDays": "{0} napos csapatengedéllyel rendelkezik", + "TrialForumSupportIncident": "{0} fórumtámogatási incidense lesz", + "Contact_Page_Title": "Lépjen kapcsolatba az ABP fejlesztési csapatával", + "Contact_Page_Description": "Lépjen kapcsolatba az ABP Development csapatával, ha segítségre van szüksége, vagy ossza meg gondolatait és véleményét! Az ABP támogatási csapata készen áll a segítségére.", + "Demo_Page_Title": "Hozzon létre egy demót", + "Demo_Page_Description": "Hozzon létre egy ingyenes demót az ABP Commercial indítósablon segítségével létrehozott mintaalkalmazás megtekintéséhez. Ne ismételje magát a gyakori alkalmazási követelmények miatt.", + "Discounted_Page_Title": "Kedvezményes ár", + "Discounted_Page_Description": "Válassza ki azokat a szolgáltatásokat és funkciókat, amelyekre ma vállalkozásának szüksége van. Vásároljon ABP kereskedelmi licenszet, és hozzon létre korlátlan számú projektet", + "Faq_Page_Title": "Gyakran Ismételt Kérdések (GYIK)", + "Faq_Page_Description": "Van kérdésed? Keressen gyakran ismételt kérdésekre, vagy tegyen fel nekünk kérdést a kapcsolatfelvételi űrlap segítségével.", + "Faq_Page_SwiftCode": "Swift kód", + "Faq_Page_BankName": "A bank neve", + "Faq_Page_AccountName": "Felhasználónév", + "Faq_Page_AccountNumber": "Számlaszám", + "Faq_Page_Currency": "Valuta", + "Faq_Page_VatNumber": "Adószám", + "Faq_Page_OtherCurrenciesInfo": "A többi pénznemhez lásd az összes fiókot", + "ModuleDetail_Page_Title": "Modul részletei – {0}", + "ProjectCreatedSuccess_Page_Title": "A projekt létrehozva", + "ProjectCreatedSuccess_Page_Description": "ABP projektje sikeresen létrejött!", + "Suite_Page_Title": "ABP Suite – CRUD oldalak létrehozása", + "Suite_Page_Description": "Az ABP Commercial gyors alkalmazásfejlesztési eszközöket biztosít a fejlesztők termelékenységének növelése érdekében. Az ABP Suite segítségével könnyedén hozhat létre CRUD oldalakat.", + "Themes_Page_Title": "Modern és funkcionális felhasználói felület témák", + "Themes_Page_Description": "Az ABP Commercial több professzionális, modern felhasználói felület témát kínál. Hozzon létre egy ingyenes bemutatót, hogy gyorsan áttekintse, hogyan néz ki a felhasználói felület.", + "Tools_Page_Title": "Gyors alkalmazásfejlesztő eszközök", + "Tools_Page_Description": "Az ABP Commercial gyors alkalmazásfejlesztési eszközöket biztosít a fejlesztők termelékenységének növelése érdekében. Az ABP Suite segítségével könnyedén hozhat létre CRUD oldalakat.", + "DeveloperPrice": "Fejlesztői ár", + "AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} fejlesztő", + "LicenseRemainingDays": "{0} napig", + "ExtendPaymentInfoSection_Description": "A licensz meghosszabbításával/megújításával továbbra is prémium támogatást kap. Emellett kisebb-nagyobb frissítéseket is kaphat a modulokhoz és témákhoz. Folytathatja új projektek létrehozását. És továbbra is használhatja az ABP Suite -ot, amely felgyorsítja a fejlesztést.", + "LicenseRenewalPrice": "Licensz megújítási ára", + "LicensePrice": "Licensz ára", + "TrialLicensePaymentInfoSection_Description": "Licensz vásárlása: licensz megvásárlásával továbbra is prémium támogatást kap . Emellett kisebb-nagyobb frissítéseket is kaphat a modulokhoz és témákhoz. Folytathatja új projektek létrehozását. És továbbra is használhatja az ABP Suite -ot, amely felgyorsítja a fejlesztést.
Tekintse meg a licensz-összehasonlító táblázatot a licensztípusok közötti különbségek ellenőrzéséhez.", + "SelectTargetLicense": "Válassza a Target License lehetőséget", + "UpgradePaymentInfoSection_ExtendMyLicenseForOneYear": "Igen, meghosszabbítom az engedélyem lejárati dátumát 1 évvel.", + "UpgradePaymentInfoSection_WantToExtendLicense": "Szeretné meghosszabbítani licenszét további {0} évre?", + "UpgradePaymentInfoSection_UpgradingWillNotExtendLicense": "A frissítés nem hosszabbítja meg a licensz lejárati dátumát!", + "UpgradePaymentInfoSection_LicenseUpgradeDescription": "Licenszének frissítésével magasabb licensztípusra lép, amely további előnyökhöz juttatja Önt. Tekintse meg a licensz-összehasonlító táblázatot a licensztípusok közötti különbségek ellenőrzéséhez.", + "Landing_Page_CustomerStories": "Ügyféltörténetek", + "Landing_Page_OurGreatCustomers": "Nagyszerű vásárlóink", + "Landing_Page_WebApplicationFramework": "Web Application Framework", + "Landing_Page_WebDevelopmentPlatform": "Webfejlesztési platform", + "Landing_Page_CompleteWebDevelopmentPlatform": "Komplett webfejlesztési platform", + "Landing_Page_TryFreeDemo": "Próbáld ki az ingyenes demót", + "Landing_Page_StartingPointForWebApplications": "Az ASP.NET Core alapú webes alkalmazások kiindulópontja! A legjobb webfejlesztés érdekében az ABP-keretrendszeren alapul.", + "Landing_Page_AbpProvidesSoftwareInfrastructure": "Az ABP Framework szoftver infrastruktúrát biztosít kiváló webalkalmazások fejlesztéséhez a legjobb gyakorlatokkal.", + "Landing_Page_MicroserviceCompatibleArchitecture": "Mikroszolgáltatással kompatibilis architektúra", + "Landing_Page_PreBuiltApplicationModulesAndThemes": "Előre elkészített alkalmazásmodulok és témák", + "Landing_Page_MultiTenantArchitecture": "Több bérlős építészet", + "Landing_Page_MultiTenancyDescription": "SaaS alkalmazások egyszerűen! Integrált több bérlés az adatbázistól a felhasználói felületig.", + "Landing_Page_DDDIntroduction": "DDD minták és elvek alapján tervezték és fejlesztették. Réteges modellt biztosít az alkalmazáshoz.", + "Landing_Page_CrossCuttingConcernsInfo": "Teljes infrastruktúra az engedélyezéshez, érvényesítéshez, kivételkezeléshez, gyorsítótárazáshoz, auditnaplózáshoz, tranzakciókezeléshez és még sok máshoz.", + "Landing_Page_PreBuiltApplicationModules": "Előre beépített alkalmazásmodulok, amelyek a leggyakoribb webalkalmazási követelményeket tartalmazzák.", + "Landing_Page_ChatModule": "Csevegés", + "Landing_Page_DocsModule": "Dokumentumok", + "Landing_Page_FileManagementModule": "Dokumentumok", + "Landing_Page_CustomerStory_1": "Az ABP Commercial lehetővé tette az SC Ventures számára, hogy 9 hónapon belül egy banki szintű többbérlős silo-adatbázis SaaS-platformot szállítson a követelések/tartozások ellátási láncának támogatására, jelentős értékű számlák több integrált horgonyból történő finanszírozásához. Az ABP modularitása lehetővé tette a csapat számára, hogy rekordidő alatt szállítson, átadja az összes VAPT-t, és a konténeres mikroszolgáltatások veremét teljes CI/CD-n és csővezetékeken keresztül üzembe helyezze.", + "Landing_Page_CustomerStory_2": "Látjuk az ABP Commercial használatának értékét az egyedi fejlesztési projektek általános költségeinek csökkentésére. És a csapat képes egységesíteni a kódmintát a különböző projektfolyamokban. Több lehetőséget látunk a keretrendszerben arra, hogy a korábbinál gyorsabban építsünk új funkciókat. Bízunk benne, hogy folyamatosan látni fogjuk az ABP Commercial kihasználásának értékét.", + "Landing_Page_CustomerStory_3": "Szeretjük az ABP-t. Nem kell mindent a nulláról írnunk. A beépített funkciókból indulunk ki, és csak arra koncentrálunk, amit valóban meg kell írnunk. Ezenkívül az ABP jól felépített, és a kód kiváló minőségű, kevesebb hibával. Ha magunknak kellene megírnunk mindent, amire szükségünk van, akkor lehet, hogy éveket kellene töltenünk. Még egyszer, amit szeretünk, az az, hogy az új verzió, a hibajavítás vagy a fejlesztés nagyon hamar, minden második héten megjelenik. Nem várunk túl sokáig.", + "Landing_Page_CustomerStory_4": "Az ABP Commercial egy fantasztikus termék, amelyet ajánlunk. Kereskedelmi termékek, amelyeket ügyfeleink számára egyetlen konfigurálható platformon értékesíthetnek. Az ugrásszerű indítás, amelyet a keret és az eszközök biztosítanak bármely csapat számára, minden centet megér. Az ABP Commercial volt a legjobban megfelel az igényeinknek.", + "Landing_Page_AdditionalServices": "Egyedi vagy mennyiségi licensz, beépítés, élő képzés és támogatás, egyedi projektfejlesztés, meglévő projektek portolása és így tovább...", + "Landing_Page_IncludedDeveloperLicenses": "{0} fejlesztői licenszet tartalmaz", + "Landing_Page_SeeOnDemo": "Lásd a demóban", + "Landing_Page_LeptonThemes": "Lepton Themes", + "Landing_Page_AccountModuleDescription_1": "Ez a modul egy alkalmazás hitelesítési rendszerét valósítja meg;", + "Landing_Page_AccountModuleDescription_2": "Bejelentkezési oldalt biztosít a felhasználónévvel és jelszóval", + "Landing_Page_AccountModuleDescription_3": "Regisztrációs oldalt biztosít új fiók létrehozásához.", + "Landing_Page_AccountModuleDescription_4": "Elfelejtett jelszó oldalt biztosít a jelszó-visszaállítási hivatkozás e-mailben történő elküldéséhez.", + "Landing_Page_AccountModuleDescription_5": "E- mail megerősítési funkciót biztosít felhasználói felülettel.", + "Landing_Page_AccountModuleDescription_6": "Kétfaktoros hitelesítést valósít meg (SMS és e-mail).", + "Landing_Page_AccountModuleDescription_7": "Megvalósítja a felhasználói zárolást (zárolja a fiókot a beállított időtartamra, ha bizonyos számú sikertelen bejelentkezés történik érvénytelen hitelesítő adatok miatt egy bizonyos időintervallumon belül).", + "Landing_Page_AccountModuleDescription_8": "Megvalósítja az Identity Server hitelesítési kiszolgáló felhasználói felületét és funkcióit.", + "Landing_Page_AccountModuleDescription_9": "Lehetővé teszi a bérlők közötti váltást több bérlős környezetben.", + "Landing_Page_AccountModuleDescription_10": "Lehetővé teszi az alkalmazás felhasználói felületének nyelvének módosítását.", + "Landing_Page_AuditLoggingModuleDescription_1": "Ez a modul biztosítja a naplózási felületet a naplózási infrastruktúra számára. Lehetővé teszi az ellenőrzési naplóbejegyzések és entitásmódosítási naplók keresését, szűrését és megjelenítését.", + "Landing_Page_AuditLoggingModuleDescription_2": "Az ellenőrzési naplóbejegyzés kritikus adatokat tartalmaz minden ügyfélkérelemről:", + "Landing_Page_AuditLoggingModuleDescription_3": "URL, böngésző, IP-cím, ügyfélnév", + "Landing_Page_AuditLoggingModuleDescription_4": "A felhasználó", + "Landing_Page_AuditLoggingModuleDescription_5": "HTTP metódus, HTTP visszatérési állapotkód", + "Landing_Page_AuditLoggingModuleDescription_6": "Siker/kudarc, kivétel részletei, ha rendelkezésre állnak", + "Landing_Page_AuditLoggingModuleDescription_7": "Kérelem végrehajtásának időtartama", + "Landing_Page_AuditLoggingModuleDescription_8": "Az entitások létrejöttek, törölve vagy frissítve ebben a kérésben (módosított tulajdonságokkal).", + "Landing_Page_BloggingModuleDescription_1": "Ez a modul egy egyszerű blogot ad hozzá az ABP-alkalmazáshoz;", + "Landing_Page_BloggingModuleDescription_2": "Lehetővé teszi több blog létrehozását egyetlen alkalmazásban.", + "Landing_Page_BloggingModuleDescription_3": "Támogatja a Markdown formátumot.", + "Landing_Page_BloggingModuleDescription_4": "Lehetővé teszi megjegyzés írását egy bejegyzéshez.", + "Landing_Page_BloggingModuleDescription_5": "Lehetővé teszi címkék hozzárendelését a blogbejegyzésekhez.", + "Landing_Page_BloggingModuleDescription_6": "Tekintse meg a blog.abp.io webhelyet a blogolási modul élő példájaként.", + "Landing_Page_ChatModuleDescription_1": "Ez a modul valós idejű üzenetküldésre szolgál az alkalmazásban lévő felhasználók között.", + "Landing_Page_ChatModuleDescription_2": "Valós idejű üzenetküldés a chat oldalon.", + "Landing_Page_ChatModuleDescription_3": "Keressen felhasználókat az alkalmazásban új beszélgetésekhez.", + "Landing_Page_ChatModuleDescription_4": "Névjegylista a legutóbbi beszélgetésekhez.", + "Landing_Page_ChatModuleDescription_5": "Új üzenet értesítések, amikor a felhasználó egy másik oldalt néz.", + "Landing_Page_ChatModuleDescription_6": "Teljes olvasatlan üzenetek száma jelvény a menüikonon.", + "Landing_Page_ChatModuleDescription_7": "Az olvasatlan üzenetek száma az egyes beszélgetéseknél.", + "Landing_Page_ChatModuleDescription_8": "Lusta betöltött beszélgetések.", + "Landing_Page_DocsModuleDescription_1": "Ez a modul műszaki dokumentációs weboldalak létrehozására szolgál;", + "Landing_Page_DocsModuleDescription_2": "Beépített GitHub integráció : közvetlenül írhat és kezelhet dokumentumokat a GitHubon.", + "Landing_Page_DocsModuleDescription_3": "Verziókezelési támogatás közvetlenül integrálva a GitHub-kiadásokba.", + "Landing_Page_DocsModuleDescription_4": "Támogatja a több nyelvet (az alapértelmezett nyelv tartalék támogatásával).", + "Landing_Page_DocsModuleDescription_5": "Támogatja a Markdown és HTML formátumokat.", + "Landing_Page_DocsModuleDescription_6": "Navigációt és vázlatos részt biztosít.", + "Landing_Page_DocsModuleDescription_7": "Lehetővé teszi több projektdokumentáció tárolását egyetlen alkalmazásban.", + "Landing_Page_DocsModuleDescription_8": "Hivatkozások a fájlra a GitHubon, így bárki könnyedén hozzájárulhat a Szerkesztés linkre kattintva .", + "Landing_Page_DocsModuleDescription_9": "A GitHub-forrás mellett lehetővé teszi egy mappa egyszerű használatát dokumentációs forrásként.", + "Landing_Page_FileManagementModuleDescription_1": "Fájlok feltöltése, letöltése és rendezése hierarchikus mappastruktúrában.", + "Landing_Page_FileManagementModuleDescription_2": "Ez a modul fájlok feltöltésére, letöltésére és hierarchikus mappastruktúrába rendezésére szolgál. Több bérléssel is kompatibilis, és meghatározhatja bérlői teljes méretkorlátját.", + "Landing_Page_FileManagementModuleDescription_3": "Ez a modul a BLOB Storing rendszeren alapul, így különböző tárolószolgáltatókat használhat a fájltartalom tárolására.", + "Landing_Page_IdentityModuleDescription_1": "Ez a modul egy alkalmazás Felhasználó és szerepkör rendszerét valósítja meg;", + "Landing_Page_IdentityModuleDescription_2": "A Microsoft ASP.NET Core Identity könyvtárára épül.", + "Landing_Page_IdentityModuleDescription_3": "Szerepkörök és felhasználók kezelése a rendszerben. Egy felhasználónak több szerepe is lehet.", + "Landing_Page_IdentityModuleDescription_4": "Állítsa be az engedélyeket szerep- és felhasználói szinteken.", + "Landing_Page_IdentityModuleDescription_5": "Felhasználónként kétfaktoros hitelesítés és felhasználói kizárás engedélyezése/letiltása.", + "Landing_Page_IdentityModuleDescription_6": "Az alapvető felhasználói profil és jelszó kezelése.", + "Landing_Page_IdentityModuleDescription_7": "Igénytípusok kezelése a rendszerben, igények beállítása szerepkörökhöz és felhasználókhoz.", + "Landing_Page_IdentityModuleDescription_8": "Beállítási oldal a jelszó összetettségének , a felhasználói bejelentkezés, a fiók és a kizárás kezeléséhez.", + "Landing_Page_IdentityModuleDescription_9": "Támogatja az LDAP hitelesítést.", + "Landing_Page_IdentityModuleDescription_10": "E-mail és telefonszám ellenőrzést biztosít.", + "Landing_Page_IdentityModuleDescription_11": "Támogatja a közösségi bejelentkezési integrációkat (Twitter, Facebook, GitHub stb.).", + "Landing_Page_IdentityModuleDescription_12": "Szervezeti egységek kezelése a rendszerben.", + "Landing_Page_PaymentModuleDescription_1": "Integrációt biztosít a különböző fizetési átjárókhoz.", + "Landing_Page_PaymentModuleDescription_2": "Ez a modul integrációt biztosít a fizetési átjárókhoz, így könnyen kaphat fizetést ügyfeleitől.", + "Landing_Page_PaymentModuleDescription_3": "Ez a modul a következő fizetési átjárókat támogatja", + "Welcome_Page_UseSameCredentialForCommercialWebsites": "Ugyanazokat a hitelesítési adatokat használja a commercial.abp.io és a support.abp.io fájlokhoz .", + "WatchCrudPagesVideo": "Nézze meg a \"CRUD oldalak létrehozása az ABP Suite segítségével\" videót!", + "WatchGeneratingFromDatabaseVideo": "Nézze meg az \"ABP Suite: CRUD-oldalak generálása meglévő adatbázistáblákból\" videót!", + "WatchTakeCloserLookVideo": "Tekintse meg a „Nézze meg közelebbről a kódgenerálást: ABP Suite” videót!", + "ConfirmedEmailAddressRequiredToStartTrial": "A próbalicenc elindításához rendelkeznie kell egy megerősített e-mail címmel.", + "EmailVerificationMailNotSent": "Nem sikerült elküldeni az ellenőrző e-mailt.", + "GetConfirmationEmail": "Kattintson ide, ha megerősítő e-mailt szeretne kapni, ha még nem kapta meg.", + "WhichLicenseTypeYouAreInterestedIn": "Melyik licenctípus érdekli?", + "DontTakeOurWordForIt": "Ne fogadd el a szavunkat...", + "ReadAbpCommercialUsersWantYouToKnow": "Olvassa el, hogy az ABP Commercial felhasználói mit szeretnének tudni", + "Testimonial_ShortDescription_1": "Az ABP modularitása lehetővé tette, hogy a csapat időben teljesítsen.", + "Testimonial_ShortDescription_2": "Építsen új funkciókat gyorsabban, mint korábban.", + "Testimonial_ShortDescription_3": "A beépített funkciókból indulunk ki, és csak arra koncentrálunk, amit valóban meg kell írnunk.", + "Testimonial_ShortDescription_4": "Az ABP Commercial volt a legjobban megfelelő az igényeinknek.", + "OnlineReviewersOnAbpCommercial": "Online vélemények az ABP Commercial-ról", + "SeeWhatToldAboutAbpCommercial": "Tekintse meg, mit mondtak az ABP Commercialról, és írja le gondolatait, ha akarja." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json index 94e1505160..d03220758a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json @@ -178,7 +178,7 @@ "HowManyDevelopers": "ABP Commercial'da kaç geliştirici çalışabilir?", "HowManyDevelopersExplanation": "ABP Ticari lisansları geliştirici başınadır. Farklı lisans türlerinin farklı geliştirici sınırları vardır. Ancak, ihtiyacınız olduğunda herhangi bir lisans türüne daha fazla geliştirici ekleyebilirsiniz. Lisans türleri, geliştirici sınırları ve ek geliştirici maliyetleri için fiyatlar sayfasına bakın.", "ChangingLicenseType": "Lisans türümü daha sonra yükseltebilir miyim?", - "ChangingLicenseTypeExplanation": "Aktif lisans süreniz içerisinde aradaki farkı ödeyerek bir üst lisansa geçebilirsiniz. Daha yüksek bir lisans planına yükselttiğinizde, yeni planın avantajlarından yararlanırsınız, ancak lisans yükseltmesi, lisansın sona erme tarihini değiştirmez. Ayrıca, mevcut lisansınıza yeni geliştirici lisansları da ekleyebilirsiniz, bkz. \"ABP Ticari üzerinde kaç geliştirici çalışabilir?\"", + "ChangingLicenseTypeExplanation": "Aktif lisans süreniz içerisinde aradaki farkı ödeyerek bir üst lisansa geçebilirsiniz. Daha yüksek bir lisans planına yükselttiğinizde, yeni planın avantajlarından yararlanırsınız, ancak lisans yükseltmesi, lisansın sona erme tarihini değiştirmez. Ayrıca, mevcut lisansınıza yeni geliştirici koltukları da ekleyebilirsiniz, bkz. \"ABP Ticari üzerinde kaç geliştirici çalışabilir?\"", "LicenseExtendUpgradeDiff": "Lisans uzatma ve yükseltme arasındaki fark nedir?", "LicenseExtendUpgradeDiffExplanation": "Uzatma: Lisansınızı uzatarak/yenileyerek, premium destek almaya ve modüller ve temalar için major veya minor güncellemeler almaya devam edeceksiniz. Ayrıca, yeni projeler oluşturmaya devam edebileceksiniz. Ve geliştirmenizi hızlandıran ABP Suite'i kullanmaya devam edebileceksiniz.
Yükseltme: Lisansınızı yükselterek, ek avantajlar elde etmenizi sağlayacak daha yüksek bir lisans planına terfi edeceksiniz. . Lisans planları arasındaki farkları kontrol etmek için lisans karşılaştırma tablosuna bakın.Öte yandan, yükseltme yaptığınızda lisans geçerlilik bitiş tarihiniz değişmez!Lisans bitiş tarihinizi uzatmak için lisansınızı uzatmanız gerekir.", "LicenseRenewalCost": "1 yıl sonra ehliyet yenileme ücreti ne kadardır?", @@ -385,6 +385,366 @@ "RemoveCurrentUserFromOrganizationWarningMessage": "Organizasyondan kendinizi kaldırıyorsunuz. Artık bu organizasyonu yönetemeyeceksiniz, onaylıyor musunuz? ", "RenewExistingOrganizationOrCreateNewOneMessage": "Aşağıdaki \"Şimdi Uzat\" buton(lar)una tıklayarak organizasyon(lar)ınızın lisansını yenileyebilir ve böylece lisans geçerlilik süresini 1 yıl uzatabilirsiniz. Ödemeye devam ederseniz, yeni bir organizasyonunuz olacak. Yeni bir organizasyonla devam etmek istiyor musunuz? ", "ExtendNow": "Şimdi Uzat", - "ContinueWithNewOrganization": "Yeni bir organizasyon ile devam et" + "ContinueWithNewOrganization": "Yeni bir organizasyon ile devam et", + "Owner": "sahip", + "AddNewOwner": "Yeni sahip ekle", + "AddNewDeveloper": "Yeni geliştirici ekle", + "FirstNameField": "Ad", + "LastNameField": "Soyad", + "AbpCommercialMetaTitle": "ABP Commercial - Eksiksiz Web Geliştirme Platformu : {0} | ABP Commercial", + "AbpCommercialMetaDescription": "ABP Commercial, açık kaynaklı ABP çerçevesinin üzerine inşa edilmiş önceden oluşturulmuş uygulama modülleri, hızlı geliştirme araçları, kullanıcı arayüzü temaları ve hizmetlerinden oluşan bir settir.", + "WhatHappensWhenLicenseEnds": "Lisans sürem sona erdiğinde ne olacak?", + "WhatHappensWhenLicenseEndsExplanation1": "ABP Ticari lisansı kalıcı bir lisanstır. Lisansınızın süresi dolduktan sonra projenizi geliştirmeye devam edebilirsiniz. Ve lisansınızı yenilemek zorunda değilsiniz. Lisansınız kutudan bir yıllık güncelleme ve destek planı ile birlikte gelir. Yeni özellikler, performans geliştirmeleri, hata düzeltmeleri, destek almaya devam etmek ve ABP Suite'i kullanmaya devam etmek için lisansınızı yenilemeniz gerekir. Lisansınızın süresi dolduğunda aşağıdaki avantajlardan yararlanamazsınız:", + "WhatHappensWhenLicenseEndsExplanation2": "ABP Ticari'yi kullanarak yeni çözümler oluşturamazsınız, ancak mevcut uygulamalarınızı sonsuza kadar geliştirmeye devam edebilirsiniz.", + "WhatHappensWhenLicenseEndsExplanation3": "MINOR sürümünüzdeki modüller ve temalar için güncellemeleri alabileceksiniz (RC veya Önizleme sürümleri hariç). Örneğin: bir modülün v3.2.0 sürümünü kullanıyorsanız, bu modülün v3.2.x (v3.2.1, v3.2.5... vb.) sürümleri için güncellemeleri almaya devam edebilirsiniz. Ancak bir sonraki büyük veya küçük sürüm için güncelleme alamazsınız (v3.3.0, v3.3.3, 4.x.x... gibi). Örneğin, lisansınızın süresi dolduğunda, en son sürüm v4.4.3 idi ve daha sonra hem 4.4.4 sürümünü hem de 4.5.0 sürümünü yayınladı, v4.4.X'e erişebilirsiniz, ancak v4.5.X'e erişemezsiniz.", + "WhatHappensWhenLicenseEndsExplanation4": "Lisansınız sona erdikten sonra ABP Ticari platformuna eklenen yeni modülleri ve temaları yükleyemezsiniz.", + "WhatHappensWhenLicenseEndsExplanation5": "ABP Suite'i kullanamazsınız.", + "WhatHappensWhenLicenseEndsExplanation6": "Artık premium desteği alamazsınız.", + "WhatHappensWhenLicenseEndsExplanation7": "Bu avantajlardan yararlanmaya devam etmek istiyorsanız lisansınızı uzatabilirsiniz (yenileyebilirsiniz). Lisansınızın süresi dolduktan sonra 1 ay içinde lisansınızı uzatırsanız, aşağıdaki indirimler uygulanacaktır: Takım Lisansı {0} % indirim, İşletme Lisansı %{1} indirim, Kurumsal Lisans %{2} indirim.", + "WhatHappensWhenLicenseEndsExplanation8": "Oluşturduğunuz ABP projeleri sunucularımızda saklanmamaktadır. Bu nedenle indirdiğiniz kaynak kodunu saklamak sizin sorumluluğunuzdadır. Lisansınızın süresi dolduğunda, oluşturulan ABP proje kaynak kodunuzu almanın bir yolu yoktur.", + "TrialPlanExplanation": "ABP Ticari takım lisansı için 14 günlük deneme süresi var. Daha fazla bilgi için burayı ziyaret edin. Ayrıca, Takım lisansları için 30 günlük para iade garantisi veriyoruz. Sadece ilk 30 gün içinde geri ödeme talebinde bulunabilirsiniz. İşletme ve Kurumsal lisansları için 30 gün içinde %60 geri ödeme sağlıyoruz. Bunun nedeni, İşletme ve Kurumsal lisanslarının tüm modüllerin ve temaların tam kaynak kodunu içermesidir.", + "ContactUsQuestions": "Herhangi bir sorunuz varsa bizimle iletişime geçin", + "ActivationRequirement": "Denemenizi başlatmanıza son bir adım kaldı.
Bilgilerinizi kontrol ettikten sonra lisansınızı etkinleştireceğiz. Lisansınız etkinleştirildiğinde, {0} adresine bir e-posta göndereceğiz. Merak etmeyin bu süreç uzun sürmeyecek!", + "PurchaseNow": "Şimdi satın al", + "DowngradeLicensePlan": "Gelecekte daha düşük bir lisans planına geçebilir miyim?", + "DowngradeLicensePlanExplanation": "Mevcut lisans planınızı düşüremezsiniz. Ancak yeni bir daha düşük lisans planı satın alabilir ve yeni lisans üzerinde geliştirmenize devam edebilirsiniz. Daha düşük bir lisans satın aldıktan sonra, ABP CLI komutu ile yeni lisans planınıza giriş yapmanız yeterlidir: ` abp login -o `.", + "LicenseTransfer": "Lisans bir geliştiriciden diğerine aktarılabilir mi?", + "LicenseTransferExplanation": "Evet! Bir lisans satın aldığınızda, lisans sahibi olursunuz, dolayısıyla kuruluş yönetim sayfasına erişiminiz olur. Bir kuruluşun sahip ve geliştirici rolleri vardır. Sahipler geliştirici lisanslarını yönetebilir ve geliştiriciler atayabilir. Atanan her geliştirici, ABP CLI komutu sisteme girecek ve geliştirme ve destek izinlerine sahip olacaktır.", + "UserOwnerDescription": "Kuruluşun 'Sahibi' bu hesabın yöneticisidir. Lisansları satın alarak ve geliştiricileri tahsis ederek organizasyonu yönetir. Bir 'Sahip' ABP Ticari projelerine kod yazamaz, ABP örnek projelerini indiremez ve destek web sitesinde soru soramaz. Tüm bunları yapmak istiyorsanız, kendinizi de geliştirici olarak eklemeniz gerekir.", + "UserDeveloperDescription": "'Geliştiriciler' ABP Ticari projelerinde kod yazabilir, ABP örnek projelerini indirebilir ve destek web sitesinde sorular sorabilir. Öte yandan, 'Geliştiriciler' bu organizasyonu yönetemezler.", + "PurchaseTrialOrganizationOrCreateNewOneMessage": "Deneme lisansınız var. Deneme lisansınızı satın almak için Şimdi Satın Al düğmesine tıklayın. Ödeme işlemine devam ederseniz, yeni bir kuruluşunuz olacak. Yeni bir organizasyon ile devam etmek istiyor musunuz?", + "CreateNewOrganization": "Yeni bir organizasyon oluştur", + "RenewLicenseEarly": "Lisansımı erken yenilersem, tüm yılı alacak mıyım?", + "RenewLicenseEarylExplanation": "Lisansınızı lisans bitiş tarihinizden önce yenilediğinizde, lisans bitiş tarihinize 1 yıl eklenecektir. Örneğin, lisansınızın süresi {0}-06-06 tarihinde doluyorsa ve lisansınızı {0}-01-01 tarihinde yenilediyseniz, yeni lisans bitiş tarihiniz {1}-06-06 olacaktır.", + "OpenSourceWebApplication": "Açık Kaynak Web Uygulaması", + "CompleteWebDevelopment": "Tam Web Geliştirme", + "ABPFrameworkDescription": "ABP Framework, yazılım geliştirme ve sözleşmelerin en iyi uygulamalarını takip ederek modern web uygulamaları oluşturmak için eksiksiz bir altyapıdır.", + "CommunityDescription": "ABP Çerçevesi ile ilgili deneyimlerinizi paylaşın!", + "GetStarted": "Başlayın", + "Views": "Görünümler", + "LatestPosts": "Son Gönderiler", + "PreBuiltApplication": "Önceden Oluşturulmuş Uygulama", + "DatabaseProviders": "Veritabanı Sağlayıcıları", + "UIFrameworks": "UI Çerçeveleri", + "UsefulLinks": "Faydalı Bağlantılar", + "Platform": "Platform", + "CoolestCompaniesUseABPCommercial": "En havalı şirketler zaten ABP Commercial kullanıyor.", + "UserInterface": "Kullanıcı Arayüzü", + "APIGateway": "API Ağ Geçidi", + "Microservice": "Mikro Hizmet", + "Database": "Veritabanı", + "Architecture": "Mimari", + "MicroserviceArchitectureExplanation": "Bu, en son teknolojilerle ölçeklenebilir bir mikro hizmet çözümü oluşturmak için birden çok uygulama, API ağ geçidi, mikro hizmet ve veritabanlarından oluşan eksiksiz bir çözüm mimarisidir.", + "BusinessLogic": "İş Mantığı", + "DataAccessLayer": "Veri Erişim Katmanı", + "Monolith": "Monolit", + "ModularArchitectureExplanation": "Bu başlangıç şablonu, temiz ve sürdürülebilir bir kod tabanı oluşturmak için katmanlı, modüler ve DDD tabanlı bir çözüm mimarisi sağlar.", + "SeeDetails": "Ayrıntıları Gör", + "SeeDocumentation": "Belgelere göz atın", + "Bs5Compatible": "Bootstrap 5 uyumlu profesyonel tema, yönetici web siteniz için mükemmel.", + "LeptonXTheme": "LeptonX Tema", + "LeptonXDark": "LeptonX Koyu", + "LeptonXLight": "LeptonX Açık", + "LeptonXSemiDark": "LeptonX Yarı Koyu", + "BuiltOnBs5Library": "Bootstrap 5 kütüphanesi üzerine inşa edilmiştir.", + "FullyCompatibleWithBs5": "Bootstrap 5 HTML yapısı ve CSS sınıfları ile %100 uyumlu", + "ResponsiveAndMobileCompatible": "Responsive, mobil uyumlu, RTL desteği", + "ProvidesStylesForDatatables": "Datatables için stil sağlar", + "MultipleLayoutOptions": "Çoklu düzen seçenekleri", + "EasilyInstallAndUpgrade": "Kolay kurulum ve yükseltme", + "SupportForum": "Destek Forumu", + "TrustedBy": "Güvenenler", + "OurPricing": "Fiyatlandırmamız", + "Plans": "Planlar", + "NameSurname": "Ad Soyad", + "Unspecified": "Belirtilmemiş", + "LicenceType": "Lisans Türü", + "LicenseDiscountWarning": "BU İNDİRİM SAYFASI VARSAYILAN İNDİRİM KODU VE VOLOSOFT GELİŞTİRİCİLERİ İÇİN KULLANILMAKTADIR. AŞAĞIDAKİ SATIN ALMA LİNKLERİ ÇALIŞMAZ.", + "DiscountedLicenseExplanation": "Bu lisans fiyatları küçük girişimler, bireysel geliştiriciler, öğrenciler, kar amacı gütmeyen kuruluşlar ve projeler içindir!", + "General": "Genel", + "License": "Lisans", + "Development": "Geliştirme", + "Payment": "Ödeme", + "WatchExplainerVideo": "Hadi Tanışalım! Açıklayıcı Videoyu İzleyin", + "LightDarkAndSemiDarkThemes": "Açık, koyu ve yarı koyu temalar", + "LeptonXThemeExplanation": "Lepton Teması, temanızı sistem ayarlarınıza göre değiştirebilir.", + "PRO": "PRO", + "WelcomeToABPCommercial": "ABP Commercial'a hoş geldiniz!", + "YourAccountDetails": "Hesap bilgileriniz", + "OrganizationName": "Organizasyon Adı", + "AddDevelopers": "Geliştiriciler Ekle", + "StartDevelopment": "Geliştirmeye Başla", + "CreateAndRunApplicationUsingStartupTemplate": "ABP Ticari başlangıç şablonunu kullanarak yeni bir web uygulamasının nasıl oluşturulacağını ve çalıştırılacağını öğrenin.", + "CommunityDescription2": "community.abp.io, insanların ABP ile ilgili makaleleri paylaşabileceği bir yerdir. Makaleler, öğreticiler, kod örnekleri, vaka çalışmaları arayın ve sizinle aynı kulvarda olan insanlarla tanışın.", + "UseABPSuiteExplanation": "Modüllerin ve temaların kaynak kodunu indirmek için ABP Suite'i kullanın.", + "ManageModulesWithSuite": "ABP modüllerinizi Suite ile de yönetebilirsiniz.", + "LearnHowToInstallSuite": "ABP Suite'in nasıl kurulacağını ve kullanılacağını öğrenin.", + "SeeMore": "Daha fazla göster", + "SeeLess": "Daha az göster", + "LayeredSolutionStructure": "Katmanlı Çözüm Yapısı", + "LayeredSolutionStructureExplanation": "Çözüm, iş mantığınızı altyapıdan ve entegrasyonlardan izole etmek ve kodun sürdürülebilirliğini ve yeniden kullanılabilirliğini en üst düzeye çıkarmak için Etki Alanı Güdümlü Tasarım ilkelerine ve modellerine dayalı olarak katmanlandırılmıştır. ABP Framework, uygulamanız için DDD'yi gerçekten uygulamak üzere soyutlamalar, temel sınıflar ve kılavuzlar sağlamaktadır.", + "MultipleUIOptions": "Çoklu UI Seçenekleri", + "MultipleUIOptionsExplanation": "Kullanıcı Arayüzü oluşturmanın farklı yollarını seviyoruz. Bu başlangıç çözümü, iş uygulamanız için üç farklı UI çerçeve seçeneği sunuyor.", + "MultipleDatabaseOptions": "Çoklu Veritabanı Seçenekleri", + "MultipleDatabaseOptionsExplanation": "İki veritabanı sağlayıcısı seçeneğiniz var (her ikisini de tek bir uygulamada kullanmanın yanı sıra). Herhangi bir ilişkisel veritabanıyla çalışmak için Entity Framework Core'u kullanın ve daha iyi bir performans için düşük seviyeli sorgular yazmanız gerektiğinde isteğe bağlı olarak Dapper'ı kullanın. Belge tabanlı bir NoSQL veritabanı kullanmanız gerekiyorsa MongoDB başka bir seçenektir. Bu sağlayıcılar iyi entegre edilmiş, soyutlanmış ve önceden yapılandırılmış olsa da, .NET ile kullanabileceğiniz herhangi bir veritabanı sistemiyle etkileşime girebilirsiniz. ", + "ModularArchitectureExplanation2": "Modülerlik, ABP.IO platformunda birinci sınıf bir vatandaştır. Uygulamalardaki tüm işlevler iyi izole edilmiş opsiyonel modüllere ayrılmıştır. Başlangıç çözümü zaten temel ABP Ticari modülleri önceden yüklenmiş olarak gelir. Kendi uygulamanız için modüler bir sistem oluşturmak üzere kendi modüllerinizi de oluşturabilirsiniz.", + "MultiTenancyForSaasBusiness": "Saas İşletmeleri için Çoklu Kiralama", + "MultiTenancyForSaasBusinessExplanation": "ABP Commercial, SaaS (Hizmet Olarak Yazılım) sistemlerinizi oluşturmak için eksiksiz, uçtan uca çoklu kiracılık sistemi sağlar. Anında veritabanı oluşturma ve taşıma sistemi ile kiracıların kendi veritabanlarını paylaşmalarına veya sahip olmalarına olanak tanır.", + "MicroserviceStartupSolution": "Mikro Hizmet Başlangıç Çözümü", + "MicroserviceArchitectureExplanation2": "Önceden oluşturulmuş temel çözümden ve damıtılmış deneyimden yararlanmak için bir sonraki mikro hizmet sisteminiz için alabilirsiniz.", + "PreIntegratedTools": "Popüler araçlara önceden entegre edilmiş", + "PreIntegratedToolsExplanation": "Çözüm halihazırda endüstri standardı araçlara ve teknolojilere entegre edilmiş durumda, ancak siz bunları her zaman değiştirebilir ve favori araçlarınıza entegre edebilirsiniz.", + "SingleSignOnAuthenticationServer": "Tek Oturum Açma Kimlik Doğrulama Sunucusu", + "SingleSignOnAuthenticationServerExplanation": "Çözüm, API erişim yönetimi özelliklerine sahip tek oturum açma sunucusu olarak diğer uygulamalar tarafından kullanılan bir kimlik doğrulama sunucusu uygulamasına sahiptir. IdentityServer tabanlıdır.", + "WebAppsWithGateways": "2 API Ağ Geçidi ile 2 Web Uygulaması", + "WebAppsWithGatewaysExplanation": "Çözüm, her biri özel bir API ağ geçidine sahip iki web uygulaması içeriyor (BFF - Backend For Frontend modeli).", + "BackOfficeApplication": "Back Office Uygulaması", + "BackOfficeApplicationExplanation": "Birden fazla UI çerçeve seçeneği ile sisteminizin gerçek web uygulaması. Her türlü iş uygulamasını oluşturabilirsiniz.", + "LandingWebsite": "Açılış Web Sitesi", + "LandingWebsiteExplanation": "Şirketinizi tanıtmak, ürünlerinizi satmak gibi çeşitli amaçlar için kullanılabilecek genel bir açılış/genel web sitesi.", + "ABPFrameworkEBook": "Mastering ABP Framework e-kitabı", + "MasteringAbpFrameworkEBookDescription": "ABP Ticari lisansınıza dahildir", + "FullName": "Tam Ad", + "LicenseTypeNotCorrect": "Lisans tipi doğru değil.", + "Trainings": "Eğitimler", + "ChooseTrainingPlaceholder": "Eğitim seçin ...", + "DoYouNeedTrainings": "Bu eğitimlerden birine ihtiyacınız var mı?", + "DoYouNeedTraining": "{0} eğitimine ihtiyacınız var mı?", + "GetInTouchUs": "Bize Ulaşın", + "ForMoreInformationClickHere": "Daha fazla bilgi için buraya tıklayın.", + "IsGetOnboardingTraining": "İşe alım ve web uygulaması geliştirme eğitimi almak ister misiniz?", + "OnboardingWebApplicationDevelopmentTrainingMessage": "Eğitim takviminizi planlamak için lütfen organizasyonu oluşturduktan sonra {0} ile iletişime geçin", + "CustomPurchaseMessage": "Bir sonraki adım için, bizimle iletişime geçmek üzere {0}'a tıklayın.", + "Note": "Not", + "AdditionalNote": "Ek Not", + "OnboardingTrainingFaqTitle": "ABP onboarding eğitiminiz var mı?", + "OnboardingTrainingFaqExplanation": " Evet, ABP projenizi hızlı bir şekilde başlatmanıza yardımcı olacak ABP Eğitim Hizmetlerimiz var. Bir ABP çekirdek ekip üyesinden ABP hakkında bilgi edinecek ve ABP projenize başlamak için gereken becerileri edineceksiniz. İlk katılım eğitiminde, geliştirme ortamınızı nasıl kuracağınızı, gerekli araçları nasıl yükleyeceğinizi ve tamamen işlevsel bir CRUD sayfasını nasıl oluşturacağınızı açıklayacağız. Eğitim canlı olacak ve Zoom uygulaması kullanılacaktır, ancak diğer çevrimiçi toplantı platformlarını kullanmaya da açığız. Eğitim dili İngilizce olacaktır. ABP ile ilgili sorularınızı oturumlar sırasında da sorabilirsiniz. Her iki taraf için de uygun bir zaman ve tarih planlanacaktır. Daha fazla bilgi almak için info@abp.io adresinden bizimle iletişime geçebilirsiniz.", + "AddBasket": "Sepete Ekle", + "SendTrainingRequest": "Eğitim Talebi Gönder", + "OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* Bu belgenin İngilizce versiyonu en güncel olanıdır ve herhangi bir anlaşmazlıkta İngilizce versiyonu geçerli olacaktır.", + "Pricing_Page_Title": "Planlar ve Fiyatlandırma", + "Pricing_Page_Description": "İşletmenizin bugün ihtiyaç duyduğu özellikleri ve işlevselliği seçin. Bir ABP Ticari lisansı satın alın ve sınırsız proje oluşturun.", + "Pricing_Page_HurryUp": "Acele edin!", + "Pricing_Page_BuyLicense": "16 Ocak'a kadar 2021 fiyatlarıyla lisans satın alın!", + "Pricing_Page_ValidForExistingCustomers": "Mevcut müşteriler ve lisans yenilemeleri için de geçerlidir.", + "Pricing_Page_Hint1": "Lisans fiyatına belirli sayıda geliştirici koltuğu dahildir. Daha fazla geliştiriciniz varsa, her zaman ek koltuk satın alabilirsiniz.", + "Pricing_Page_Hint2": "Şimdi veya gelecekte daha fazla geliştirici koltuğu satın alabilirsiniz. Lisanlar koltuk bazlıdır, bu nedenle bir koltuğu bir geliştiriciden diğerine aktarabilirsiniz.", + "Pricing_Page_Hint3": "Lisansınız ile sınırsız sayıda farklı ürün geliştirebilirsiniz.", + "Pricing_Page_Hint4": "ABP Suite, üretkenliğinizi artırmak için geliştirmenize yardımcı olacak bir araçtır. CRUD sayfaları oluşturmayı ve yeni projeler oluşturmayı destekler.", + "Pricing_Page_Hint5": "Önceden oluşturulmuş tüm modülleri uygulamalarınızda kullanabilirsiniz.", + "Pricing_Page_Hint6": "Önceden oluşturulmuş tüm temaları uygulamalarınızda kullanabilirsiniz.", + "Pricing_Page_Hint7": "Başlangıç şablonu, projenize hızlı bir başlangıç yapmanızı sağlayan bir Visual Studio çözümüdür. Tüm temel modüller eklenir ve sizin için önceden yapılandırılır.", + "Pricing_Page_Hint8": "Mastering ABP Framework e-kitabı, .NET çözümlerinin en iyi uygulamalarla nasıl hayata geçirileceğini anlatıyor. Amazon.com'da satılmaktadır ve kitabı lisansınız dahilinde ücretsiz olarak indirebilirsiniz.", + "Pricing_Page_Hint9": "Herhangi bir modülün kaynak kodunu indirebilirsiniz. Köklü değişiklikler yapmak için kaynak kodunu çözümünüze eklemek veya güvenlik nedeniyle kendinize saklamak isteyebilirsiniz.", + "Pricing_Page_Hint10": "Lisanslar ömür boyu geçerlidir. Bu, uygulamanızı sonsuza kadar geliştirmeye devam edebileceğiniz anlamına gelir. En son sürüme erişim ve destek alma lisans süresi içinde (yenilemediğiniz sürece 1 yıl) verilir.", + "Pricing_Page_Hint11": "Dağıtım konusunda kısıtlama yok! Bulut hizmetleri veya şirket içi dahil olmak üzere istediğiniz kadar sunucuya dağıtabilirsiniz.", + "Pricing_Page_Hint12": "Aktif lisans süreniz içinde modülleri, temaları ve araçları en son sürüme güncelleyebilirsiniz. Lisansınızın süresi dolduktan sonra, hata düzeltmeleri, yeni özellikler ve geliştirmelerle ilgili güncellemeleri almaya devam etmek için lisansınızı yenilemeniz gerekir.", + "Pricing_Page_Hint13": "Premium desteği bir yıl boyunca alabilirsiniz (uzatmak için lisansınızı yenileyebilirsiniz).", + "Pricing_Page_Hint14": "Ekip ve İşletme lisanslarının olay/soru sayısı limiti vardır. Ek geliştirici lisansları satın alırsanız, olay limitiniz geliştirici başına {0} (Ekip Lisansı için) veya {1} (İşletme Lisansı için) artar.", + "Pricing_Page_Hint15": "Sadece Kurumsal Lisans özel destek içerir. Özel bilet seçeneği ile doğrudan ABP Ekibine e-posta gönderebilir veya support.abp.io adresinden soru sorabilirsiniz. Özel biletler herkese açık değildir.", + "Pricing_Page_Hint16": "Tüm ABP temalarının kaynak kodunu indirebilirsiniz. Köklü değişiklikler yapmak için kaynak kodunu çözümünüze eklemek isteyebilir veya güvenlik nedeniyle kendiniz için saklayabilirsiniz.", + "Pricing_Page_Testimonial_1": "ABP Commercial, SC Ventures'ın 9 ay içinde banka sınıfı çok kiracılı bir silo-veritabanı SaaS platformu sunmasını ve birden fazla entegre çapadan gelen önemli değerdeki faturaların alacak / borç hesapları tedarik zinciri finansmanını desteklemesini sağladı. ABP'nin modülerliği, ekibin rekor sürede teslimat yapmasını, tüm VAPT'leri geçmesini ve konteynerleştirilmiş mikro hizmet yığınını tam CI/CD ve boru hatları aracılığıyla üretime dağıtmasını mümkün kıldı.", + "Pricing_Page_Testimonial_2": "Özel geliştirme projelerinin ek yükünü azaltmak için ABP Commercial kullanmanın değerini görüyoruz. Ekip, farklı proje akışlarında kod modelini birleştirebiliyor. Yeni özellikleri eskisinden daha hızlı oluşturabilmemiz için çerçevede daha fazla potansiyel görüyoruz. ABP Commercial'dan yararlanmanın değerini sürekli olarak göreceğimize inanıyoruz.", + "Pricing_Page_Testimonial_3": "ABP'yi seviyoruz. Her şeyi sıfırdan yazmak zorunda kalmıyoruz. Kullanıma hazır özelliklerden başlıyoruz ve sadece gerçekten yazmamız gerekenlere odaklanıyoruz. Ayrıca, ABP iyi tasarlanmış ve kod daha az hata ile yüksek kalitede. İhtiyaç duyduğumuz her şeyi kendi başımıza yazmak zorunda kalsaydık, yıllarımızı harcamak zorunda kalabilirdik. Hoşumuza giden bir diğer şey de yeni sürümün, sorun düzeltmenin ya da iyileştirmenin iki haftada bir çıkması. Çok uzun süre beklemiyoruz.", + "Pricing_Page_Testimonial_4": "ABP Commercial harika bir ürün, tavsiye ederim. Müşterilerimiz için ticari ürünleri tek bir yapılandırılabilir platformda pazara sunuyor. Çerçeve ve araçların herhangi bir ekibe sağladığı hızlı başlangıç her kuruşa değer. ABP Commercial ihtiyaçlarımız için en uygun üründü.", + "Pricing_Page_Testimonial_5": "ABP Framework sadece bir framework değil, aynı zamanda bir proje geliştirme/yönetme rehberi, çünkü DDD, GenericRepository, DI, Microservice ve Modularity eğitimleri veriyor. Framework'ün kendisini kullanmayacak olsanız bile, iyi ve profesyonelce hazırlanmış docs.abp.io ile kendinizi geliştirebilirsiniz (OpenIddict, Redis, Quartz vb.). Birçok şey önceden hazır olduğu için proje geliştirme süresini önemli ölçüde kısaltıyor (Giriş sayfası, istisna işleme, veri filtreleme, tohumlama, denetim günlüğü, yerelleştirme, otomatik API denetleyicisi vb.) Uygulamamızdan bir örnek olarak, stok kontrolü için Local Event Bus kullandım. Böylece stok işleyicisi yazarak sipariş hareketlerini yönetebiliyorum. CreationTime, CreatorId için zaman kaybetmemek harika. Bunlar otomatik olarak dolduruluyor.", + "AbpBookDownloadArea_ClaimYourEBook": "Mastering ABP Framework E-Kitabınızı talep edin", + "AddMemberModal_Warning_1": "Eklemeye çalıştığınız kullanıcı adı sistemde yoksa, lütfen ekip üyenizden {0} adresine kaydolmasını ve hesabının kullanıcı adını sizinle paylaşmasını isteyin.", + "MyOrganizations_Detail_WelcomeMessage": "Organizasyonunuza hoş geldiniz, {0}", + "MyOrganizations_Detail_OrganizationManagement": "Organizasyon Yönetimi", + "OrganizationDisplayName": "Organizasyon Görünür Adı", + "MyOrganizations_Detail_EditDisplayName": "Görünür adı düzenle", + "MyOrganizations_Detail_LicenseStartAndExpiryDate": "Lisans Başlangıç Tarihi - Sona Erme Tarihi", + "MyOrganizations_Detail_OwnerRightInfo": "{1} sahiplik hakkınızın {0}'ını kullanıyorsunuz.", + "MyOrganizations_Detail_CopyApiKey": "Anahtarı Kopyala", + "MyOrganizations_Detail_ApiKeyDescription": "API Anahtarı, {1} üzerinde barındırılan PRO paketlerinin belirtecidir.", + "MyOrganizations_Detail_YourPrivateNugetSource": "Özel NuGet kaynağınız {0}", + "MyOrganizations_Detail_PrivateNugetSourceWarning": "Bu, ABP çözümünüzdeki NuGet.Config'inize otomatik olarak bir besleme olarak eklenir. Özel anahtarınızı yetkisiz kullanıcılarla paylaşmayın!", + "MyOrganizations_Detail_DeveloperSeatInfo": "{1} geliştirici koltuğunuzun {0} tanesini kullanıyorsunuz.", + "NeedMoreSeatsForYourTeam": "Takımınız için daha fazla geliştirici koltuğu gerekiyor mu?", + "MyOrganizations_Detail_PricePerYear": "{0} / per year", + "MyOrganizations_Detail_UpgradeYourLicense": "Lisansınızı yükseltin", + "MyOrganizations_Detail_PurchaseDeveloperSeats": "Geliştirici Koltuğu Satın Alın", + "Invoices": "Faturalar", + "RequestInvoice": "Fatura Talep Et", + "OrderNumber": "Sipariş Numarası", + "Date": "Tarih", + "Products": "Ürünler", + "TotalPrice": "Toplam Fiyat", + "ThereIsNoInvoice": "Fatura yok", + "MyOrganizations_Detail_PaymentProviderInfo": "Lisansınızı {0} ağ geçidi üzerinden satın aldıysanız, PDF faturasını e-posta adresinize gönderir, bkz. {0} faturalandırma.", + "MyOrganizations_Detail_PayUInfo": "If you have purchased through the PayU gateway, click the \"Request Invoice\" button and fill in the billing information.", + "MyOrganizations_Detail_ConclusionInfo": "Fatura talebiniz {0} iş günü içerisinde sonuçlandırılacaktır.", + "ExtendYourLicense": "{0} lisansınızı uzatın", + "Continue": "Devam et", + "DownloadInvoiceModal_DownloadInvoice": "Faturayı İndir", + "DownloadInvoiceModal_SaveInformationOnlyOnce": "Fatura bilgilerinizi yalnızca bir kez kaydedebilirsiniz.", + "InvoiceModal_EnterCompanyName": "Yasal şirket adınızı girin...", + "InvoiceModal_EnterCompanyAddress": "Yasal şirket adresinizi girin...", + "InvoiceModal_EnterTaxNumber": "Varsa VERGİ/KDV numaranızı girin...", + "RequestInvoiceModal_EnterNotes": "Faturanızla ilgili ekstra mesajınızı girin...", + "PrePayment_PayWithIyzico": "Iyzico ile ödeyeceksiniz", + "ContinueToCheckout": "Ödeme ekranına git", + "PrePayment_IyzicoRedirectionInfo": "Satın alma işleminizi güvenli bir şekilde tamamlamak için Iyzico Ödeme Geçidine yönlendirileceksiniz.", + "PrePayment_IyzicoAcceptVisaAndMasterCard": "Iyzico Visa ve MasterCard kabul etmektedir.", + "Purchase": "Satın al", + "AcceptTermsAndConditions": "Gizlilik politikasını, hüküm ve koşulları ve EULA'yı okudum, anladım ve kabul ediyorum.", + "AcceptTermsAndConditionsWarningMessage": "Lütfen gizlilik politikasını ve hüküm ve koşulları kabul edin", + "SelectGatewayToContinue": "Devam etmek için lütfen bir Ağ Geçidi seçin!", + "GatewaySelection_SelectGateway": "Bir Ödeme Ağ Geçidi Seçin", + "GatewaySelection_RedirectionMessage": "Sonrasında, işlem için seçilen ödeme ağ geçidinin web sitesine yönlendirileceksiniz.", + "PaymentSucceed_PaymentSuccessMessage": "Ödeme Başarılı", + "PaymentSucceed_ThanksForPurchase": "Satın aldığınız için teşekkür ederiz!", + "PaymentSucceed_CreateYourOrganization": "Organizasyonunuzu oluşturun", + "PaymentSucceed_AddMeAsDeveloper": "Ben de bir geliştiriciyim, beni organizasyonuma bir geliştirici olarak ekleyin.", + "PaymentSucceed_CreateOrganization": "Organizasyon Oluştur", + "PaymentSucceed_OrganizationDescription": "Bir organizasyon geliştiriciler ve sahiplerden oluşur. Geliştiriciler, ABP projesinde kod yazan ve {1} web sitesinden yararlanacak olan kullanıcılardır. Sahipler ise geliştirici koltuklarını tahsis eden ve lisanslamayı yöneten kullanıcılardır.", + "PaymentSucceed_ViewOrganization": "Organizasyonu görüntülemek için buraya tıklayın", + "Purchase_TotalAnnualPrice": "TOPLAM (yıllık ücret)", + "Purchase_TrainingPrice": "Eğitim Fiyatı", + "Purchase_OnboardingTraining": "ABP Onboarding & Web Uygulama Geliştirme Canlı Eğitimi", + "TotalDeveloperPrice": "Toplam Geliştirici Fiyatı", + "Purchase_PricePerDeveloper": "{0} {1} geliştirici başına", + "Purchase_IncludedDeveloperInfo": "{0} {1} dahil.", + "Purchase_LicenseExtraDeveloperPurchaseMessage": "{0} lisansı {1} geliştirici(ler) içerir. Şimdi veya daha sonra ek geliştiriciler ekleyebilirsiniz.", + "StartupTemplates_Page_Title": "Başlangıç Şablonları", + "StartupTemplates_Page_Description": "ABP Commercial, her düzeyde karmaşıklığa sahip çözümler oluşturmanıza olanak tanır. Önceden oluşturulmuş iki ana başlangıç çözümü sunar. Gereksinimlerinize yakın olanı seçebilir ve bunun üzerine kendi özel çözümünüzü oluşturabilirsiniz.", + "MicroserviceStartupSolutionForDotnet": ".NET için Mikro Hizmet Başlatma Çözümü", + "MonolithSolutionForDotnet": ".NET için Monolith (modüler) Çözüm", + "TrainingDetailsHeaderInfo_TrainingHour": "{0} saat(ler)", + "Trainings_Content": "Eğitim İçeriği", + "Trial_Page_StartYourFreeTrial": "Ücretsiz Denemenizi Başlatın", + "TrialLicenseFeatures": "Tüm ABP ticari özelliklerinden yararlanabileceksiniz", + "TrialPeriodDays": "{0} günlük Takım Lisansınız olacak", + "TrialForumSupportIncident": "{0} forum destek olayınız olacak", + "Contact_Page_Title": "ABP Geliştirme Ekibi ile İletişime Geçin", + "Contact_Page_Description": "Herhangi bir yardıma ihtiyacınız olursa veya düşüncelerinizi ve görüşlerinizi paylaşırsanız ABP Geliştirme ekibi ile iletişime geçin! ABP Destek Ekibi yardıma hazır.", + "Demo_Page_Title": "Demo Oluştur", + "Demo_Page_Description": "ABP Ticari başlangıç şablonu kullanılarak oluşturulan örnek bir uygulamayı görmek için ücretsiz bir demo oluşturun. Ortak uygulama gereksinimleri için kendinizi tekrar etmeyin.", + "Discounted_Page_Title": "İndirimli fiyatlandırma", + "Discounted_Page_Description": "İşletmenizin bugün ihtiyaç duyduğu özellikleri ve işlevselliği seçin. Bir ABP Ticari lisansı satın alın ve sınırsız proje oluşturun", + "Faq_Page_Title": "Sıkça Sorulan Sorular (SSS)", + "Faq_Page_Description": "Herhangi bir sorunuz var mı? Sıkça sorulan soruları arayın veya iletişim formunu kullanarak bize bir soru sorun.", + "Faq_Page_SwiftCode": "SWIFT Kodu", + "Faq_Page_BankName": "Banka Adı", + "Faq_Page_AccountName": "Hesap Adı", + "Faq_Page_AccountNumber": "Hesap Numarası", + "Faq_Page_Currency": "Para Birimi", + "Faq_Page_VatNumber": "Vergi Numarası", + "Faq_Page_OtherCurrenciesInfo": "Diğer para birimleri için tüm hesaplar bölümüne bakınız", + "ModuleDetail_Page_Title": "Modül Detayı - {0}", + "ProjectCreatedSuccess_Page_Title": "Projeniz oluşturuldu", + "ProjectCreatedSuccess_Page_Description": "ABP projeniz başarıyla oluşturuldu!", + "Suite_Page_Title": "ABP Suite - CRUD Sayfaları Oluşturun", + "Suite_Page_Description": "ABP Commercial, geliştirici verimliliğini artırmak için hızlı uygulama geliştirme araçları sağlar. ABP Suite, CRUD sayfalarını kolayca oluşturmanızı sağlar.", + "Themes_Page_Title": "Modern ve İşlevsel Kullanıcı Arayüzü Temaları", + "Themes_Page_Description": "ABP Commercial birden fazla profesyonel, modern kullanıcı arayüzü teması sunar. Kullanıcı arayüzünün neye benzediğini hızlıca görmek için ücretsiz bir demo oluşturun.", + "Tools_Page_Title": "Hızlı Uygulama Geliştirme Araçları", + "Tools_Page_Description": "ABP Commercial, geliştirici verimliliğini artırmak için hızlı uygulama geliştirme araçları sağlar. ABP Suite, CRUD sayfalarını kolayca oluşturmanızı sağlar.", + "DeveloperPrice": "Geliştirici Fiyatı", + "AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} geliştiriciler", + "LicenseRemainingDays": " {0} gün boyunca", + "ExtendPaymentInfoSection_Description": "Lisansınızı uzatarak/yenileyerek premium destek almaya devam edeceksiniz. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. Ve gelişiminizi hızlandıran ABP Suite'i kullanmaya devam edebileceksiniz.", + "LicenseRenewalPrice": "Lisans yenileme fiyatı", + "LicensePrice": "Lisans fiyatı", + "TrialLicensePaymentInfoSection_Description": "Lisans satın alma: Bir lisans satın alarak premium destek almaya devam edeceksiniz. Ayrıca modüller ve temalar için büyük veya küçük güncellemeler alabileceksiniz. Yeni projeler oluşturmaya devam edebileceksiniz. Ve gelişiminizi hızlandıran ABP Suite'i kullanmaya devam edebileceksiniz. Lisans türleri arasındaki farkları kontrol etmek için lisans karşılaştırma tablosuna bakın.", + "SelectTargetLicense": "Hedef Lisansı Seçin", + "UpgradePaymentInfoSection_ExtendMyLicenseForOneYear": "Evet, lisansımın geçerlilik süresini 1 yıl uzatın.", + "UpgradePaymentInfoSection_WantToExtendLicense": "Lisansınızı {0} yıl daha uzatmak istiyor musunuz?", + "UpgradePaymentInfoSection_UpgradingWillNotExtendLicense": "Yükseltme işlemi lisansınızın son kullanma tarihini uzatmayacaktır!", + "UpgradePaymentInfoSection_LicenseUpgradeDescription": "Lisansınızı yükselterek, ek avantajlar elde etmenizi sağlayacak daha yüksek bir lisans türüne terfi edeceksiniz. Lisans türleri arasındaki farkları kontrol etmek için lisans karşılaştırma tablosuna bakın.", + "Landing_Page_CustomerStories": "Müşteri Hikayeleri", + "Landing_Page_OurGreatCustomers": "Büyük Müşterilerimiz", + "Landing_Page_WebApplicationFramework": "Web Uygulama Çerçevesi", + "Landing_Page_WebDevelopmentPlatform": "Web Geliştirme Platformu", + "Landing_Page_CompleteWebDevelopmentPlatform": "Tam Web Geliştirme Platformu", + "Landing_Page_TryFreeDemo": "Ücretsiz Demo Dene", + "Landing_Page_StartingPointForWebApplications": "ASP.NET Core tabanlı web uygulamaları için başlangıç noktası! En iyi web geliştirme için ABP Framework'ü temel alır.", + "Landing_Page_AbpProvidesSoftwareInfrastructure": "ABP Framework, en iyi uygulamalarla mükemmel web uygulamaları geliştirmek için bir yazılım altyapısı sağlar.", + "Landing_Page_MicroserviceCompatibleArchitecture": "Mikroservis Uyumlu Mimari", + "Landing_Page_PreBuiltApplicationModulesAndThemes": "Önceden Oluşturulmuş Uygulama Modülleri ve Temaları", + "Landing_Page_MultiTenantArchitecture": "Çoklu Kiracı Mimari", + "Landing_Page_MultiTenancyDescription": "SaaS uygulamaları artık çok kolay! Veritabanından kullanıcı arayüzüne kadar entegre çoklu kiracılık.", + "Landing_Page_DDDIntroduction": "DDD kalıpları ve ilkeleri temel alınarak tasarlanmış ve geliştirilmiştir. Uygulamanız için katmanlı bir model sağlar.", + "Landing_Page_CrossCuttingConcernsInfo": "Yetkilendirme, doğrulama, istisna işleme, önbelleğe alma, denetim kaydı, işlem yönetimi ve daha fazlası için eksiksiz altyapı.", + "Landing_Page_PreBuiltApplicationModules": "En yaygın web uygulaması gereksinimlerini içeren Önceden Oluşturulmuş Uygulama Modülleri.", + "Landing_Page_ChatModule": "Sohbet", + "Landing_Page_DocsModule": "Belgeler", + "Landing_Page_FileManagementModule": "Dosya Yönetimi", + "Landing_Page_CustomerStory_1": "ABP Commercial, SC Ventures'ın 9 ay içinde banka sınıfı çok kiracılı bir silo-veritabanı SaaS platformu sunmasını ve birden fazla entegre çapadan gelen önemli değerdeki faturaların alacak / borç hesapları tedarik zinciri finansmanını desteklemesini sağladı. ABP'nin modülerliği, ekibin rekor sürede teslimat yapmasını, tüm VAPT'leri geçmesini ve konteynerleştirilmiş mikro hizmet yığınını tam CI/CD ve boru hatları aracılığıyla üretime dağıtmasını mümkün kıldı.", + "Landing_Page_CustomerStory_2": "Özel geliştirme projelerinin ek yükünü azaltmak için ABP Commercial kullanmanın değerini görüyoruz. Ekip, farklı proje akışlarında kod modelini birleştirebiliyor. Yeni özellikleri eskisinden daha hızlı oluşturabilmemiz için çerçevede daha fazla potansiyel görüyoruz. ABP Commercial'dan yararlanmanın değerini sürekli olarak göreceğimize inanıyoruz.", + "Landing_Page_CustomerStory_3": "ABP'yi seviyoruz. Her şeyi sıfırdan yazmak zorunda kalmıyoruz. Kullanıma hazır özelliklerden başlıyoruz ve sadece gerçekten yazmamız gerekenlere odaklanıyoruz. Ayrıca, ABP iyi tasarlanmış ve kod daha az hata ile yüksek kalitede. İhtiyacımız olan her şeyi kendi başımıza yazmak zorunda kalsaydık, yıllarımızı harcamak zorunda kalabilirdik. Hoşumuza giden bir diğer şey de yeni sürümün, sorun düzeltmenin ya da iyileştirmenin çok kısa bir süre içinde\nher iki haftada bir çıkması. Çok uzun süre beklemiyoruz.", + "Landing_Page_CustomerStory_4": "ABP Commercial harika bir ürün, tavsiye ederim. Müşterilerimiz için ticari ürünleri tek bir yapılandırılabilir platformda pazara sunuyor. Çerçeve ve araçların herhangi bir ekibe sağladığı hızlı başlangıç her kuruşa değer. ABP Commercial ihtiyaçlarımız için en uygun üründü.", + "Landing_Page_AdditionalServices": "Özel veya toplu lisans, işe alım, canlı eğitim ve destek, özel proje geliştirme, mevcut projeleri taşıma ve daha fazlası...", + "Landing_Page_IncludedDeveloperLicenses": "{0} geliştirici lisansı dahil", + "Landing_Page_SeeOnDemo": "Demo'da göster", + "Landing_Page_LeptonThemes": "Lepton Temaları", + "Landing_Page_AccountModuleDescription_1": "Bu modül bir uygulama için kimlik doğrulama sistemini uygular;", + "Landing_Page_AccountModuleDescription_2": "Kullanıcı adı ve şifre ile bir giriş sayfası sağlar", + "Landing_Page_AccountModuleDescription_3": "Yeni bir hesap oluşturmak için bir kayıt sayfası sağlar.", + "Landing_Page_AccountModuleDescription_4": "Bir şifre sıfırlama bağlantısını e-posta olarak göndermek için bir şifremi unuttum sayfası sağlar.", + "Landing_Page_AccountModuleDescription_5": "UI ile e-posta onayı işlevselliği sağlar.", + "Landing_Page_AccountModuleDescription_6": "İki faktörlü kimlik doğrulama uygular (SMS ve e-posta).", + "Landing_Page_AccountModuleDescription_7": "Kullanıcı kilitleme uygular (belirli bir zaman aralığında geçersiz kimlik bilgileri nedeniyle belirli sayıda başarısız oturum açıldığında hesabı belirlenen süre boyunca kilitler).", + "Landing_Page_AccountModuleDescription_8": "Identity Server kimlik doğrulama sunucusu kullanıcı arayüzünü ve işlevselliğini uygular.", + "Landing_Page_AccountModuleDescription_9": "Allows to switch between tenants in a multi-tenant environment.", + "Landing_Page_AccountModuleDescription_10": "Uygulamanın UI dilini değiştirmeye izin verir.", + "Landing_Page_AuditLoggingModuleDescription_1": "Bu modül, denetim altyapısı için denetim günlüğü raporlama kullanıcı arayüzü sağlar. Denetim günlüğü girdilerini ve varlık değişikliği günlüklerini aramaya, filtrelemeye ve göstermeye izin verir.", + "Landing_Page_AuditLoggingModuleDescription_2": "Bir denetim günlüğü girdisi, her bir müşteri talebi hakkında kritik verilerden oluşur:", + "Landing_Page_AuditLoggingModuleDescription_3": "URL, Tarayıcı, IP adresi, istemci adı", + "Landing_Page_AuditLoggingModuleDescription_4": "Kullanıcı", + "Landing_Page_AuditLoggingModuleDescription_5": "HTTP yöntemi, HTTP dönüş durum kodu", + "Landing_Page_AuditLoggingModuleDescription_6": "Başarı/başarısızlık, varsa istisna ayrıntıları", + "Landing_Page_AuditLoggingModuleDescription_7": "İstek yürütme süresi", + "Landing_Page_AuditLoggingModuleDescription_8": "Bu talepte varlıklar oluşturuldu, silindi veya güncellendi (değişen özelliklerle).", + "Landing_Page_BloggingModuleDescription_1": "Bu modül ABP uygulamanıza basit bir blog ekler;", + "Landing_Page_BloggingModuleDescription_2": "Tek bir uygulamada birden çok blog oluşturmaya izin verir.", + "Landing_Page_BloggingModuleDescription_3": "Markdown formatını destekler.", + "Landing_Page_BloggingModuleDescription_4": "Bir gönderi için yorum yazmaya izin verir.", + "Landing_Page_BloggingModuleDescription_5": "Blog yazılarına etiket atamaya izin verir.", + "Landing_Page_BloggingModuleDescription_6": "Blog modülünün canlı bir örneği olarak blog.abp.io web sitesine bakın.", + "Landing_Page_ChatModuleDescription_1": "Bu modül, uygulamadaki kullanıcılar arasında gerçek zamanlı mesajlaşma için kullanılır.", + "Landing_Page_ChatModuleDescription_2": "Sohbet sayfasında gerçek zamanlı mesajlaşma.", + "Landing_Page_ChatModuleDescription_3": "Yeni konuşmalar için uygulamadaki kullanıcıları arayın.", + "Landing_Page_ChatModuleDescription_4": "Son konuşmalar için kişi listesi.", + "Landing_Page_ChatModuleDescription_5": "Kullanıcı başka bir sayfaya bakarken yeni mesaj bildirimleri.", + "Landing_Page_ChatModuleDescription_6": "Menü simgesindeki toplam okunmamış mesaj sayısı rozeti.", + "Landing_Page_ChatModuleDescription_7": "Her konuşma için okunmamış mesaj sayısı.", + "Landing_Page_ChatModuleDescription_8": "Tembel yüklü konuşmalar.", + "Landing_Page_DocsModuleDescription_1": "Bu modül teknik dokümantasyon web siteleri oluşturmak için kullanılır;", + "Landing_Page_DocsModuleDescription_2": "Yerleşik GitHub entegrasyonu: GitHub'da doğrudan belge yazın ve yönetin.", + "Landing_Page_DocsModuleDescription_3": "Sürüm oluşturma desteği, GitHub sürümlerine doğrudan entegre edilmiştir.", + "Landing_Page_DocsModuleDescription_4": "Çoklu dili destekler (varsayılan dile geri dönüş desteği ile).", + "Landing_Page_DocsModuleDescription_5": "Markdown ve HTML formatlarını destekler.", + "Landing_Page_DocsModuleDescription_6": "Bir navigasyon ve bir ana hat bölümü sağlar.", + "Landing_Page_DocsModuleDescription_7": "Tek bir uygulamada birden çok proje belgesinin barındırılmasına izin verir.", + "Landing_Page_DocsModuleDescription_8": "GitHub'daki dosyaya bağlantı verir, böylece herkes Düzenle bağlantısına tıklayarak kolayca katkıda bulunabilir.", + "Landing_Page_DocsModuleDescription_9": "GitHub kaynağına ek olarak, dokümantasyon kaynağı olarak bir klasörün kullanılmasına izin verir.", + "Landing_Page_FileManagementModuleDescription_1": "Dosyaları hiyerarşik bir klasör yapısı içinde yükleyin, indirin ve düzenleyin.", + "Landing_Page_FileManagementModuleDescription_2": "Bu modül, dosyaları hiyerarşik bir klasör yapısında yüklemek, indirmek ve düzenlemek için kullanılır. Ayrıca çoklu kiracılığa uyumludur ve kiracılarınız için toplam boyut sınırını belirleyebilirsiniz.", + "Landing_Page_FileManagementModuleDescription_3": "Bu modül BLOB Depolama sistemine dayanmaktadır, bu nedenle dosya içeriklerini depolamak için farklı depolama sağlayıcıları kullanabilir.", + "Landing_Page_IdentityModuleDescription_1": "Bu modül bir uygulamanın Kullanıcı ve Rol sistemini uygular;", + "Landing_Page_IdentityModuleDescription_2": "Microsoft'un ASP.NET Core Identity kütüphanesi üzerine inşa edilmiştir.", + "Landing_Page_IdentityModuleDescription_3": "Sistemdeki rolleri ve kullanıcıları yönetin. Bir kullanıcının birden çok role sahip olmasına izin verilir.", + "Landing_Page_IdentityModuleDescription_4": "Rol ve kullanıcı düzeylerinde izinleri ayarlayın.", + "Landing_Page_IdentityModuleDescription_5": "Kullanıcı başına iki faktörlü kimlik doğrulamayı ve kullanıcı kilitlemeyi etkinleştirin/devre dışı bırakın.", + "Landing_Page_IdentityModuleDescription_6": "Temel kullanıcı profilini ve parolayı yönetin.", + "Landing_Page_IdentityModuleDescription_7": "Sistemdeki talep türlerini yönetin, rollere ve kullanıcılara talepler ayarlayın.", + "Landing_Page_IdentityModuleDescription_8": "Parola karmaşıklığı, kullanıcı oturum açma, hesap ve kilitlemeyi yönetmek için ayar sayfası.", + "Landing_Page_IdentityModuleDescription_9": "LDAP kimlik doğrulamasını destekler.", + "Landing_Page_IdentityModuleDescription_10": "E-posta & telefon numarası doğrulaması sağlar.", + "Landing_Page_IdentityModuleDescription_11": "Kullanıcılar için parola sıfırlama desteği.", + "Landing_Page_IdentityModuleDescription_12": "Sistemdeki organizasyon birimlerini yönetin.", + "Landing_Page_PaymentModuleDescription_1": "Farklı ödeme ağ geçitleri için entegrasyon sağlar.", + "Landing_Page_PaymentModuleDescription_2": "Bu modül ödeme ağ geçitleri için entegrasyon sağlar, böylece müşterilerinizden kolayca ödeme alabilirsiniz.", + "Landing_Page_PaymentModuleDescription_3": "Bu modül aşağıdaki ödeme ağ geçitlerini destekler", + "Welcome_Page_UseSameCredentialForCommercialWebsites": "Hem commercial.abp.io hem de support.abp.io için aynı kimlik bilgilerini kullanın.", + "WatchCrudPagesVideo": "\"ABP Suite ile CRUD Sayfaları Oluşturma\" Videosunu İzleyin!", + "WatchGeneratingFromDatabaseVideo": "\"ABP Suite: Mevcut Veritabanı Tablolarından CRUD Sayfaları Oluşturma\" Videosunu izleyin!", + "WatchTakeCloserLookVideo": "\"Kod üretimine daha yakından bakın: ABP Suite\" videosunu izleyin!", + "ConfirmedEmailAddressRequiredToStartTrial": "Deneme lisansı başlatmak için onaylanmış bir e -posta adresiniz olmalı.", + "EmailVerificationMailNotSent": "E-posta doğrulama postası gönderilemedi.", + "GetConfirmationEmail": "Daha önce bir onay e-postası almadıysanız almak için buraya tıklayın.", + "WhichLicenseTypeYouAreInterestedIn": "Hangi lisans türüyle ilgileniyorsunuz?" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json index 0d67d9cca5..8cf695102b 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json @@ -137,7 +137,6 @@ "Success": "成功", "WeWillReplyYou": "你的消息已经发送! 我们会在短时间内给你答复.", "GoHome": "回到主页面", - "Home": "主页", "CreateLiveDemo": "创建在线演示", "RegisterToTheNewsletter": "注册到时事简报以获取有关ABP.IO的消息,比如新发布的内容.", "EnterYourEmailOrLogin": "输入你的e-mail地址来创建你的演示或者使用你的已有账号登录.", @@ -184,6 +183,7 @@ "ChangingLicenseType": "将来更改我的许可类型吗?", "ChangingLicenseTypeExplanation": "你始终可以在同一许可中添加新的开发人员. 参阅 \"有多少开发者可以参与ABP商业版工作?\". 你还可以通过支付计算出的价格差来升级到更高的许可. 当你升级到更高的许可计划时,可以享受新计划的好处,但是许可升级不会更改许可的到期日期.", "LicenseExtendUpgradeDiff": "许可扩展和升级有什么区别?", + "LicenseExtendUpgradeDiffExplanation": "延长: 你通过延长/续费你的许可证,你将继续获得高级支持和主要或次要更新模块和主题。除此之外,你还将继续创建新的项目。并且你仍然可以使用ABP Suite,这将加快你的开发。当你延长你的许可证时,1年将添加到你的许可证到期日。
升级: 你通过升级你的许可证,你将晋升到更高的许可证方案,这将允许你获得额外的奖励。查看许可证比较表来查看许可证方案之间的差异。但是,当你升级时,你的许可证到期日不会更改!要延长你的许可证结束日期,你需要延长你的许可证。", "LicenseRenewalCost": "一年后的许可续期费用是多少?", "LicenseRenewalCostExplanation": "标准 Team 许可证的续订(扩展)价格为 ${0},标准 Business License 为 ${1},标准 Enterprise License 为 ${2}。 如果您已经是客户,请登录您的帐户查看可用的续订价格。", "HowDoIRenewMyLicense": "如何续费我的许可证?", @@ -195,10 +195,19 @@ "IsSourceCodeIncludedExplanation4": "

将模块的源代码包含到解决方案中,可以最大程度地自定义该模块. 但是当新版本发布时,将无法自动升级模块.

这些许可均不包含ABP Suite源代码,该源代码是一个外部工具,可以为你生成代码并帮助你进行开发

有关许可类型之间的其它差异查看定价页面.

", "ChangingDevelopers": "我将来可以更改我组织的注册开发人员吗?", "ChangingDevelopersExplanation": "除了将新的开发人员添加到你的许可中之外,你还可以更改现有的开发人员(可以删除一个开发人员并将新的开发人员添加到同一位置),而无需任何额外费用.", + "WhatHappensWhenLicenseEnds": "我的许可证期间结束时,会发生什么?", + "WhatHappensWhenLicenseEndsExplanation1": "你的ABP商业许可证是一个永久许可证。你的许可证到期后,你可以继续开发你的项目。并且你不需要续费你的许可证。你的许可证是一年更新和支持方案默认的。为了继续获得新的功能,性能改进,修复,支持和继续使用ABP Suite,你需要续费你的许可证。你的许可证到期后,你将失去以下功能:", + "WhatHappensWhenLicenseEndsExplanation2": "你不能使用ABP商业创建新的解决方案,但是你可以继续开发你现有的应用程序。", + "WhatHappensWhenLicenseEndsExplanation3": "你将能够获得模块和主题的更新(除了RC或预览版本)。例如,如果你使用模块的v3.2.0版本,你仍然可以获得v3.2.x版本的更新(v3.2.1,v3.2.5...等等)。但是,你不能获得下一个主要或次要版本的更新(例如v3.3.0,v3.3.3,4.x.x...等等)。例如,你的许可证到期后,最新的发行是v4.4.3,之后,它发布了4.4.4版本和4.5.0版本,你将能够访问v4.4.X,但你不能访问v4.5.X。", + "WhatHappensWhenLicenseEndsExplanation4": "你不能在你的许可证到期后安装ABP商业平台上添加的新模块和主题。", + "WhatHappensWhenLicenseEndsExplanation5": "你不能使用ABP Suite。", + "WhatHappensWhenLicenseEndsExplanation6": "你不能再获得高级支持。", + "WhatHappensWhenLicenseEndsExplanation7": "如果你想继续获得这些奖励,你可以继续续费你的许可证。如果你在许可证到期后1个月内续费,将会应用以下折扣:团队许可证{0}%折扣,商业许可证{1}%折扣,企业许可证{2}%折扣。", "WhatHappensWhenLicenseEndsExplanation8": "您生成的 ABP 项目未存储在我们的服务器上。 因此,您有责任保留下载的源代码。 当您的许可证到期时,将无法获取您生成的 ABP 项目源代码。", "WhenShouldIRenewMyLicense": "我什么时候应该续订我的许可?", "WhenShouldIRenewMyLicenseExplanation": "如果您在许可证到期后 1 个月内续订许可证,将享受以下折扣:团队许可证 {0}% 折扣、商业许可证 {1}% 折扣、企业许可证 {2}% 折扣 . 如果您在许可证到期后 1 个月续订许可证,续订价格将与许可证购买价格相同,并且续订不会有折扣。", "TrialPlan": "你们有试用计划吗?", + "TrialPlanExplanation": "ABP商业团队许可证有14天的试用期。若要了解更多信息,请访问这里。此外,我们为团队许可证提供30天的金额返还保证。你只需要在30天内请求退款。商业和企业许可证将提供60%的金额返还保证。这是因为商业和企业许可证包含了所有模块和主题的全部源代码。", "DoYouAcceptBankWireTransfer": "你们接受银行电汇吗?", "DoYouAcceptBankWireTransferExplanation": "是的,我们接受银行电汇。
在通过银行转账发送许可费后,将您的收据和所需的许可类型通过电子邮件发送至accounting@abp.io。 我们的国际银行账户信息:", "HowToUpgrade": "可用新版本时如何升级现有应用程序?", @@ -365,7 +374,9 @@ "CompanySize": "公司规模", "Next": "下一个", "StartTrial": "开始我的免费试用", + "ContactUsQuestions": "如果你有任何问题,请联系我们", "TrialActivatedWarning": "亲爱的{0},用户只能享受 1 个免费试用期。您已经使用了试用期。", + "ActivationRequirement": "你已经距离开始你的试用还有最后一步。
检查你的信息后,我们将激活你的许可证。一旦你的许可证激活,我们将向{0}发送电子邮件。请不要担心,这个过程不会太久!", "SaveAndDownload": "保存和下载", "CompanyNameValidationMessage": "公司名称太长!", "AddressValidationMessage": "地址太长!", @@ -389,6 +400,7 @@ "UserDeveloperDescription": "“开发人员”可以在 ABP 商业版项目中编写代码,下载 ABP 示例项目,并在支持的网站上进行提问。 然而另一方面,“开发者”无法管理这个组织。", "RemoveCurrentUserFromOrganizationWarningMessage": "您正在将自己从自己的组织中移除。 您将无法再管理此组织,您确定吗?", "RenewExistingOrganizationOrCreateNewOneMessage": "您可以通过单击下面的 \"立即延长\"按钮来更新您组织的许可证,因此您可以将许可证到期日期延长 1 年。 如果您继续结帐,您将拥有一个新的组织。 您想继续开始新的组织许可吗?", + "PurchaseTrialOrganizationOrCreateNewOneMessage": "你有试用许可证。如果你想购买试用许可证,请点击“立即购买”按钮。如果你继续支付,你将会有一个新的组织。你想继续使用一个新的组织吗?", "ExtendNow": "立即延长", "CreateNewOrganization": "建立新的组织", "RenewLicenseEarly": "如果我提前更新我的许可证,我会得到一整年吗?", @@ -524,6 +536,7 @@ "Pricing_Page_Testimonial_2": "我们看到了使用 ABP Commercial 能减少定制开发项目开销的价值。 并且团队能够在不同的项目流中统一代码模式。 我们在框架中看到了能比以前更快地构建新功能的更多潜力。 我们相信我们将会持续地看到使用 ABP Commercial 的价值。", "Pricing_Page_Testimonial_3": "我们大爱 ABP。 我们不必从头开始编写所有内容。 我们从\"开箱即用\"的功能开始,只需关注我们真正需要编写的内容。 此外,ABP 架构良好,代码质量高,错误少。 如果我们需要自己来编写所需的一切,我们可能需要花费数年时间。 另一点让我们喜欢的是新版本、问题修复或改进每隔一周很快地就会出现。 我们不会等太久。", "Pricing_Page_Testimonial_4": "ABP 商业版 是一款很值得推荐的出色产品。 是在一个可配置的平台上为我们的客户推向市场的商业产品。 其框架和工具为任何团队提供的快速启动值得每一分钱。 ABP 商业版 最适合我们的需求。", + "Pricing_Page_Testimonial_5": "ABP Framework 不仅是一个框架,它还是项目开发/管理的指南,因为它提供了 DDD、GenericRepository、DI、微服务和模块化培训。 即使你不打算使用框架本身,你也可以通过 docs.abp.io 进行自己的开发,该文档已经做好了专业的准备(OpenIddict、Redis、Quartz 等)。 因为很多东西都是预先构建的,它大大缩短了项目开发时间(例如登录页面、异常处理、数据过滤、种子、审计日志、本地化、自动 API 控制器等)。 作为我们应用程序的一个示例,我使用本地事件总线进行库存控制。 因此我可以通过编写库存处理程序来管理订单移动。 不为 CreationTime,CreatorId 浪费时间真是太好了。 它们正在自动填充。", "AbpBookDownloadArea_ClaimYourEBook": "领取您的掌握ABP框架电子书", "AddMemberModal_Warning_1": "如果您尝试添加的用户名在系统中不存在,请让您的团队成员在 {0} 并与您分享他/她帐户的用户名。", "MyOrganizations_Detail_WelcomeMessage": "欢迎加入您的组织,{0}", @@ -535,7 +548,7 @@ "MyOrganizations_Detail_OwnerRightInfo": "您正在使用您的 {1} 所有者权利中的 {0}。", "MyOrganizations_Detail_CopyApiKey": "复制密钥", "MyOrganizations_Detail_ApiKeyDescription": "API 密钥是托管在 {1} 上的 PRO 包的令牌。 ", - "MyOrganizations_Detail_YourPrivateNugetSource": "您的私有 NuGet 源是 {0}", + "MyOrganizations_Detail_YourPrivateNugetSource": "您的私有 NuGet 源是 {0}", "MyOrganizations_Detail_PrivateNugetSourceWarning": "这将自动添加一个源到您的 ABP 解决方案中的 NuGet.Config。 不要与未经授权的用户共享您的私钥!", "MyOrganizations_Detail_DeveloperSeatInfo": "您正在使用您的 {1} 个开发者席位中的 {0} 个。", "NeedMoreSeatsForYourTeam": "您的团队需要更多席位吗?", @@ -591,6 +604,9 @@ "TrainingDetailsHeaderInfo_TrainingHour": "{0} 小时", "Trainings_Content": "培训内容", "Trial_Page_StartYourFreeTrial": "开始您的免费试用", + "TrialLicenseFeatures": "你将能够享受所有 ABP 商业特性", + "TrialPeriodDays": "你将有 {0} 天的团队许可证", + "TrialForumSupportIncident": "你将有 {0} 个论坛支持事件", "Contact_Page_Title": "联系 ABP 开发团队", "Contact_Page_Description": "如果您需要任何帮助或分享您的想法和意见,请与 ABP 开发团队联系! ABP 支持团队随时准备提供帮助。", "Demo_Page_Title": "创建演示", @@ -644,7 +660,7 @@ "Landing_Page_PreBuiltApplicationModules": "预建应用程序模块,其中包括最常见的 Web 应用程序要求。", "Landing_Page_ChatModule": "聊天", "Landing_Page_DocsModule": "文档", - "Landing_Page_FileManagementModule": "文档", + "Landing_Page_FileManagementModule": "文件管理", "Landing_Page_CustomerStory_1": "ABP 商业版 允许 SC Ventures 在 9 个月内交付银行级多租户silo数据库 SaaS 平台,以支持来自多个集成锚点的大额发票的应收账款/应付账款供应链融资。 ABP 的模块化使团队能够在创纪录的时间内交付,通过所有 VAPT,并通过完整的 CI/CD 和管道将容器化的微服务码部署到生产中。", "Landing_Page_CustomerStory_2": "我们看到了使用 ABP 商业版 来减少定制开发项目开销的价值。 并且团队能够在不同的项目流中统一代码模式。 我们在框架中看到了比以前更快地构建新功能的更多潜力。 我们相信我们将不断看到使用 ABP 商业版 的价值。", "Landing_Page_CustomerStory_3": "我们很爱 ABP。 我们不必从头开始编写所有内容。 我们从\"开箱即用\"的功能开始,只关注我们真正需要编写的内容。 此外,ABP 架构良好,代码质量高,错误少。 如果我们必须自己编写所需的一切,我们可能需要花费数年时间。 另一件事是我们喜欢的是新版本、问题修复或改进每隔一周就很快会出现\n。 我们不会等太久。", @@ -712,6 +728,15 @@ "Landing_Page_PaymentModuleDescription_1": "为不同的支付网关提供集成。", "Landing_Page_PaymentModuleDescription_2": "该模块提供支付网关的集成,因此您可以轻松地从客户那里获得付款。", "Landing_Page_PaymentModuleDescription_3": "该模块支持以下支付网关", - "Welcome_Page_UseSameCredentialForCommercialWebsites": "commercial.abp.iosupport.abp.io使用相同的凭据。" + "Welcome_Page_UseSameCredentialForCommercialWebsites": "commercial.abp.iosupport.abp.io使用相同的凭据。", + "WatchCrudPagesVideo": "观看“创建 CRUD 页面的 ABP Suite”视频!", + "WatchGeneratingFromDatabaseVideo": "观看”ABP Suite:从现有数据库表生成 CRUD 页面”视频!", + "WatchTakeCloserLookVideo": "观看“详细了解ABP Suite 的代码生成”视频!", + "ConfirmedEmailAddressRequiredToStartTrial": "你应该有一个确认的电子邮件地址,以便开始试用许可证。", + "EmailVerificationMailNotSent": "电子邮件验证邮件不能发送。", + "GetConfirmationEmail": "点击这里获取确认邮件 如果你还没有收到。", + "WhichLicenseTypeYouAreInterestedIn": "你感兴趣的许可证类型是什么?", + "BlazoriseLicense": "我们是否需要购买Blazorise许可证?", + "BlazoriseLicenseExplanation": "我公司Volosoft和公司Megabit之间有合作协议,根据此协议,购买将同时包含了Blazorise许可证与ABP商业产品,因此我们的客户不需要再额外购买Blazorise许可证。" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json index c258ea0800..a8b0398369 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json @@ -380,6 +380,8 @@ "TrialLicenseExpireMessage": "您正在使用試用許可證,您的試用許可證將於 {0}到期。", "TryForFree": "免費試用", "TrialLicenseExpiredInfo": "您的試用許可期限已過!", - "CommercialNewsletterConfirmationMessage": "我同意條款和條件隱私政策。" + "CommercialNewsletterConfirmationMessage": "我同意條款和條件隱私政策。", + "BlazoriseLicense": "我们是否需要购买Blazorise许可证?", + "BlazoriseLicenseExplanation": "我公司Volosoft和公司Megabit之间有合作协议,根据此协议,购买将同时包含了Blazorise许可证与ABP商业产品,因此我们的客户不需要再额外购买Blazorise许可证。" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json index b8e8d9b135..8387eb0f5d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json @@ -43,7 +43,7 @@ "Done": "Done", "Open": "Open", "Closed": "Closed", - "RecentQuestionFrom": "Recent question from ", + "RecentQuestionFrom": "Recent question from {0}", "Stackoverflow": "Stackoverflow", "Votes": "votes", "Answer": "Answer", @@ -150,7 +150,6 @@ "GetStarted": "Get Started", "SourceCode": "Source Code", "LeaveComment": "Leave Comment", - "ReadMore": "Read more", "ShowMore": "Show More", "NoPublishedPostsYet": "No published posts yet.", "Name": "Name", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hu.json index 68d70f8615..e0989162c4 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hu.json @@ -29,14 +29,12 @@ "ContributionGuide": "Hozzájárulási útmutató", "BugReport": "Hibajelentés", "SeeAllPosts": "Az összes bejegyzés megtekintése", - "WelcomeToABPCommunity!": "Üdvözöljük az ABP közösségben!", - "MyProfile": "A profilom", - "MyOrganizations": "Szervezeteim", + "WelcomeToABP": "Üdvözöljük az ABP-ben", "EmailNotValid": "Kérjük valós e-mail címet adjon meg!", "FeatureRequest": "Funkciókérés", "CreatePostTitleInfo": "A bejegyzés listán megjelenítendő bejegyzés címe.", "CreatePostSummaryInfo": "A bejegyzés rövid összefoglalása, amely megjelenik a bejegyzéslistán.", - "CreatePostCoverInfo": "Hatékony cikk létrehozásához adjon hozzá egy borítóképet. Tölts fel 16:9-es képarányú képeket a legjobb nézet érdekében. Maximális fájlméret: 1 MB.", + "CreatePostCoverInfo": "Hatékony bejegyzés létrehozásához adj hozzá egy borítóképet. Tölts fel 16:9-es képarányú képeket a legjobb nézet érdekében. Maximális fájlméret: 1 MB.", "ThisExtensionIsNotAllowed": "Ez a bővítmény nem engedélyezett.", "TheFileIsTooLarge": "A fájl túl nagy.", "GoToThePost": "Ugrás a cikkre", @@ -45,7 +43,7 @@ "Done": "Kész", "Open": "Nyisd ki", "Closed": "Zárva", - "LatestQuestionOnThe": "Legújabb kérdés a", + "RecentQuestionFrom": "Friss kérdés tőle {0}", "Stackoverflow": "Stackoverflow", "Votes": "szavazatokat", "Answer": "Válasz", @@ -59,7 +57,7 @@ "QuestionItemErrorMessage": "Nem sikerült lekérni a Stackoverflow legújabb kérdés részleteit.", "Oops": "Hoppá!", "CreatePostSuccessMessage": "A cikk sikeresen beküldve. A webhely adminisztrátorának felülvizsgálata után tesszük közzé.", - "ChooseCoverImage": "Válasszon borítóképet...", + "Browse": "Tallózás", "CoverImage": "Borítókép", "ShareYourExperiencesWithTheABPFramework": "Ossza meg tapasztalatait az ABP keretrendszerrel!", "Optional": "Választható", @@ -88,6 +86,8 @@ "PostRequestFromGithubIssue": "Jelenleg nincs cikkkérés.", "LatestPosts": "Legutóbbi bejegyzések", "ArticleRequests": "Cikkigények", + "ArticleRequestsDescription": "Konkrét tartalmat szeretne látni itt? Megkérheted a közösséget, hogy hozza létre!", + "LatestContentRequests": "Legújabb tartalomkérések", "AllPostRequests": "Lásd: Összes cikkigénylés", "SubscribeToTheNewsletter": "Feliratkozás a Hírlevélre", "NewsletterEmailDefinition": "Információkat kaphat az ABP eseményeiről, például új kiadásokról, ingyenes forrásokról, cikkekről és egyebekről.", @@ -115,7 +115,6 @@ "VideoUrl": "Videó URL-je", "GithubPostUrl": "Github cikk URL-je", "ExternalPostUrl": "Külső cikk URL", - "CreatePostCoverInfo": "Hatékony bejegyzés létrehozásához adj hozzá egy borítóképet. Tölts fel 16:9-es képarányú képeket a legjobb nézet érdekében. Maximális fájlméret: 1 MB.", "ThankYouForContribution": "Köszönjük, hogy hozzájárult az ABP közösséghez.", "GithubPost": "Github cikk", "GithubPostSubmitStepOne": "1. Írjon cikket bármely nyilvános GitHub-tárhelyről Markdown formátumban. példa ", @@ -142,6 +141,49 @@ "MinimumSearchContent": "Legalább 3 karaktert kell megadnod!", "Volo.AbpIo.Domain:060001": "A forrás URL (\"{PostUrl}\") nem Github URL", "Volo.AbpIo.Domain:060002": "A cikk tartalma nem érhető el a Github(\"{PostUrl}\") forrásból.", - "Volo.AbpIo.Domain:060003": "Nem található a cikk tartalma!" + "Volo.AbpIo.Domain:060003": "Nem található a cikk tartalma!", + "SeeMore": "Nézek többet", + "JoinTheABPCommunity": "Csatlakozz az ABP közösséghez", + "ABPCommunityTalks": "ABP közösségi beszélgetések", + "LiveDemo": "Élő Demo", + "GetLicense": "Szerezzen licenszet", + "GetStarted": "Fogj neki", + "SourceCode": "Forráskód", + "LeaveComment": "Hagyj megjegyzést", + "ShowMore": "Mutass többet", + "NoPublishedPostsYet": "Még nincsenek közzétett bejegyzések.", + "Name": "Név", + "Surname": "Vezetéknév", + "WebSite": "Weboldal", + "FullURL": "Teljes URL", + "JobTitle": "Munka megnevezése", + "Prev": "Előző", + "Previous": "Előző", + "Next": "Következő", + "Share": "Részvény", + "SortBy": "Sorrend", + "NoPublishedEventsYet": "Még nincsenek közzétett események.", + "SubscribeYoutubeChannel": "Iratkozz fel a Youtube csatornára", + "Enum:EventType:0": "Beszélgetések", + "MemberNotPublishedPostYet": "Ez a tag még nem tett közzé bejegyzéseket.", + "TimeAgo": "{0} ezelőtt", + "Discord_Page_JoinCommunityMessage": "Csatlakozz az ABP Discord közösséghez", + "Discord_Page_Announce": "Örömmel jelentjük be az ABP Community Discord Servert!", + "Discord_Page_Description_1": "Az ABP közösség az első nap óta növekszik. Egy hivatalos ABP Discord szerver létrehozásával a következő lépésre akartuk vinni, hogy az ABP közösség az azonnali üzenetküldés csodáival kommunikálhasson egymással.", + "Discord_Page_Description_2": "Az ABP Community Discord Server az a hely, ahol bemutathatja alkotásait az ABP Framework segítségével, megoszthatja az Ön számára bevált tippeket, értesülhet az ABP Framework-vel kapcsolatos legfrissebb hírekről és bejelentésekről, egyszerűen cseveghet a közösség tagjaival, hogy eszmét cserélhessen, és érezze jól magát!", + "Discord_Page_Description_3": "Ez az ABP Community Discord szerver a hivatalos, ahol az ABP Core Team jelen van a szerveren, hogy felügyelje.", + "Discord_Page_JoinToServer": "Csatlakozz az ABP Discord szerverhez", + "Events_Page_MetaTitle": "ABP közösségi események", + "Events_Page_MetaDescription": "Az ABP Team által rendezett élő műsorok hétköznapi előadások, tele közösségi tartalommal, demókkal, kérdésekkel és válaszokkal, valamint az ABP-ben zajló eseményekről szóló vitákkal.", + "Events_Page_Title": "ABP közösségi beszélgetések", + "Events_Page_WritingFromUser": "Olvassa el {0} írását az ABP közösségben.", + "Post_Create_Page_MetaTitle": "Új bejegyzés", + "Post_Create_Page_MetaDescription": "Hozzon létre bejegyzést, hogy megossza tapasztalatait az ABP keretrendszerrel kapcsolatban, és hozzájáruljon az ABP közösséghez.", + "Post_Create_Page_CreateNewPost": "Új bejegyzés létrehozása", + "Post_Index_Page_MetaDescription": "Az ABP Community célja egy hozzájárulási környezet létrehozása az ABP keretrendszert használó fejlesztők számára.", + "Layout_Title": "{0} | ABP-közösség", + "Layout_MetaDescription": "Az ABP Community egy olyan környezet, ahol az emberek megoszthatnak bejegyzéseket az ABP keretrendszerről, és követhetik a projekteket.", + "Index_Page_CommunityIntroduction": "Ez az ABP Framework, a .NET és a szoftverfejlesztés központja. Elolvashatja a cikkeket, megnézheti az oktatóvideókat, tájékozódhat az ABP fejlesztési előrehaladásáról és az ABP-vel kapcsolatos eseményekről, segíthet más fejlesztőknek, és megoszthatja szakértelmét az ABP közösséggel.", + "TagsInArticle": "Címkék a cikkben" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json index 0b118ed73d..73b2ff6559 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json @@ -36,7 +36,6 @@ "FeatureRequest": "Özellik isteği", "CreatePostTitleInfo": "Gönderi listesinde gösterilecek gönderinin başlığı.", "CreatePostSummaryInfo": "Gönderi listesinde gösterilecek gönderinin kısa bir özeti.", - "CreatePostCoverInfo": "Etkili bir makale oluşturmak için bir kapak fotoğrafı ekleyin. En iyi görünüm için 16:9 en boy oranlı resimler yükleyin. Maksimum dosya boyutu: 1MB.", "ThisExtensionIsNotAllowed": "Bu uzantıya izin verilmiyor.", "TheFileIsTooLarge": "Dosya çok büyük.", "GoToThePost": "Makaleye git", @@ -46,7 +45,7 @@ "Open": "Açık", "Closed": "Kapalı", "LatestQuestionOnThe": "Son Soru", - "Stackoverflow": "yığın akışı", + "Stackoverflow": "Stackoverflow", "Votes": "oy", "Answer": "Cevap", "Views": "Görüntüleme", @@ -143,6 +142,53 @@ "Volo.AbpIo.Domain:060001": "Kaynak URL (\"{PostUrl}\") Github URL'si değil", "Volo.AbpIo.Domain:060002": "Makale İçeriği Github(\"{PostUrl}\") kaynağında mevcut değil.", "Volo.AbpIo.Domain:060003": "Makale içeriği bulunamadı!", - "MemberNotPublishedPostYet": "Bu üye henüz bir gönderi yayınlamadı." + "MemberNotPublishedPostYet": "Bu üye henüz bir gönderi yayınlamadı.", + "WelcomeToABP": "ABP'ye Hoşgeldiniz", + "Browse": "Göz at", + "ArticleRequestsDescription": "Burada belirli bir içerik mi görmek istiyorsunuz? Topluluktan bunu oluşturmasını isteyebilirsiniz!", + "LatestContentRequests": "Son İçerik Talepleri", + "SeeMore": "Daha fazla göster", + "JoinTheABPCommunity": "ABP Topluluğuna Katılın", + "ABPCommunityTalks": "ABP Topluluk Konuşmaları", + "LiveDemo": "Canlı Demo", + "GetLicense": "Lisans Al", + "GetStarted": "Başlayın", + "SourceCode": "Kaynak Kodu", + "LeaveComment": "Yorum bırak", + "RecentQuestionFrom": "{0}' dan son soru", + "ReadMore": "Daha fazla oku", + "ShowMore": "Daha fazla göster", + "NoPublishedPostsYet": "Henüz yayınlanmış bir gönderi yok.", + "Name": "İsim", + "Surname": "Soyisim", + "WebSite": "Web Sitesi", + "FullURL": "Tam URL", + "JobTitle": "Meslek İsmi", + "Prev": "Önceki", + "Previous": "Önceki", + "Next": "Sonraki", + "Share": "Paylaş", + "SortBy": "Sırala", + "NoPublishedEventsYet": "Henüz yayınlanmış bir etkinlik yok.", + "SubscribeYoutubeChannel": "Youtube Kanalına Abone Ol", + "Enum:EventType:0": "Konuşma", + "TimeAgo": "{0} önce", + "Discord_Page_JoinCommunityMessage": "ABP Discord Topluluğuna Katılın", + "Discord_Page_Announce": "ABP Topluluk Discord Sunucusunu duyurmaktan mutluluk duyuyoruz!", + "Discord_Page_Description_1": "ABP Topluluğu ilk günden beri büyüyor. Resmi bir ABP Discord sunucusu oluşturarak bunu bir sonraki adıma taşımak istedik, böylece ABP Topluluğu anlık mesajlaşmanın harikalarını kullanarak birbirleriyle etkileşime geçebilir.", + "Discord_Page_Description_2": "ABP Topluluğu Discord Sunucusu, ABP Framework kullanarak yarattıklarınızı sergileyebileceğiniz, işinize yarayan ipuçlarını paylaşabileceğiniz, ABP Framework ile ilgili en son haberleri ve duyuruları takip edebileceğiniz, fikir alışverişinde bulunmak ve eğlenmek için topluluk üyeleriyle sohbet edebileceğiniz bir yerdir!", + "Discord_Page_Description_3": "Bu ABP Topluluk Discord Sunucusu, ABP Çekirdek Ekibinin sunucuda izlenmesi için mevcut olduğu resmi sunucudur.", + "Discord_Page_JoinToServer": "ABP Discord Sunucusuna Katılın", + "Events_Page_MetaTitle": "ABP Topluluk Etkinlikleri", + "Events_Page_MetaDescription": "ABP Ekibi tarafından düzenlenen canlı programlar, topluluk içeriği, demolar, Soru-Cevap ve ABP'de neler olup bittiğine dair tartışmalarla dolu rahat oturumlardır.", + "Events_Page_Title": "ABP Topluluk Konuşmaları", + "Events_Page_WritingFromUser": "ABP Topluluğunda {0} adlı kişiden gelen yazıları okuyun.", + "Post_Create_Page_MetaTitle": "Yeni Gönderi", + "Post_Create_Page_MetaDescription": "ABP çerçevesi hakkındaki deneyimlerinizi paylaşmak ve ABP Topluluğuna katkıda bulunmak için gönderinizi oluşturun.", + "Post_Create_Page_CreateNewPost": "Yeni Gönderi Oluştur", + "Post_Index_Page_MetaDescription": "ABP Topluluğu'nun amacı, ABP çerçevesini kullanan geliştiriciler için bir katkı ortamı yaratmaktır.", + "Layout_Title": "{0} | ABP Topluluğu", + "Layout_MetaDescription": "ABP Topluluğu, insanların ABP çerçevesi hakkında paylaşımlarda bulunabileceği ve projeleri takip edebileceği bir ortamdır.", + "Index_Page_CommunityIntroduction": "Burası ABP Çerçevesi, .NET ve yazılım geliştirme için bir merkezdir. Makaleleri okuyabilir, eğitim videolarını izleyebilir, ABP'nin gelişim süreci ve ABP ile ilgili etkinlikler hakkında bilgi alabilir, diğer geliştiricilere yardımcı olabilir ve uzmanlığınızı ABP topluluğu ile paylaşabilirsiniz." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json index 3513a37dde..657d85160c 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json @@ -43,7 +43,7 @@ "Done": "完成", "Open": "打开", "Closed": "关闭", - "RecentQuestionFrom": "最近的问题来自", + "RecentQuestionFrom": "最近的问题来自 {0}", "Stackoverflow": "Stackoverflow", "Votes": "票数", "Answer": "回答", @@ -150,7 +150,6 @@ "GetStarted": "开始使用", "SourceCode": "源代码", "LeaveComment": "发表评论", - "ReadMore": "阅读更多", "ShowMore": "展示更多", "NoPublishedPostsYet": "还没有发布的帖子。", "Name": "名字", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/AbpIoDocsResource.cs b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/AbpIoDocsResource.cs index bd155f768d..2000e8f1b6 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/AbpIoDocsResource.cs +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/AbpIoDocsResource.cs @@ -1,5 +1,8 @@ -namespace AbpIoLocalization.Docs.Localization +using Volo.Abp.Localization; + +namespace AbpIoLocalization.Docs.Localization { + [LocalizationResourceName("AbpIoDocs")] public class AbpIoDocsResource { diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/en.json index 189662ee7c..7f01b621c1 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/en.json @@ -1,5 +1,6 @@ { "culture": "en", "texts": { + "Buy": "Buy" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/hu.json new file mode 100644 index 0000000000..292fb51cd2 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/hu.json @@ -0,0 +1,6 @@ +{ + "culture": "hu", + "texts": { + "FAQ": "GYIK" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 2a5f9a2584..faba7ab94e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -293,7 +293,6 @@ "ExploreDocumentationAndGuides": "Explore the comprehensive documentation and guides.", "Documentations": "Documentation", "Views": "Views", - "ReadMore": "Read More", "EnterYouEmailToGetNews": "Enter your email to get the latest news about the ABP Framework", "Tiered": "Tiered", "SeparateIdentityServer": "Separate Identity Server", @@ -378,6 +377,13 @@ "IntroducingTheSolution": "Introducing the eShopOnAbp Solution", "RunningTheSolution": "Running the Solution", "UnderstandingTheAuthenticationSystem": "Understanding the Authentication System", + "ExploringTheApplications": "Exploring the Applications", + "UnderstandingTheAPIGateways": "Understanding the API Gateways", + "DevelopingTheMicroservices": "Developing the Microservices", + "UnderstandingTheInfrastructure": "Understanding the Infrastructure", + "DiggingInTheUseCases": "Digging in the Use Cases", + "DeployingTheSolution": "Deploying the Solution", + "ThisBookIsInDraftStageAndIsNotCompletedYet": "This book is in draft stage and is not completed yet.", "Authors": "Authors", "MicroserviceEBook": "Microservice E-Book", "SelectUITheme": "Select UI Theme", @@ -386,6 +392,7 @@ "LeptonXLiteThemeInfo": " A modern and stylish Bootstrap UI theme. Ideal if you want to have a production ready UI theme. This is the newest theme and is the default.", "BasicThemeInfo": "Minimalist UI theme with plain Bootstrap colors and styles. Ideal if you will build your own UI theme.", "SeeDocumentation": "See documentation.", - "SeeFullScreen": "🖼️ See the screenshot" + "SeeFullScreen": "🖼️ See the screenshot", + "BuildingMicroserviceSolutionsShortDescription": "This book is a reference guide for developing and managing microservice-based applications using the ABP Framework." } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json index cb1639fd42..2a6ebf2feb 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json @@ -174,6 +174,7 @@ "CreateProjectWizard": "Ez a varázsló új projektet hoz létre az indítási sablonból, amely megfelelően be van állítva a projekt elindításához.", "TieredOption": "Létrehoz egy többszintű megoldást, ahol a webes és a HTTP API-rétegek fizikailag el vannak választva. Ha nincs bejelölve, réteges megoldást hoz létre, amely kevésbé bonyolult és a legtöbb forgatókönyvhöz megfelelő.", "SeparateIdentityServerOption": "A szerveroldalt két alkalmazásra választja szét: az első az identitáskiszolgálóhoz, a második pedig a szerveroldali HTTP API-hoz való.", + "ProgressiveWebApplicationOption": "Progresszív webalkalmazásként határozza meg a projektet", "UseslatestPreVersion": "A legújabb kiadás előtti verziót használja", "ReadTheDocumentation": "Olvassa el a dokumentációt", "Documentation": "Dokumentáció", @@ -214,7 +215,11 @@ "SeeDocs": "Lásd: Dokumentumok", "None": "Egyik sem", "Application": "Alkalmazás", + "ApplicationExplanation": "Teljesen rétegzett megoldást hoz létre a tartományvezérelt tervezési gyakorlatok alapján. Olyan hosszú távú projektekhez ajánlott, amelyek karbantartható és bővíthető kódbázist igényelnek.", + "ApplicationNoLayer": "Alkalmazás (egyrétegű)", + "ApplicationNoLayerExplanation": "Egyrétegű webalkalmazást hoz létre. Egyszerűbb és könnyen érthető architektúrájú alkalmazás készítéséhez ajánlott.", "Module": "Modul", + "ModuleExplanation": "Újrafelhasználható, teljesen rétegzett alkalmazásmodul-megoldást hoz létre. Ezzel a lehetőséggel modulokat hozhat létre a moduláris alkalmazásához.", "PackageName": "Csomag név", "LicenseURL": "Licenc URL", "License": "Engedély", @@ -273,6 +278,120 @@ "SubscribeToNewsletter": "Iratkozzon fel a hírlevélre, hogy tájékozódjon az ABP.IO Platform eseményeiről, például új kiadásokról, cikkekről, ajánlatokról stb.", "FirstEdition": "Első kiadás", "ThankYou": "Köszönöm!", - "CheckboxMandatory": "Ezt ellenőriznie kell a folytatáshoz!" + "CheckboxMandatory": "Ezt ellenőriznie kell a folytatáshoz!", + "UserInterface": "Felhasználói felület", + "APIGateway": "API átjáró", + "Database": "Adatbázis", + "Saas": "Saas", + "OpenSourceWebApp": "Nyílt forráskódú
webalkalmazás", + "Framework": "Keretrendszer", + "AuditLoggingExplanation": "Automatikusan nyomon követheti az összes műveletet és adatváltozást a rendszerben.", + "AbpNewCommandExplanation": "Új megoldásokat hoz létre az ABP indítási sablonjaival.", + "AbpAddModuleCommandExplanation": "Előre elkészített alkalmazásmodulokat telepít a megoldásra", + "AbpUpdateCommandExplanation": "Automatikusan frissíti az összes ABP-vel kapcsolatos NuGet és NPM csomagot a megoldásban.", + "ExploreAllCLICommands": "Fedezze fel az összes CLI-parancsot", + "ExploreDocumentationAndGuides": "Fedezze fel az átfogó dokumentációt és útmutatókat.", + "Documentations": "Dokumentáció", + "Views": "Nézetek", + "EnterYouEmailToGetNews": "Adja meg e-mail címét, hogy megkapja a legfrissebb híreket az ABP-keretrendszerrel kapcsolatban", + "Tiered": "Többszintű", + "SeparateIdentityServer": "Külön identitásszerver", + "ProgressiveWebApplication": "Progresszív webes alkalmazás", + "Preview": "Előnézet", + "CreateANewSolution": "Hozzon létre egy új megoldást", + "ABPFrameworkFeatures": "Az ABP keretrendszer jellemzői", + "Commercial": "Kereskedelmi", + "ThirdPartyTools": "Harmadik féltől származó eszközök", + "Back": "Vissza", + "Community": "Közösség", + "SeeMore": "Többet látni", + "DetailsOfTheEBook": "Az E-könyv részletei", + "JoinOurMarketingNewsletter": "Csatlakozzon marketing hírlevelünkhöz", + "FrameworkNewsletterConfirmationMessage": "Elfogadom az Általános Szerződési Feltételeket és az Adatvédelmi szabályzatot .", + "GetYourFreeEBook": "Szerezze meg ingyenes DDD e-könyvét", + "EverythingYouNeedToKnow": "Minden, amit tudnia kell.", + "PreOrderNow": "Előrendelés most", + "UITheming": "UI témák", + "UIThemingExplanation": "Hozzon létre újrafelhasználható felhasználói felület-témákat és elrendezéseket, vagy használja valamelyik előre elkészített felhasználói felület-témát.", + "DataFilteringExplanation2": "Automatikusan szűrje le az adatbázisból származó lekérdezéseket, hogy könnyen megvalósíthassa az olyan mintákat, mint a soft-delete és a multi-tenancy.", + "NeedHelp": "Kell segítség?", + "GiveYourProjectAName": "Adjon nevet a projektjének", + "SelectProjectType": "Válassza ki a Projekt típusát", + "SelectUIFramework": "Válassza a UI-keretrendszert", + "SelectDatabaseProvider": "Válassza az Adatbázis-szolgáltató lehetőséget", + "SelectDatabaseManagementSystem": "Válassza az Adatbázis-kezelő rendszer lehetőséget", + "InstallingTheABPCLI": "Az ABP CLI telepítése", + "CreateYourProjectNow": "Hozza létre projektjét most", + "OrderOn": "Rendelés itt: {0}", + "DownloadFreeDDDBook": "Ingyenes DDD könyv letöltése", + "WhatIsABPFramework": "Mi az az ABP-keretrendszer?", + "TenantDatabase": "Bérlői {0} adatbázis", + "SharedDatabase": "Megosztott adatbázis", + "ConnectionResolver": "Kapcsolatfeloldó", + "TenantBasedDataFilter": "Bérlő alapú adatszűrő", + "ApplicationCode": "Alkalmazási kód", + "TenantResolution": "Bérlői állásfoglalás", + "TenantUser": "Bérlő {0} felhasználó", + "CardTitle": "Kártya címe", + "View": "Nézet", + "Model": "Modell", + "Email": "Email", + "Password": "Jelszó", + "Address": "Cím", + "Gender": "Nem", + "Male": "Férfi", + "Female": "Nő", + "Submit": "Beküldés", + "Unspecified": "Meg nem határozott", + "StaticFileMiddleware": "Statikus fájl köztes-szoftver", + "RazorViewEngine": "Razor View Engine", + "PhysicalFiles": "Fizikai fájlok (wwwroot)", + "EmbeddedFiles": "Beágyazott fájlok (DLL)", + "DynamicFiles": "Dinamikus fájlok (memória)", + "BuildSolutionsWithAbp": "Építsen karbantartható .NET-megoldásokat az ABP használatával bevált szoftverfejlesztési gyakorlatok követésével.", + "BuyOnAmazon": "Vásároljon az Amazonon", + "BuyOnPackt": "Vásároljon Packt-en", + "Discounted": "Kedvezményes", + "MasteringAbpFramework_Book_KeyFeatures": "Főbb jellemzők", + "MasteringAbpFramework_Book_Key_Features_Description_1": "Építsen robusztus, karbantartható, moduláris és méretezhető szoftvermegoldásokat az ABP Framework segítségével.", + "MasteringAbpFramework_Book_Key_Features_Description_2": "Ismerje meg, hogyan valósíthatja meg a SOLID elveket és a tartományvezérelt tervezést webalkalmazásaiban.", + "MasteringAbpFramework_Book_Key_Features_Description_3": "Fedezze fel, hogyan gyorsítja fel az ABP Framework a fejlesztési ciklust az ismétlődő feladatok automatizálásával.", + "MasteringAbpFramework_Book_Description": "Könyv leírása", + "MasteringAbpFramework_Book_Description_Details_1": "Az ABP Framework egy komplett infrastruktúra modern webalkalmazások létrehozásához a szoftverfejlesztési bevált gyakorlatok és konvenciók követésével. Az ABP magas szintű keretrendszerével és ökoszisztémájával megvalósíthatja a Ne ismételje meg magát (DRY) elvet, és az üzleti kódjára összpontosítson.", + "MasteringAbpFramework_Book_Description_Details_2": "Az ABP Framework megalkotója által írt könyv segít a keretrendszer és a modern webalkalmazás-fejlesztési technikák teljes megértésében. Az alapvető fogalmak lépésről lépésre történő magyarázatával és gyakorlati példákkal megismerheti a modern webes megoldások követelményeit, és megértheti, hogy az ABP Framework hogyan teszi élvezetessé saját megoldásainak fejlesztését. Felfedezi a vállalati webalkalmazás-fejlesztés általános követelményeit, és felfedezheti az ABP által biztosított infrastruktúrát. A könyv során megismerkedhet a szoftverfejlesztés bevált gyakorlataival a karbantartható és moduláris webes megoldások létrehozásához.", + "MasteringAbpFramework_Book_Description_Details_3": "A könyv végére képes lesz egy komplett webes megoldás létrehozására, amely könnyen fejleszthető, karbantartható és tesztelhető.", + "MasteringAbpFramework_Book_WhatYouWillLearn": "Mit fog tanulni", + "MasteringAbpFramework_Book_What_You_Will_Learn_1": "Állítsa be a fejlesztői környezetet, és kezdje el az ABP Framework használatát.", + "MasteringAbpFramework_Book_What_You_Will_Learn_2": "Az Entity Framework Core és a MongoDB segítségével fejlesztheti adathozzáférési rétegét.", + "MasteringAbpFramework_Book_What_You_Will_Learn_3": "Ismerje meg a több területet érintő aggályokat és azt, hogy az ABP hogyan automatizálja az ismétlődő feladatokat.", + "MasteringAbpFramework_Book_What_You_Will_Learn_4": "Ismerkedjen meg a tartományvezérelt tervezés megvalósításával az ABP Framework segítségével.", + "MasteringAbpFramework_Book_What_You_Will_Learn_5": "Hozzon létre felhasználói felület oldalakat és összetevőket az ASP.NET Core MVC (Razor Pages) és a Blazor segítségével.", + "MasteringAbpFramework_Book_What_You_Will_Learn_6": "Moduláris webalkalmazások létrehozásához dolgozzon több bérléssel.", + "MasteringAbpFramework_Book_What_You_Will_Learn_7": "Ismerje meg a modularitást, és hozzon létre újrafelhasználható alkalmazásmodulokat.", + "MasteringAbpFramework_Book_What_You_Will_Learn_8": "Írjon egység-, integrációs és UI-teszteket az ABP-keretrendszer használatával.", + "MasteringAbpFramework_Book_WhoIsThisBookFor": "Kinek szól ez a könyv", + "MasteringAbpFramework_Book_WhoIsThisBookFor_Description": "Ez a könyv azoknak a webfejlesztőknek szól, akik szoftverarchitektúrákat és bevált gyakorlatokat szeretnének megtanulni karbantartható webalapú megoldások Microsoft technológiáit és ABP-keretrendszert használó felépítéséhez. A könyv használatának megkezdéséhez alapszintű C# és ASP.NET Core ismerete szükséges.", + "ComputersAndTechnology": "Számítógépek és technológia", + "BuildingMicroserviceSolutions": "Mikroszolgáltatási megoldások építése", + "MicroserviceBookPracticalGuide": "Ez a könyv egy referencia útmutató a mikroszolgáltatás alapú alkalmazások fejlesztéséhez és kezeléséhez az ABP Framework segítségével. Hivatkozik a .NET Microservice Sample Reference Application : eShopOnContainers alkalmazásra, és tárgyalja az ABP-keretrendszert használó tervezési és megvalósítási megközelítéseket. A könyv végére megtudhatja, hogyan közelíti meg az ABP az olyan általános mikroszolgáltatási bonyolultságokat, mint az engedélyezés, az elosztott tranzakciók, a mikroszolgáltatások közötti kommunikáció, a telepítés stb.", + "IntroducingTheSolution": "Az eShopOnAbp megoldás bemutatása", + "RunningTheSolution": "A megoldás futtatása", + "UnderstandingTheAuthenticationSystem": "A hitelesítési rendszer megértése", + "ExploringTheApplications": "Az alkalmazások felfedezése", + "UnderstandingTheAPIGateways": "Az API-átjárók megértése", + "DevelopingTheMicroservices": "Mikroszolgáltatások fejlesztése", + "UnderstandingTheInfrastructure": "Az infrastruktúra megértése", + "DiggingInTheUseCases": "feltérképezni a használati eseteket", + "DeployingTheSolution": "A megoldás telepítése", + "ThisBookIsInDraftStageAndIsNotCompletedYet": "Ez a könyv tervezet stádiumban van, és még nem készült el.", + "Authors": "Szerzői", + "MicroserviceEBook": "Mikroszolgáltatás e-könyv", + "SelectUITheme": "Válassza ki a felhasználói felület témáját", + "LeptonXLiteTheme": "LeptonX Lite téma", + "BasicTheme": "Alap téma", + "LeptonXLiteThemeInfo": "Modern és stílusos Bootstrap UI téma. Ideális, ha gyártásra kész felhasználói felület témát szeretne. Ez a legújabb téma, és az alapértelmezett.", + "BasicThemeInfo": "Minimalista felhasználói felület téma egyszerű Bootstrap színekkel és stílusokkal. Ideális, ha saját felhasználói felület témát készít.", + "SeeDocumentation": "Lásd a dokumentációt .", + "SeeFullScreen": "🖼️ Nézze meg a képernyőképet" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json index 01fe9f8b7b..97aaafb1b4 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json @@ -279,6 +279,100 @@ "SubscribeToNewsletter": "ABP.IO Platform'u ile ilgili yeni haberler, makaleler, teklifler ve daha fazlası gibi gelişmeler hakkında bilgi almak için bültene abone olun.", "FirstEdition": "İlk Baskı", "ThankYou": "Teşekkürler!", - "CheckboxMandatory": "Devam etmek için bunu kontrol etmeniz gerekiyor!" + "CheckboxMandatory": "Devam etmek için bunu kontrol etmeniz gerekiyor!", + "UserInterface": "Kullanıcı Arayüzü", + "APIGateway": "API Ağ Geçidi", + "Database": "Veritabanı", + "Saas": "Saas", + "OpenSourceWebApp": " Açık kaynak
web uygulaması", + "Framework": "Çerçeve", + "AuditLoggingExplanation": "Sisteminizdeki tüm işlemleri ve veri değişikliklerini otomatik olarak izleyin.", + "AbpNewCommandExplanation": "ABP başlangıç şablonlarını kullanarak yeni çözümler oluşturur.", + "AbpAddModuleCommandExplanation": "Çözümünüze önceden oluşturulmuş uygulama modülleri yükler", + "ExploreAllCLICommands": "Tüm CLI komutlarını keşfedin", + "ExploreDocumentationAndGuides": "Kapsamlı belge ve kılavuzları keşfedin.", + "Documentations": "Belgeler", + "Views": "Görünümler", + "ReadMore": "Daha fazla oku", + "EnterYouEmailToGetNews": "ABP Çerçevesi hakkında en son haberleri almak için e-postanızı girin", + "Tiered": "Katmanlı", + "SeparateIdentityServer": "Ayrı Kimlik Sunucusu", + "Preview": "Önizleme", + "CreateANewSolution": "Yeni bir çözüm oluşturun", + "ABPFrameworkFeatures": "ABP Çerçevesi Özellikleri", + "Commercial": "Ticari", + "ThirdPartyTools": "Üçüncü taraf araçlar", + "Back": "Geri", + "Community": "Topluluk", + "SeeMore": "Daha fazla göster", + "DetailsOfTheEBook": "E-kitap detayları", + "JoinOurMarketingNewsletter": "Pazarlama bültenimize katılın", + "FrameworkNewsletterConfirmationMessage": "Şartlar ve Koşulları ve Gizlilik Politikasını kabul ediyorum.", + "GetYourFreeEBook": "Ücretsiz DDD E-kitabınızı Alın ", + "EverythingYouNeedToKnow": "Bilmeniz gereken her şey", + "PreOrderNow": "Şimdi Ön sipariş verin", + "UITheming": "Arayüz Teması", + "UIThemingExplanation": "Yeniden kullanılabilir UI temaları ve düzenleri oluşturun veya önceden oluşturulmuş UI temalarından birini kullanın.", + "DataFilteringExplanation2": "Soft-delete ve çoklu kiracılık gibi modelleri kolayca uygulamak için veritabanından sorgulama yaparken otomatik olarak filtreleme yapın.", + "AbpUpdateCommandExplanation": "Çözümünüzdeki ABP ile ilgili tüm NuGet ve NPM paketlerini otomatik olarak günceller.", + "NeedHelp": "Yardıma ihtiyacınız var mı?", + "GiveYourProjectAName": "Projenize bir isim verin", + "SelectProjectType": "Proje türünü seçin", + "SelectUIFramework": "Arayüz çerçevesini seçin", + "SelectDatabaseProvider": "Veritabanı sağlayıcısını seçin", + "SelectDatabaseManagementSystem": "Veritabanı yönetim sisteminizi seçin", + "InstallingTheABPCLI": "ABP CLI yükleniyor", + "CreateYourProjectNow": "Projenizi şimdi oluşturun", + "OrderOn": "{0} numaralı sipariş", + "DownloadFreeDDDBook": "DDD E-kitabını Ücretsiz İndirin", + "WhatIsABPFramework": "ABP Çerçevesi nedir?", + "TenantDatabase": "Kiracı {0} Veritabanı", + "SharedDatabase": "Paylaşımlı Veritabanı", + "ConnectionResolver": "Bağlantı Çözücü", + "TenantBasedDataFilter": "Kiracı Tabanlı Veri Filtresi", + "ApplicationCode": "Uygulama Kodu", + "TenantResolution": "Kiracı Çözümü", + "TenantUser": "Kiracı {0} Kullanıcı", + "CardTitle": "Kart Başlığı", + "View": "Görünüm", + "Model": "Model", + "Email": "E-posta", + "Password": "Şifre", + "Address": "Adres", + "Gender": "Cinsiyet", + "Male": "Erkek", + "Female": "Kadın", + "Submit": "Gönder", + "Unspecified": "Belirtilmemiş", + "StaticFileMiddleware": "Statik Dosya Ara Yazılımı", + "RazorViewEngine": "Razor Görünüm Motoru", + "PhysicalFiles": "Fiziksel Dosyalar (wwwroot)", + "EmbeddedFiles": "Gömülü Dosyalar (DLL)", + "DynamicFiles": "Dinamik Dosyalar (Bellek)", + "BuildSolutionsWithAbp": "ABP kullanarak en iyi yazılım geliştirme uygulamalarını takip ederek sürdürülebilir .NET çözümleri oluşturun.", + "BuyOnAmazon": "Amazon'dan satın alın", + "BuyOnPackt": "Packt'ten satın alın", + "Discounted": "İndirimli", + "MasteringAbpFramework_Book_KeyFeatures": "Anahtar Özellikler", + "MasteringAbpFramework_Book_Key_Features_Description_1": "ABP Çerçevesini kullanarak sağlam, sürdürülebilir, modüler ve ölçeklenebilir yazılım çözümleri oluşturun.", + "MasteringAbpFramework_Book_Key_Features_Description_2": "Web uygulamalarınızda SOLID ilkelerini ve etki alanı odaklı tasarımı nasıl uygulayacağınızı öğrenin.", + "MasteringAbpFramework_Book_Key_Features_Description_3": "ABP Çerçevesinin tekrar eden görevleri otomatikleştirerek geliştirme döngünüzü nasıl hızlandırdığını keşfedin.", + "MasteringAbpFramework_Book_Description": "Kitap Açıklaması", + "MasteringAbpFramework_Book_Description_Details_1": "ABP Çerçevesi, yazılım geliştirme en iyi uygulamalarını ve kurallarını izleyerek modern web uygulamaları oluşturmak \n için eksiksiz bir altyapıdır. ABP'nin üst düzey çerçevesi ve ekosistemi ile Kendinizi Tekrar Etmeyin (DRY) ilkesini uygulayabilir ve iş kodunuza odaklanabilirsiniz.", + "MasteringAbpFramework_Book_Description_Details_2": "ABP Çerçevesinin yaratıcısı tarafından yazılan bu kitap,çerçevesini ve modern web uygulaması geliştirme\n tekniklerini tam olarak anlamanıza yardımcı olacaktır. Temel kavramların adım adım açıklamaları ve\n pratik örneklerle, modern bir web çözümünün gereksinimlerini ve ABP Çerçevesinin kendi çözümlerinizi\n geliştirmeyi nasıl keyifli hale getirdiğini anlayacaksınız. Kurumsal web uygulaması geliştirmenin ortak gereksinimlerini\n keşfedecek ve ABP tarafından sağlanan altyapıyı keşfedeceksiniz. Kitap boyunca, sürdürülebilir ve\n modüler web çözümleri oluşturmak için en iyi yazılım geliştirme uygulamalarını öğreneceksiniz.", + "MasteringAbpFramework_Book_Description_Details_3": "Bu kitabın sonunda, geliştirilmesi, bakımı ve test edilmesi kolay eksiksiz bir web çözümü\n oluşturabileceksiniz.", + "MasteringAbpFramework_Book_WhatYouWillLearn": "Ne Öğreneceksiniz", + "MasteringAbpFramework_Book_What_You_Will_Learn_1": "Geliştirme ortamını kurun ve ABP Çerçevesi ile çalışmaya başlayın.", + "MasteringAbpFramework_Book_What_You_Will_Learn_2": "Veri erişim katmanınızı geliştirmek için Entity Framework Core ve MongoDB ile çalışın.", + "MasteringAbpFramework_Book_What_You_Will_Learn_3": "Birbiriyle kesişen endişeleri ve ABP'nin tekrarlayan görevleri nasıl otomatikleştirdiğini anlayın.", + "MasteringAbpFramework_Book_What_You_Will_Learn_4": "ABP Çerçevesi ile etki alanı odaklı tasarımın uygulanmasını öğrenin.", + "MasteringAbpFramework_Book_What_You_Will_Learn_5": "ASP.NET Core MVC (Razor Pages) ve Blazor ile UI sayfaları ve bileşenleri oluşturun.", + "MasteringAbpFramework_Book_What_You_Will_Learn_6": "Modüler web uygulamaları oluşturmak için çoklu kiracılık ile çalışın.", + "MasteringAbpFramework_Book_What_You_Will_Learn_7": "Modülerliği anlayın ve yeniden kullanılabilir uygulama modülleri oluşturun.", + "MasteringAbpFramework_Book_What_You_Will_Learn_8": "ABP Çerçevesini kullanarak birim, entegrasyon ve UI testleri yazın.", + "MasteringAbpFramework_Book_WhoIsThisBookFor": "Bu kitap kimler için?", + "MasteringAbpFramework_Book_WhoIsThisBookFor_Description": "Bu kitap, Microsoft teknolojilerini ve ABP Çerçevesini kullanarak sürdürülebilir web tabanlı çözümler\n oluşturmak için yazılım mimarilerini ve en iyi uygulamaları öğrenmek isteyen web geliştiricileri içindir.\n Bu kitaba başlamak için temel C# ve ASP.NET Core bilgisi gereklidir.", + "ComputersAndTechnology": "Bilgisayar ve Teknoloji", + "ThisBookIsInDraftStageAndIsNotCompletedYet": "Bu kitap taslak aşamasındadır ve henüz tamamlanmamıştır." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json index 9a1373f76b..7826f00ca2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json @@ -293,7 +293,6 @@ "ExploreDocumentationAndGuides": "探索全面的文档和指南。", "Documentations": "文档", "Views": "意见", - "ReadMore": "阅读更多", "EnterYouEmailToGetNews": "输入您的电子邮件以获取有关 ABP 框架的最新消息", "Tiered": "分层", "SeparateIdentityServer": "独立的身份服务器", @@ -372,6 +371,7 @@ "MasteringAbpFramework_Book_What_You_Will_Learn_8": "使用 ABP 框架编写单元、集成和 UI 测试。", "MasteringAbpFramework_Book_WhoIsThisBookFor": "这本书是给谁看的", "MasteringAbpFramework_Book_WhoIsThisBookFor_Description": "本书适用于希望学习软件架构和最佳实践的 Web 开发人员,以使用 Microsoft 技术和 ABP 框架构建\n 可维护的基于 Web 的解决方案。 C#\n 和 ASP.NET Core 的基本知识是开始阅读本书所必需的。", - "ComputersAndTechnology": "计算机与技术" + "ComputersAndTechnology": "计算机与技术", + "ThisBookIsInDraftStageAndIsNotCompletedYet": "这本书正在草案阶段,还没有完成。" } } \ No newline at end of file diff --git a/build/common.ps1 b/build/common.ps1 index 01a7565fba..61d9cf03d2 100644 --- a/build/common.ps1 +++ b/build/common.ps1 @@ -20,8 +20,7 @@ $solutionPaths = @( "../modules/background-jobs", "../modules/account", "../modules/cms-kit", - "../modules/blob-storing-database", - "../studio" + "../modules/blob-storing-database" ) if ($full -eq "-f") diff --git a/common.DotSettings b/common.DotSettings index 5c1cda48fd..f2cc0339ae 100644 --- a/common.DotSettings +++ b/common.DotSettings @@ -38,5 +38,6 @@ False False False + True True \ No newline at end of file diff --git a/docs/en/Apps/VoloDocs.md b/docs/en/Apps/VoloDocs.md index f85aa78bbd..9fe8ff2507 100644 --- a/docs/en/Apps/VoloDocs.md +++ b/docs/en/Apps/VoloDocs.md @@ -23,13 +23,13 @@ https://github.com/abpframework/abp/tree/master/modules/docs You can download the VoloDocs release from the following links: -http://apps.abp.io/VoloDocs/VoloDocs.win-x64.zip - **Windows 64 bit** +https://apps.abp.io/VoloDocs/VoloDocs.win-x64.zip - **Windows 64 bit** -http://apps.abp.io/VoloDocs/VoloDocs.win-x86.zip - **Windows 32 bit** +https://apps.abp.io/VoloDocs/VoloDocs.win-x86.zip - **Windows 32 bit** -http://apps.abp.io/VoloDocs/VoloDocs.osx-x64.zip - **MacOS** +https://apps.abp.io/VoloDocs/VoloDocs.osx-x64.zip - **MacOS** -http://apps.abp.io/VoloDocs/VoloDocs.linux-x64.zip - **Linux** +https://apps.abp.io/VoloDocs/VoloDocs.linux-x64.zip - **Linux** Notice that, all installations are self-contained deployments. It means all the required third-party dependencies along with the version of .NET Core is included. So you don't need to install any .NET Core SDK / Runtime. diff --git a/docs/en/Audit-Logging.md b/docs/en/Audit-Logging.md index 2076764345..b3bc4335e1 100644 --- a/docs/en/Audit-Logging.md +++ b/docs/en/Audit-Logging.md @@ -41,6 +41,7 @@ Here, a list of the options you can configure: * `IsEnabledForAnonymousUsers` (default: `true`): If you want to write audit logs only for the authenticated users, set this to `false`. If you save audit logs for anonymous users, you will see `null` for `UserId` values for these users. * `AlwaysLogOnException` (default: `true`): If you set to true, it always saves the audit log on an exception/error case without checking other options (except `IsEnabled`, which completely disables the audit logging). * `IsEnabledForGetRequests` (default: `false`): HTTP GET requests should not make any change in the database normally and audit log system doesn't save audit log objects for GET request. Set this to `true` to enable it also for the GET requests. +* `DisableLogActionInfo` (default: `false`):If you set to true, Will no longer log `AuditLogActionInfo`. * `ApplicationName`: If multiple applications saving audit logs into a single database, set this property to your application name, so you can distinguish the logs of different applications. * `IgnoredTypes`: A list of `Type`s to be ignored for audit logging. If this is an entity type, changes for this type of entities will not be saved. This list is also used while serializing the action parameters. * `EntityHistorySelectors`: A list of selectors those are used to determine if an entity type is selected for saving the entity change. See the section below for details. diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index 4ee58d84ef..869a8075af 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -80,6 +80,41 @@ After you have installed these NuGet packages, you need to configure your projec } ```` +### Specifying Queue + +You can use the [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) to specify the queue. + +````csharp +using System.Threading.Tasks; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Emailing; + +namespace MyProject +{ + [Queue("alpha")] + public class EmailSendingJob + : AsyncBackgroundJob, ITransientDependency + { + private readonly IEmailSender _emailSender; + + public EmailSendingJob(IEmailSender emailSender) + { + _emailSender = emailSender; + } + + public override async Task ExecuteAsync(EmailSendingArgs args) + { + await _emailSender.SendAsync( + args.EmailAddress, + args.Subject, + args.Body + ); + } + } +} +```` + ### Dashboard Authorization Hangfire Dashboard provides information about your background jobs, including method names and serialized arguments as well as gives you an opportunity to manage them by performing different actions – retry, delete, trigger, etc. So it is important to restrict access to the Dashboard. diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md index 2b756f2958..e545681d71 100644 --- a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md +++ b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md @@ -10,16 +10,16 @@ Try this version and provide feedback for the stable ABP v6.0! Thank you to all. Follow the steps below to try version 6.0.0 RC today: -1) **Upgrade** the ABP CLI to version `6.0.0-rc.1` using a command line terminal: +1) **Upgrade** the ABP CLI to version `6.0.0-rc.5` using a command line terminal: ````bash -dotnet tool update Volo.Abp.Cli -g --version 6.0.0-rc.1 +dotnet tool update Volo.Abp.Cli -g --version 6.0.0-rc.5 ```` **or install** it if you haven't before: ````bash -dotnet tool install Volo.Abp.Cli -g --version 6.0.0-rc.1 +dotnet tool install Volo.Abp.Cli -g --version 6.0.0-rc.5 ```` 2) Create a **new application** with the `--preview` option: @@ -199,7 +199,7 @@ The following improvements have been made on [eShopOnAbp project](https://github * Performance Improvements have been made in the **Settings Module** and tabs on the *Settings* page are lazy loading now. * Some improvements have been made in the CMS Kit Module. You can see the improvements from [here](https://github.com/abpframework/abp/issues/11965). -If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/6.0.0-rc.1), which contains a list of all the issues and pull requests closed in this version. +If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/6.0.0-rc.5), which contains a list of all the issues and pull requests closed in this version. diff --git a/docs/en/Blog-Posts/2022-09-21 v6_0_Release_Stable/POST.md b/docs/en/Blog-Posts/2022-09-21 v6_0_Release_Stable/POST.md new file mode 100644 index 0000000000..2049c24352 --- /dev/null +++ b/docs/en/Blog-Posts/2022-09-21 v6_0_Release_Stable/POST.md @@ -0,0 +1,76 @@ +# ABP.IO Platform 6.0 Final Has Been Released! + +[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 6.0 versions have been released today. + +## What's New With 6.0? + +Since all the new features are already explained in details with the [6.0 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published) for all the features and enhancements. + +## Getting Started with 6.0 + +### Creating New Solutions + +You can create a new solution with the ABP Framework version 6.0 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). + +> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. + +### How to Upgrade an Existing Solution + +#### Install/Update the ABP CLI + +First of all, install the ABP CLI or upgrade to the latest version. + +If you haven't installed it yet: + +```bash +dotnet tool install -g Volo.Abp.Cli +``` + +To update an existing installation: + +```bash +dotnet tool update -g Volo.Abp.Cli +``` + +#### Upgrading Existing Solutions with the ABP Update Command + +[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: + +```bash +abp update +``` + +Run this command in the root folder of your solution. + +## Migration Guides + +Check the following migration guides for the applications with version 5.3 that are upgrading to version 6.0. + +* [ABP Framework 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/abp/6.0/Migration-Guides/Abp-6_0) +* [ABP Commercial 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/commercial/6.0/migration-guides/v6_0) + +## Community News + +### New ABP Community Posts + +Here are some of the recent posts added to the [ABP Community](https://community.abp.io/): + +* [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) has created two new community articles: + * [Consuming gRPC Services from Blazor WebAssembly Application Using gRPC-Web](https://community.abp.io/posts/consuming-grpc-services-from-blazor-webassembly-application-using-grpcweb-dqjry3rv) + * [Using gRPC with the ABP Framework](https://community.abp.io/posts/using-grpc-with-the-abp-framework-2dgaxzw3) +* [Malik Masis](https://twitter.com/malikmasis) also has created two new community articles: + * [Consuming HTTP APIs from a .NET Client Using ABP's Client Proxy System](https://community.abp.io/posts/consuming-http-apis-from-a-.net-client-using-abps-client-proxy-system-xriqarrm) + * [Using MassTransit via eShopOnAbp](https://community.abp.io/posts/using-masstransit-via-eshoponabp-8amok6h8) +* [Xeevis](https://community.abp.io/members/Xeevis) has created her/his first community article, that shows [Prerendering in Blazor WASM applications](https://community.abp.io/posts/prerendering-blazor-wasm-application-with-abp-6.x-2v8590g3). +* [Don Boutwell](https://community.abp.io/members/dboutwell) has created two new community articles: + * [Logging to Datadog from ABP framework](https://community.abp.io/posts/logging-to-datadog-from-abp-framework-fm4ozds4) + * [Configuring Multiple DbContexts in an ABP Framework Project](https://community.abp.io/posts/configuring-multiple-dbcontexts-in-an-abp-framework-project-uoz5is3o) +* [Kirti Kulkarni](https://twitter.com/kirtimkulkarni) has created a new community article: [Deploying ABP angular application to Azure and App Insights integration](https://community.abp.io/posts/deploying-abp-angular-application-to-azure-and-app-insights-integration-4jrhtp01) + +Thanks to the ABP Community for all the contents they have published. You can also [post your ABP related (text or video) contents](https://community.abp.io/articles/submit) to the ABP Community. + +## About the Next Version + +The next feature version will be 7.0. It is planned to release the 7.0 RC (Release Candidate) on November 15 and the final version on December 13, 2022. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). + +Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 8dc6d425dc..7a5e59a62a 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -124,7 +124,7 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample * `--separate-auth-server`: The Identity Server project comes as a separate project and runs at a different endpoint. It separates the Identity Server from the API Host application. If not specified, you will have a single endpoint in the server side. * `--mobile` or `-m`: Specifies the mobile application framework. If not specified, no mobile application will be created. Available options: * `react-native`: React Native. - * `maui`: MAUI. + * `maui`: MAUI. This mobile option is only available for ABP Commercial. * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: * `ef`: Entity Framework Core. * `mongodb`: MongoDB. @@ -145,7 +145,8 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample * `mongodb`: MongoDB. * `--theme`: Specifes the theme. Default theme is `leptonx-lite`. Available themes: * `leptonx-lite`: [LeptonX Lite Theme](/Themes/LeptonXLite/mvc.md). - * `basic`: [Basic Theme](/UI/AspNetCore/Basic-Theme.md). + * `basic`: [Basic Theme](/UI/AspNetCore/Basic-Theme.md). + * **`maui`**: .NET MAUI. A minimalist .NET MAUI application will be created if you specify this option. * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--preview`: Use latest preview version. @@ -161,6 +162,7 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample * `PostgreSQL` * `--local-framework-ref --abp-path`: Uses local projects references to the ABP framework instead of using the NuGet packages. This can be useful if you download the ABP Framework source code and have a local reference to the framework from your application. * `--no-random-port`: Uses template's default ports. +* `--skip-installing-libs` or `-sib`: Skip installing client side packages. See some [examples for the new command](CLI-New-Command-Samples.md) here. @@ -244,7 +246,7 @@ It can also create a new module for your solution and add it to your solution. S > A business module generally consists of several packages (because of layering, different database provider options or other reasons). Using `add-module` command dramatically simplifies adding a module to a solution. However, each module may require some additional configurations which is generally indicated in the documentation of the related module. -Usage +Usage: ````bash abp add-module [options] @@ -278,7 +280,7 @@ abp add-module ProductManagement --new --add-to-solution-file Lists names of open-source application modules. -Usage +Usage: ````bash abp list-modules [options] @@ -294,11 +296,21 @@ abp list-modules * `--include-pro-modules`: Includes commercial (pro) modules in the output. +### list-templates + +Lists all available templates to create a solution. + +Usage: + +```bash +abp list-templates +``` + ### get-source Downloads the source code of a module to your computer. -Usage +Usage: ````bash abp get-source [options] diff --git a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/How-to-Design-Multi-Lingual-Entity.md b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/How-to-Design-Multi-Lingual-Entity.md new file mode 100644 index 0000000000..2c4b860c95 --- /dev/null +++ b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/How-to-Design-Multi-Lingual-Entity.md @@ -0,0 +1,690 @@ +# How to Design Multi-Lingual Entity + +## Introduction + +If you want to open up to the global market these days, end-to-end localization is a must. ABP provides an already established infrastructure for static texts. However, this may not be sufficient for many applications. You may need to fully customize your app for a particular language and region. + +Let's take a look at a few quotes from Christian Arno's article "[How Foreign-Language Internet Strategies Boost Sales](https://www.mediapost.com/publications/article/155250/how-foreign-language-internet-strategies-boost-sal.html)" to better understand the impact of this: + +- 82% of European consumers are less likely to buy online if the site is not in their native tongue ([Eurobarometer survey](http://europa.eu/rapid/pressReleasesAction.do?reference=IP/11/556)). +- 72.4% of global consumers are more likely to buy a product if the information is available in their own language ([Common Sense Advisory](http://www.commonsenseadvisory.com/)). +- The English language currently only accounts for 31% of all online use, and more than half of all searches are in languages other than English. +- Today, 42% of all Internet users are in Asia, while almost one-quarter are in Europe and just over 10% are in Latin America. + +- Foreign languages have experienced exponential growth in online usage in the past decade -- with Chinese now officially the [second-most-prominent-language](http://english.peopledaily.com.cn/90001/90776/90882/7438489.html) on the Web. [Arabic](http://www.internetworldstats.com/stats7.htm) has increased by a whopping 2500%, while English has only risen by 204% + +If you are looking for ways to expand your market share by fully customizing your application for a particular language and region, in this article I will explain how you can do it with ABP framework. + +### Source Code + +You can find the source code of the application at [abpframework/abp-samples](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreMultiLingual). + +### Demo of the Final Application + +At the end of this article, we will have created an application same as in the gif below. + +![data-model](./result.gif) + +## Development + +In order to keep the article short and get rid of unrelated information in the article (like defining entities etc.), we'll be using the [BookStore](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) example, which is used in the "[Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF)" documentation of ABP Framework and we will make the Book entity as multi-lingual. If you do not want to finish this tutorial, you can find the application [here](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore). + +### Determining the data model + +We need a robust, maintainable, and efficient data model to store content in multiple languages. + +> I read many articles to determine the data model correctly, and as a result, I decided to use one of the many approaches that suit us. +> However, as in everything, there is a trade-off here. If you are wondering about the advantages and disadvantages of the model we will implement compared to other models, I recommend you to read [this article](https://vertabelo.com/blog/data-modeling-for-multiple-languages-how-to-design-a-localization-ready-system/). + +![data-model](./data-model.png) + +As a result of the tutorial, we already have the `Book` and `Author` entities, as an extra, we will just add the `BookTranslation`. + +> In the article, we will make the Name property of the Book entity multi-lingual, but the article is independent of the Book entity, you can make the entity you want multi-lingual with similar codes according to your requirements. + +#### Acme.BookStore.Domain.Shared + +Create a folder named `MultiLingualObjects` and create the following interfaces in its contents. + +We will use the `IObjectTranslation` interface to mark the translation of a multi-lingual entity: + +```csharp +public interface IObjectTranslation +{ + string Language { get; set; } +} +``` + +We will use the `IMultiLingualObject` interface to mark multi-lingual entities: + +```csharp +public interface IMultiLingualObject + where TTranslation : class, IObjectTranslation +{ + ICollection Translations { get; set; } +} +``` + +#### Acme.BookStore.Domain + +In the `Books` folder, create the `BookTranslation` class as follows: + +```csharp +public class BookTranslation : Entity, IObjectTranslation +{ + public Guid BookId { get; set; } + + public string Name { get; set; } + + public string Language { get; set; } + + public override object[] GetKeys() + { + return new object[] {BookId, Language}; + } +} +``` + +`BookTranslation` contains the `Language` property, which contains a language code for translation and a reference to the multi-lingual entity. We also have the `BookId` foreign key to help us know which book is translated. + +Implement `IMultiLingualObject` in the `Book` class as follows: + +```csharp +public class Book : AuditedAggregateRoot, IMultiLingualObject +{ + public Guid AuthorId { get; set; } + + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + + public ICollection Translations { get; set; } +} +``` + +Create a folder named `MultiLingualObjects` and add the following class inside of this folder: + +```csharp +public class MultiLingualObjectManager : ITransientDependency +{ + protected const int MaxCultureFallbackDepth = 5; + + public async Task FindTranslationAsync( + TMultiLingual multiLingual, + string culture = null, + bool fallbackToParentCultures = true) + where TMultiLingual : IMultiLingualObject + where TTranslation : class, IObjectTranslation + { + culture ??= CultureInfo.CurrentUICulture.Name; + + if (multiLingual.Translations.IsNullOrEmpty()) + { + return null; + } + + var translation = multiLingual.Translations.FirstOrDefault(pt => pt.Language == culture); + if (translation != null) + { + return translation; + } + + if (fallbackToParentCultures) + { + translation = GetTranslationBasedOnCulturalRecursive( + CultureInfo.CurrentUICulture.Parent, + multiLingual.Translations, + 0 + ); + + if (translation != null) + { + return translation; + } + } + + return null; + } + + protected TTranslation GetTranslationBasedOnCulturalRecursive( + CultureInfo culture, ICollection translations, int currentDepth) + where TTranslation : class, IObjectTranslation + { + if (culture == null || + culture.Name.IsNullOrWhiteSpace() || + translations.IsNullOrEmpty() || + currentDepth > MaxCultureFallbackDepth) + { + return null; + } + + var translation = translations.FirstOrDefault(pt => pt.Language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)); + return translation ?? GetTranslationBasedOnCulturalRecursive(culture.Parent, translations, currentDepth + 1); + } +} +``` + +With `MultiLingualObjectManager`'s `FindTranslationAsync` method, we get the translated version of the book according to `CurrentUICulture`. If no translation of culture is found, we return null. + +> Every thread in .NET has `CurrentCulture` and `CurrentUICulture` objects. + +#### Acme.BookStore.EntityFrameworkCore + +In the `OnModelCreating` method of the `BookStoreDbContext` class, configure the `BookTranslation` as follows: + +```csharp +builder.Entity(b => +{ + b.ToTable(BookStoreConsts.DbTablePrefix + "BookTranslations", + BookStoreConsts.DbSchema); + + b.ConfigureByConvention(); + + b.HasKey(x => new {x.BookId, x.Language}); +}); +``` + +> I haven't explicitly set up a one-to-many relationship between `Book` and `BookTranslation` here, but the entity framework will do it for us. + +After that, you can just run the following command in a command-line terminal to add a new database migration (in the directory of the `EntityFrameworkCore` project): + +```bash +dotnet ef migrations add Added_BookTranslation +``` + +This will add a new migration class to your project. You can then run the following command (or run the `.DbMigrator` application) to apply changes to the database: + +```bash +dotnet ef database update +``` + +Add the following code to the `ConfigureServices` method of the `BookStoreEntityFrameworkCoreModule`: + +```csharp + Configure(options => + { + options.Entity(bookOptions => + { + bookOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Translations); + }); +}); +``` + +Now we can use `WithDetailsAsync` without any parameters on `BookAppService` knowing that `Translations` will be included. + +#### Acme.BookStore.Application.Contracts + +Implement `IObjectTranslation` in the `BookDto` class as follows: + +```csharp +public class BookDto : AuditedEntityDto, IObjectTranslation +{ + public Guid AuthorId { get; set; } + + public string AuthorName { get; set; } + + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + + public string Language { get; set; } +} +``` + +`Language` property is required to understand which language the translated book name belongs to in the UI. + +Create the `AddBookTranslationDto` class in the `Books` folder as follows: + +```csharp +public class AddBookTranslationDto : IObjectTranslation +{ + [Required] + public string Language { get; set; } + + [Required] + public string Name { get; set; } +} +``` + +Add the `AddTranslationsAsync` method to the `IBookAppService` as follows: + +```csharp +public interface IBookAppService : + ICrudAppService< + BookDto, + Guid, + PagedAndSortedResultRequestDto, + CreateUpdateBookDto> +{ + Task> GetAuthorLookupAsync(); + + Task AddTranslationsAsync(Guid id, AddBookTranslationDto input); // added this line +} +``` + +#### Acme.BookStore.Application + +Now, we need to implement the `AddTranslationsAsync` method in `BookAppService` and include `Translations` in the `Book` entity, for this you can change the `BookAppService` as follows: + +```csharp +[Authorize(BookStorePermissions.Books.Default)] +public class BookAppService : + CrudAppService< + Book, //The Book entity + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto>, //Used to create/update a book + IBookAppService //implement the IBookAppService +{ + private readonly IAuthorRepository _authorRepository; + + public BookAppService( + IRepository repository, + IAuthorRepository authorRepository) + : base(repository) + { + _authorRepository = authorRepository; + GetPolicyName = BookStorePermissions.Books.Default; + GetListPolicyName = BookStorePermissions.Books.Default; + CreatePolicyName = BookStorePermissions.Books.Create; + UpdatePolicyName = BookStorePermissions.Books.Edit; + DeletePolicyName = BookStorePermissions.Books.Create; + } + + public override async Task GetAsync(Guid id) + { + //Get the IQueryable from the repository + var queryable = await Repository.WithDetailsAsync(); // this line changed + + //Prepare a query to join books and authors + var query = from book in queryable + join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id + where book.Id == id + select new { book, author }; + + //Execute the query and get the book with author + var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query); + if (queryResult == null) + { + throw new EntityNotFoundException(typeof(Book), id); + } + + var bookDto = ObjectMapper.Map(queryResult.book); + bookDto.AuthorName = queryResult.author.Name; + return bookDto; + } + + public override async Task> GetListAsync(PagedAndSortedResultRequestDto input) + { + //Get the IQueryable from the repository + var queryable = await Repository.WithDetailsAsync(); // this line changed + + //Prepare a query to join books and authors + var query = from book in queryable + join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id + select new {book, author}; + + //Paging + query = query + .OrderBy(NormalizeSorting(input.Sorting)) + .Skip(input.SkipCount) + .Take(input.MaxResultCount); + + //Execute the query and get a list + var queryResult = await AsyncExecuter.ToListAsync(query); + + //Convert the query result to a list of BookDto objects + var bookDtos = queryResult.Select(x => + { + var bookDto = ObjectMapper.Map(x.book); + bookDto.AuthorName = x.author.Name; + return bookDto; + }).ToList(); + + //Get the total count with another query + var totalCount = await Repository.GetCountAsync(); + + return new PagedResultDto( + totalCount, + bookDtos + ); + } + + public async Task> GetAuthorLookupAsync() + { + var authors = await _authorRepository.GetListAsync(); + + return new ListResultDto( + ObjectMapper.Map, List>(authors) + ); + } + + public async Task AddTranslationsAsync(Guid id, AddBookTranslationDto input) + { + var queryable = await Repository.WithDetailsAsync(); + + var book = await AsyncExecuter.FirstOrDefaultAsync(queryable, x => x.Id == id); + + if (book.Translations.Any(x => x.Language == input.Language)) + { + throw new UserFriendlyException($"Translation already available for {input.Language}"); + } + + book.Translations.Add(new BookTranslation + { + BookId = book.Id, + Name = input.Name, + Language = input.Language + }); + + await Repository.UpdateAsync(book); + } + + private static string NormalizeSorting(string sorting) + { + if (sorting.IsNullOrEmpty()) + { + return $"book.{nameof(Book.Name)}"; + } + + if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase)) + { + return sorting.Replace( + "authorName", + "author.Name", + StringComparison.OrdinalIgnoreCase + ); + } + + return $"book.{sorting}"; + } +} +``` + +Create the `MultiLingualBookObjectMapper` class as follows: + +```csharp +public class MultiLingualBookObjectMapper : IObjectMapper, ITransientDependency +{ + private readonly MultiLingualObjectManager _multiLingualObjectManager; + + private readonly ISettingProvider _settingProvider; + + public MultiLingualBookObjectMapper( + MultiLingualObjectManager multiLingualObjectManager, + ISettingProvider settingProvider) + { + _multiLingualObjectManager = multiLingualObjectManager; + _settingProvider = settingProvider; + } + + public BookDto Map(Book source) + { + var translation = AsyncHelper.RunSync(() => + _multiLingualObjectManager.FindTranslationAsync(source)); + + return new BookDto + { + Id = source.Id, + AuthorId = source.AuthorId, + Type = source.Type, + Name = translation?.Name ?? source.Name, + PublishDate = source.PublishDate, + Price = source.Price, + Language = translation?.Language ?? AsyncHelper.RunSync(() => _settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage)), + CreationTime = source.CreationTime, + CreatorId = source.CreatorId, + LastModificationTime = source.LastModificationTime, + LastModifierId = source.LastModifierId + }; + } + + public BookDto Map(Book source, BookDto destination) + { + return default; + } +} +``` + +To map the multi-lingual `Book` entity to `BookDto`, we implement custom mapping using the `IObjectMapper` interface. If no translation is found, default values are returned. + +So far we have created the entire infrastructure. We don't need to change anything in the UI, if there is a translation according to the language chosen by the user, the list view will change. However, I want to create a simple modal where we can add new translations to an existing book in order to see what we have done. + +#### Acme.BookStore.Web + +Create a new razor page named `AddTranslationModal` in the `Books` folder as below. + +**View** + +```html +@page +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model Acme.BookStore.Web.Pages.Books.AddTranslationModal + +@{ + Layout = null; +} + +
+ + Translations + + + + + + + + + +
+``` + +**Model** + +```csharp +public class AddTranslationModal : BookStorePageModel +{ + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid Id { get; set; } + + public List Languages { get; set; } + + [BindProperty] + public BookTranslationViewModel TranslationViewModel { get; set; } + + private readonly IBookAppService _bookAppService; + private readonly ILanguageProvider _languageProvider; + + public AddTranslationModal( + IBookAppService bookAppService, + ILanguageProvider languageProvider) + { + _bookAppService = bookAppService; + _languageProvider = languageProvider; + } + + public async Task OnGetAsync() + { + Languages = await GetLanguagesSelectItem(); + + TranslationViewModel = new BookTranslationViewModel(); + } + + public async Task OnPostAsync() + { + await _bookAppService.AddTranslationsAsync(Id, ObjectMapper.Map(TranslationViewModel)); + + return NoContent(); + } + + private async Task> GetLanguagesSelectItem() + { + var result = await _languageProvider.GetLanguagesAsync(); + + return result.Select( + languageInfo => new SelectListItem + { + Value = languageInfo.CultureName, + Text = languageInfo.DisplayName + " (" + languageInfo.CultureName + ")" + } + ).ToList(); + } + + public class BookTranslationViewModel + { + [Required] + [SelectItems(nameof(Languages))] + public string Language { get; set; } + + [Required] + public string Name { get; set; } + + } +} +``` + +Then, we can open the `BookStoreWebAutoMapperProfile` class and define the required mapping as follows: + +```csharp +CreateMap(); +``` + +Finally, change the content of `index.js` in the `Books` folder as follows: + +```javascript +$(function () { + var l = abp.localization.getResource('BookStore'); + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); + var addTranslationModal = new abp.ModalManager(abp.appPath + 'Books/AddTranslationModal'); // added this line + + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: + [ + { + text: l('Edit'), + visible: abp.auth.isGranted('BookStore.Books.Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + }, + { + text: l('Add Translation'), // added this action + visible: abp.auth.isGranted('BookStore.Books.Edit'), + action: function (data) { + addTranslationModal.open({ id: data.record.id }); + } + }, + { + text: l('Delete'), + visible: abp.auth.isGranted('BookStore.Books.Delete'), + confirmMessage: function (data) { + return l( + 'BookDeletionConfirmationMessage', + data.record.name + ); + }, + action: function (data) { + acme.bookStore.books.book + .delete(data.record.id) + .then(function() { + abp.notify.info( + l('SuccessfullyDeleted') + ); + dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + title: l('Name'), + data: "name" + }, + { + title: l('Author'), + data: "authorName" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), + data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } + } + ] + }) + ); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + editModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +``` + +## Conclusion + +With a multi-lingual application, you can expand your market share, but if not designed well, may your application will be unusable. So, I've tried to explain how to design a sustainable multi-lingual entity in this article. + +### Source Code + +You can find source code of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreMultiLingual). diff --git a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/data-model.png b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/data-model.png new file mode 100644 index 0000000000..c7e02bc2cd Binary files /dev/null and b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/data-model.png differ diff --git a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/result.gif b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/result.gif new file mode 100644 index 0000000000..0bc41cb8f1 Binary files /dev/null and b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/result.gif differ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md index 308358bb67..178a8f261e 100644 --- a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md +++ b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md @@ -1,9 +1,13 @@ # How to Add Custom Properties to the User Entity +> **Note:** If your application is greater than version 4.3.3, please follow [this article](https://community.abp.io/posts/how-to-add-custom-properties-to-the-user-entity-rixchoha). + ## Introduction In this step-by-step article, I will explain how you can customize the user entity class, which is available in every web application you create using the ABP framework, according to your needs. When you read this article, you will learn how to override the services of built-in modules, extend the entities, extend data transfer objects and customize the user interface in the applications you develop using the ABP framework. +> **Note:** This article is not about customizing the `Login` page. If you have such a need, please follow [this article](https://community.abp.io/posts/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd). + You can see the screenshots below which we will reach at the end of the article. ![custom-identity-user-list](./custom-identity-user-list.png) diff --git a/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md new file mode 100644 index 0000000000..df9a8cf41a --- /dev/null +++ b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md @@ -0,0 +1,154 @@ +# How to Add Custom Properties to the User Entity + +> **Note:** If your application is less than version 4.4.x, please follow [this article](https://community.abp.io/posts/how-to-add-custom-property-to-the-user-entity-6ggxiddr). + +## Introduction + +In this step-by-step article, I will explain how you can customize the user entity class, which is available in every web application you create using the ABP framework, according to your needs. When you read this article, you will learn how to override the services of built-in modules, extend the entities, extend data transfer objects and customize the user interface in the applications you develop using the ABP framework. + +> **Note:** This article is not about customizing the `Login` page. If you have such a need, please follow [this article](https://community.abp.io/posts/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd). + +You can see the screenshots below which we will reach at the end of the article. + +![custom-identity-user-list](./custom-identity-user-list.png) + +![new-user](./new-user.png) + +## Preparing the Project + +### Startup template and the initial run + +Abp Framework offers startup templates to get into the work faster. We can create a new startup template using Abp CLI: + +`abp new CustomizeUserDemo` + +> In this article, I will go through the MVC application, but it will work also in the [Angular](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No), [Blazor Server](https://docs.abp.io/en/abp/latest/Getting-Started?UI=BlazorServer&DB=EF&Tiered=No), and [Blazor WebAssembly](https://docs.abp.io/en/abp/latest/Getting-Started?UI=Blazor&DB=EF&Tiered=No) application. + +After the download is finished, we can run **CustomizeUserDemo.DbMigrator** project to create the database migrations and seed the initial data (admin user, role, etc). Then we can run `CustomizeUserDemo.Web` to see that our application is working. + +> Default admin username is **admin** and password is **1q2w3E\*** + +![initial-project](./initial-project.png) + +In this article, we will go through a scenario together and find the solutions to our questions through this scenario. However, since the scenario is not a real-life scenario, it may be strange, please don't get too about this issue :) + +## Step-1 + +Create the Users folder in the **CustomizeUserDemo.Domain.Shared** project, create the class `UserConsts` inside the folder and update the class you created as below: + +```csharp +public static class UserConsts +{ + public const string TitlePropertyName = "Title"; + + public const string ReputationPropertyName = "Reputation"; + + public const int MaxTitleLength = 64; + + public const double MaxReputationValue = 1_000; + + public const double MinReputationValue = 1; +} +``` + +## Step-2 + +Update the `CustomizeUserDemoEfCoreEntityExtensionMappings` class in the **CustomizeUserDemo.EntityFramework** project in the EntityFrameworkCore folder as below: + +```csharp +public static class CustomizeUserDemoEfCoreEntityExtensionMappings +{ + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + CustomizeUserDemoGlobalFeatureConfigurator.Configure(); + CustomizeUserDemoModuleExtensionConfigurator.Configure(); + + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance + .MapEfCoreProperty( + UserConsts.TitlePropertyName, + (_, propertyBuilder) => + { + propertyBuilder.HasDefaultValue(""); + propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength); + } + ).MapEfCoreProperty( + UserConsts.ReputationPropertyName, + (_, propertyBuilder) => + { + propertyBuilder.HasDefaultValue(UserConsts.MinReputationValue); + } + ); + }); + } +} +``` + +This class can be used to map these extra properties to table fields in the database. Please read [this](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities) article to improve your understanding of what we are doing. + +So far, we have added our extra features to the `User` entity and matched these features with the `ef core`. + +Now we need to add migration to see what has changed in our database. This for, open the Package Manager Console (PMC) under the menu Tools > NuGet Package Manager. + +![nuget-package-manager](./nuget-package-manager.png) + +Select the **CustomizeUserDemo.EntityFramework** as the **default project** and execute the following command: + +```bash +Add-Migration "Updated-User-Entity" +``` + +![added-new-migration](./added-new-migration.png) + +This will create a new migration class inside the `Migrations` folder of the **CustomizeUserDemo.EntityFrameworkCore** project. + +> If you are using another IDE than the Visual Studio, you can use `dotnet-ef` tool as [documented here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration). + +Finally, run the **CustomizeUserDemo.DbMigrator** project to update the database. + +When we updated the database, you can see that the `Title` and `Reputation` columns are added to the `Users` table. + +![user-table](./user-table.png) + +## Step-3 +Open the `CustomizeUserDemoModuleExtensionConfigurator` in the **CustomizeUserDemo.Domain.Shared** project, and change the contents of the `ConfigureExtraProperties` method as shown below: +```csharp +private static void ConfigureExtraProperties() +{ + ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity => + { + identity.ConfigureUser(user => + { + user.AddOrUpdateProperty( + UserConsts.TitlePropertyName, + options => + { + options.Attributes.Add(new RequiredAttribute()); + options.Attributes.Add( + new StringLengthAttribute(UserConsts.MaxTitleLength) + ); + } + ); + user.AddOrUpdateProperty( + UserConsts.ReputationPropertyName, + options => + { + options.DefaultValue = UserConsts.MinReputationValue; + options.Attributes.Add( + new RangeAttribute(UserConsts.MinReputationValue, UserConsts.MaxReputationValue) + ); + } + ); + }); + }); +} +``` + +That's it. Now let's run the application and look at the Identity user page. You can also try to edit and recreate a record if you want, it will work even though we haven't done anything extra. Here is the magic code behind ABP framework. + +If there is a situation you want to add, you can click the contribute button or make a comment. Also, if you like the article, don't forget to share it :) + +Happy coding :) diff --git a/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png new file mode 100755 index 0000000000..d459fdeed2 Binary files /dev/null and b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png differ diff --git a/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png new file mode 100644 index 0000000000..896ed947aa Binary files /dev/null and b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png differ diff --git a/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png new file mode 100755 index 0000000000..b64e93a99f Binary files /dev/null and b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png differ diff --git a/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png new file mode 100644 index 0000000000..d3a5c66198 Binary files /dev/null and b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png differ diff --git a/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png new file mode 100755 index 0000000000..680bc9d2e7 Binary files /dev/null and b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png differ diff --git a/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png new file mode 100755 index 0000000000..5bb71b8325 Binary files /dev/null and b/docs/en/Community-Articles/2022-07-19-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png differ diff --git a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md new file mode 100644 index 0000000000..b3870ff067 --- /dev/null +++ b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md @@ -0,0 +1,242 @@ +# Using gRPC with the ABP Framework + +[gRPC](https://grpc.io/) defines itself as an open source, language agnostic, universal, high-performance **Remote Procedure Call (RPC)** framework. + +In this article, I will show you how to create a gRPC service and consume it from a console application with the ABP Framework. While the client application is console in this article, it can easily be a service consuming another service in a microservice system. + +> **This article will be a step by step tutorial.** I wrote the article based on Microsoft's [Code-first gRPC services and clients with .NET](https://docs.microsoft.com/en-us/aspnet/core/grpc/code-first) document. You can read that document for more details about gRPC and the code-first approach. + +## Creating the Application + +Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it yet: + +````bash +dotnet tool install -g Volo.Abp.Cli +```` + +or update to the latest version if you've already installed an old version: + +````bash +dotnet tool update Volo.Abp.Cli -g +```` + +Create an empty folder, open a command-line terminal and type the following command in the terminal window to create a new ABP solution using the ABP CLI: + +````bash +abp new ProductManagement -u blazor -t app --preview +```` + +I've created an application with the Blazor UI, but the UI is not important for this tutorial, you can select your favorite UI option. + +## Open the Solution + +Open the solution in your favorite IDE. I like [Rider](https://www.jetbrains.com/rider/), but Visual Studio, VS Code or any other IDE perfectly works. The following figure shows the solution structure in Rider: + +![solution](solution.png) + +Run the `ProductManagement.DbMigrator` project (a console application) to create the database and seed the initial data. + +## Defining the Service Contract + +We are starting by defining the service contract and DTO classes that will be shared between the server and the client applications. + +Create a `Products` folder in the `ProductManagement.Application.Contracts` project and add a new interface named `IProductAppService`: + +````csharp +using System.Collections.Generic; +using System.ServiceModel; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace ProductManagement.Products; + +[ServiceContract] +public interface IProductAppService : IApplicationService +{ + Task> GetListAsync(); +} +```` + +Your IDE will complain about the `[ServiceContract]` attribute, but it is necessary for the contract-first gRPC library we will be using later. So, add the [System.ServiceModel.Primitives](https://www.nuget.org/packages/System.ServiceModel.Primitives) NuGet package to the `ProductManagement.Application.Contracts` project, and it should be fixed. You can simply edit the `ProductManagement.Application.Contracts.csproj` file and add the following line in an `ItemGroup` tag: + +````xml + +```` + +Or you can use your IDE to find and add that NuGet package, it is up to you. + +I've also used the `ProductDto` class, but haven't defined it yet. Create a new class in the same folder with the `IProductAppService` file: + +````csharp +using System; +using System.Runtime.Serialization; + +namespace ProductManagement.Products; + +[DataContract] +public class ProductDto +{ + [DataMember(Order = 1)] + public Guid Id { get; set; } + + [DataMember(Order = 2)] + public string Name { get; set; } +} +```` + +The `[DataContract]` and `[DataMember]` properties are needed for serialization. In gRPC, property serialization orders are important, because property names are not transferred to the target application, to keep the serialized data small. + +After adding these classes, the `ProductManagement.Application.Contracts` project should look as in the following figure: + +![contracts](contracts.png) + +The contracts part is over. We actually didn't have any dependency to gRPC at that point. Our service and DTOs are pretty plain classes, except a few standard attributes, which are already defined in the .NET Core framework. Now, we can implement the `IProductAppService`. + +## Implementing the Service + +We are implementing the application services in the `ProductManagement.Application` project. So, add a new `Products` folder to that project and define a `ProductAppService` class inside it: + +````csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ProductManagement.Products; + +public class ProductAppService : ProductManagementAppService, IProductAppService +{ + public async Task> GetListAsync() + { + return new List + { + new ProductDto { Id = Guid.NewGuid(), Name = "Product 1" }, + new ProductDto { Id = Guid.NewGuid(), Name = "Product 2" }, + }; + } +} +```` + +This is a pretty standard, plain [application service ](https://docs.abp.io/en/abp/latest/Application-Services)class. All the ABP application service features (validation, audit logging, unit of work, etc.) are available. You can inject [repositories](https://docs.abp.io/en/abp/latest/Repositories) and perform database queries. To keep this article simple, I am returning hard-coded data from here. + +> `ProductManagementAppService` is a base class coming in the ABP startup template. While you don't have to inherit from it, it provides useful base properties and methods you typically need in an application service. + +The application service part is over. Again, we didn't write any gRPC specific code. Don't worry, we will write in the next section. + +## Configuring the gRPC Server + +In this solution, `ProductManagement.HttpApi.Host` is the project that configures and runs the server-side application. So, we will make changes in that project. + +First, add the [protobuf-net.Grpc.AspNetCore](https://www.nuget.org/packages/protobuf-net.Grpc.AspNetCore) NuGet package to the `ProductManagement.HttpApi.Host` project: + +````xml + +```` + +Then open the `ProductManagementHttpApiHostModule.cs` file, find the `ConfigureServices` method and add the following line into this method: + +````csharp +context.Services.AddCodeFirstGrpc(); +```` + +This will register code-first gRPC services to the [dependency injection](https://docs.abp.io/en/abp/latest/Dependency-Injection) system. Then find the `app.UseConfiguredEndpoints()` line in the `OnApplicationInitialization` method and change it as shown below: + +````csharp +app.UseConfiguredEndpoints(endpoints => +{ + endpoints.MapGrpcService(); +}); +```` + +We've configured the `IProductAppService` to handle gRPC requests to that service. The following figure shows the whole change done in the `ProductManagementHttpApiHostModule` class: + +![host-changes-1](host-changes-1.png) + +gRPC handles requests with the HTTP/2 protocol and should listen an endpoint other than the default HTTP endpoint used by the application. We can easily configure the Kestrel server to listen two endpoints, one for our HTTP APIs, the other one for gRPC services. Add the following configuration inside the `appsettings.json` file of the `ProductManagement.HttpApi.Host` project: + +````json +"Kestrel": { + "Endpoints": { + "Https": { + "Url": "https://localhost:44388", + "Protocols": "Http1AndHttp2" + }, + "gRPC": { + "Url": "https://localhost:10042", + "Protocols": "Http2" + } + } +} +```` + +Note that `https://localhost:44388` may be different for your case, since ABP CLI assignes a random port while you're creating a new solution. You can check your port by running the `ProductManagement.HttpApi.Host` project and looking at the address bar on your browser. + +The server-side configuration is done. It is ready to receive gRPC requests. Now, we can change the client to consume the gRPC service we've created. + +## Implementing the Client Side + +The ABP startup solution template comes with a console application to test consuming your HTTP APIs. For this example, the project is named as `ProductManagement.HttpApi.Client.ConsoleTestApp` and located under the `test` folder in the solution. + +First, add the [Grpc.Net.Client](https://www.nuget.org/packages/Grpc.Net.Client) and the [protobuf-net.Grpc](https://www.nuget.org/packages/protobuf-net.Grpc) NuGet packages to the `ProductManagement.HttpApi.Client.ConsoleTestApp` project. + +````xml + + +```` + +Now, open the `ClientDemoService.cs` file under the `ProductManagement.HttpApi.Client.ConsoleTestApp` project and change its contents with the following code block: + +````csharp +using System; +using System.Threading.Tasks; +using Grpc.Net.Client; +using ProductManagement.Products; +using ProtoBuf.Grpc.Client; +using Volo.Abp.DependencyInjection; + +namespace ProductManagement.HttpApi.Client.ConsoleTestApp; + +public class ClientDemoService : ITransientDependency +{ + public async Task RunAsync() + { + using (var channel = GrpcChannel.ForAddress("https://localhost:10042")) + { + var productAppService = channel.CreateGrpcService(); + var productDtos = await productAppService.GetListAsync(); + + foreach (var productDto in productDtos) + { + Console.WriteLine($"[Product] Id = {productDto.Id}, Name = {productDto.Name}"); + } + } + } +} +```` + +We are simply creating a gRPC channel, then creating a client proxy for the `IProductAppService` service. Then we can call its method just like local method calls. You can run the applications to test it. + +## Run the Applications + +First run the `ProductManagement.HttpApi.Host` application. It should show a Swagger UI as shown below: + +![swagger](swagger.png) + +If you see that page, it means your server-side is up and running. Now, you can run the `ProductManagement.HttpApi.Client.ConsoleTestApp` console application to call the gRPC service defined on the server. + +The test console application should produce an output as shown below: + +![client-application](client-application.png) + +As you see, products are returned from the server. That's all, you've done it! + +## Conclusion + +In this article, I've used the [code-first approach](https://docs.microsoft.com/en-us/aspnet/core/grpc/code-first) to implement a gRPC server and consume it in a client application. Code-first approach is very practical if both of your client and server applications are built with .NET. By the help of ABP's layered solution structure, we even didn't add any gRPC dependency into our server-side and contracts. We've just configured gRPC in the hosting side, with a small amount of code. + +gRPC on .NET has different approaches, features, configurations and more details. I suggest you to read [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/grpc) to learn more about it. All the approaches can work with the ABP Framework. Enjoy coding! + +## The Source Code + +* You can find the completed source code here: https://github.com/abpframework/abp-samples/tree/master/GrpcDemo2 + +* You can also see all the changes I've done in this article here: https://github.com/abpframework/abp-samples/pull/200/files diff --git a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/client-application.png b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/client-application.png new file mode 100644 index 0000000000..b16c4a6c1e Binary files /dev/null and b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/client-application.png differ diff --git a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/contracts.png b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/contracts.png new file mode 100644 index 0000000000..ef9f80764d Binary files /dev/null and b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/contracts.png differ diff --git a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/host-changes-1.png b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/host-changes-1.png new file mode 100644 index 0000000000..b9dc207636 Binary files /dev/null and b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/host-changes-1.png differ diff --git a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/solution.png b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/solution.png new file mode 100644 index 0000000000..c3becdbf44 Binary files /dev/null and b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/solution.png differ diff --git a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/swagger.png b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/swagger.png new file mode 100644 index 0000000000..47ed5340c3 Binary files /dev/null and b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/swagger.png differ diff --git a/docs/en/Community-Articles/2022-09-18-Grpc-Demo-Part-2/POST.md b/docs/en/Community-Articles/2022-09-18-Grpc-Demo-Part-2/POST.md new file mode 100644 index 0000000000..b6a49173e1 --- /dev/null +++ b/docs/en/Community-Articles/2022-09-18-Grpc-Demo-Part-2/POST.md @@ -0,0 +1,121 @@ +# Consuming gRPC Services from Blazor WebAssembly Application Using gRPC-Web + +> **WARNING: I've demonstrated [Using gRPC with the ABP Framework](https://community.abp.io/posts/using-grpc-with-the-abp-framework-2dgaxzw3) in my latest post. If you haven't seen it, you should read it before this article, since this is a continuation of that article.** + +In this second part, I will show how to consume the gRPC service from the Blazor WebAssembly application, using the gRPC-Web technology. + +This will be a short article, based on Microsoft's [gRPC-Web in ASP.NET Core gRPC apps](https://learn.microsoft.com/en-us/aspnet/core/grpc/grpcweb) and [Code-first gRPC services and clients with .NET](https://learn.microsoft.com/en-us/aspnet/core/grpc/code-first) documents. For more information, I suggest to check these documents. Let's get started... + +## Configuring the Server Side + +First of all, the server-side should support gRPC-Web. Follow the steps below to enable it: + +### Add Grpc.AspNetCore.Web Package + +Add [Grpc.AspNetCore.Web](https://www.nuget.org/packages/Grpc.AspNetCore.Web) NuGet package to the `ProductManagement.HttpApi.Host` project. + +### Add GrpcWeb Middleware + +Add the following line just before the `app.UseConfiguredEndpoints(...)` line to add the GrpcWeb middleware to your ASP.NET Core request pipeline: + +````csharp +app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); +```` + +### Configure Cors + +ABP's startup template already configures Cors when you create a new solution. However, we need to allow some extra headers in our Cors configuration. + +Add the following line just after the `.WithAbpExposedHeaders()` line in the `OnApplicationInitialization` method of the `ProductManagementHttpApiHostModule` class: + +````csharp +.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding") +```` + +Finally, call `RequireCors` extension method just after the `MapGrpcService` calls: + +````csharp +app.UseConfiguredEndpoints(endpoints => +{ + endpoints + .MapGrpcService() + .RequireCors("__DefaultCorsPolicy"); // Configure Cors for the product service +}); +```` + +`__DefaultCorsPolicy` may seem a magic string here. Let me explain it: ABP startup template configures the default Cors policy with the `context.Services.AddCors(...)` method (you can see it in the source code). If we define a named policy, we should use the same name here. However, when we don't specify, ASP.NET Core uses `__DefaultCorsPolicy` as the policy name by default. If you don't want to use the magic string, you can resolve the `IOptions` service and get the `DefaultPolicyName` from the `CorsOption` object. + +Anyway, that's all on the server-side. We can work on he client now. + +## Configuring the Client Side + +`ProductManagement.Blazor` is the Blazor WebAssembly application in the solution I'd created in the [first article](https://community.abp.io/posts/using-grpc-with-the-abp-framework-2dgaxzw3). We will configure that project to be able to consume the server-side gRPC services from our Blazor application. + +### Add Client-side Nuget Packages + +Add [Grpc.Net.Client](https://www.nuget.org/packages/Grpc.Net.Client), [Grpc.Net.Client.Web](https://www.nuget.org/packages/Grpc.Net.Client.Web) and [protobuf-net.Grpc](https://www.nuget.org/packages/protobuf-net.Grpc) NuGet packages to the `ProductManagement.Blazor` project. We are ready to consume the gRPC services. + +### Consume the Product Service + +Change the `Pages/Index.razor.cs` file's content with the following code block: + +````csharp +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Grpc.Net.Client; +using Grpc.Net.Client.Web; +using ProductManagement.Products; +using ProtoBuf.Grpc.Client; + +namespace ProductManagement.Blazor.Pages; + +public partial class Index +{ + private List Products { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + var channel = GrpcChannel.ForAddress("https://localhost:10042", new GrpcChannelOptions + { + HttpHandler = new GrpcWebHandler(new HttpClientHandler()) + }); + + var productAppService = channel.CreateGrpcService(); + Products = await productAppService.GetListAsync(); + } +} +```` + +* We've created a gRPC channel for the server-side endpoint (surely, you get the address from a configuration) with channel options by specifying that we will use the `GrpcWebHandler`. +* We've created a service proxy object using the `CreateGrpcService` extension method that is defined by the [protobuf-net.Grpc](https://www.nuget.org/packages/protobuf-net.Grpc) NuGet package. +* We've used the service proxy object, `productAppService`, to consume remote endpoint just like a local service. + +That's all. If we want to show the products on the page, we can add the following markup into the `Pages/Index.razor` view: + +````xml +

A list of products:

+ +
    + @foreach(var product in Products) + { +
  • + @product.Name
    + @product.Id.ToString() +
  • + } +
+```` + +Run the applications (first run the `ProductManagement.HttpApi.Host` project, then run the `ProductManagement.Blazor` project in the solution) to see it in action: + +![blazor-product-list](blazor-product-list.png) + +## Conclusion + +In the first part of this article, I'd demonstrated how to implement a gRPC service and consume it in a client application, using the [code-first approach](https://docs.microsoft.com/en-us/aspnet/core/grpc/code-first). In this article, I've demonstrated how to consume the same gRPC service from a Blazor WebAssembly application, using the [gRPC-Web](https://learn.microsoft.com/en-us/aspnet/core/grpc/grpcweb) technology. As you see in these two articles, using gRPC with the ABP Framework is straightforward. + +## The Source Code + +- You can find the completed source code here: https://github.com/abpframework/abp-samples/tree/master/GrpcDemo2 +- You can also see all the changes I've done in this article here: https://github.com/abpframework/abp-samples/pull/201/files \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-09-18-Grpc-Demo-Part-2/blazor-product-list.png b/docs/en/Community-Articles/2022-09-18-Grpc-Demo-Part-2/blazor-product-list.png new file mode 100644 index 0000000000..279a6ae6f3 Binary files /dev/null and b/docs/en/Community-Articles/2022-09-18-Grpc-Demo-Part-2/blazor-product-list.png differ diff --git a/docs/en/Dapr/Index.md b/docs/en/Dapr/Index.md new file mode 100644 index 0000000000..2596c449d1 --- /dev/null +++ b/docs/en/Dapr/Index.md @@ -0,0 +1,469 @@ +# ABP Dapr Integration + +> This document assumes that you are already familiar with [Dapr](https://dapr.io/) and you want to use it in your ABP based applications. + +[Dapr](https://dapr.io/) (Distributed Application Runtime) provides APIs that simplify microservice connectivity. It is an open source project that is mainly backed by Microsoft. It is also a CNCF (Cloud Native Computing Foundation) project and trusted by the community. + +ABP and Dapr have some intersecting features like service-to-service communication, distributed message bus and distributed locking. However their purposes are totally different. ABP's goal is to provide an end-to-end developer experience by offering an opinionated architecture and providing the necessary infrastructure libraries, reusable modules and tools to implement that architecture properly. Dapr's purpose, on the other hand, is to provide a runtime to decouple common microservice communication patterns from your application logic. + +ABP and Dapr can perfectly work together in the same application. ABP offers some packages to provide better integration where Dapr features intersect with ABP. You can use other Dapr features with no ABP integration packages based on [its own documentation](https://docs.dapr.io/). + +## ABP Dapr Integration Packages + +ABP provides the following NuGet packages for the Dapr integration: + +* [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr): The main Dapr integration package. All other packages depend on this package. +* [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr): Integration package for ABP's [dynamic](../API/Dynamic-CSharp-API-Clients.md) and [static](../API/Static-CSharp-API-Clients.md) C# API Client Proxies systems with Dapr's [service invocation](https://docs.dapr.io/developing-applications/building-blocks/service-invocation/service-invocation-overview/) building block. +* [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr): Implements ABP's distributed event bus with Dapr's [publish & subscribe](https://docs.dapr.io/developing-applications/building-blocks/pubsub/) building block. With this package, you can send events, but can not receive. +* [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus): Provides the endpoints to receive events from Dapr's [publish & subscribe](https://docs.dapr.io/developing-applications/building-blocks/pubsub/) building block. Use this package to send and receive events. +* [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr): Uses Dapr's [distributed lock](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/) building block for [distributed locking](../Distributed-Locking.md) service of the ABP Framework. + +In the following sections, we will see how to use these packages to use Dapr in your ABP based solutions. + +## Basics + +### Installation + +> This section explains how to add [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr), the core Dapr integration package to your project. If you are using one of the other Dapr integration packages, you can skip this section since this package will be indirectly added. + +Use the ABP CLI to add the [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet package to your project: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.Dapr` package. +* Run the `abp add-package Volo.Abp.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. + +### AbpDaprOptions + +`AbpDaprOptions` is the main [options class](../Options.md) that you can configure the global Dapr settings with. **All settings are optional and you mostly don't need to configure them.** If you need, you can configure it in the `ConfigureServices` method of your [module class](../Module-Development-Basics.md): + +````csharp +Configure(options => +{ + // ... +}); +```` + +Available properties of the `AbpDaprOptions` class: + +* `HttpEndpoint` (optional): HTTP endpoint that is used while creating a `DaprClient` object. If you don't specify, the default value is used. +* `GrpcEndpoint` (optional): The gRPC endpoint that is used while creating a `DaprClient` object. If you don't specify, the default value is used. +* `DaprApiToken` (optional): The [Dapr API token](https://docs.dapr.io/operations/security/api-token/) that is used while sending requests from the application to Dapr. It is filled from the `DAPR_API_TOKEN` environment variable by default (which is set by Dapr once it is configured). See the *Security* section in this document for details. +* `AppApiToken` (optional): The [App API token](https://docs.dapr.io/operations/security/app-api-token/) that is used to validate requests coming from Dapr. It is filled from the `APP_API_TOKEN` environment variable by default (which is set by Dapr once it is configured). See the *Security* section in this document for details. + +Alternatively, you can configure the options in the `Dapr` section of your `appsettings.json` file. Example: + +````csharp +"Dapr": { + "HttpEndpoint": "http://localhost:3500/" +} +```` + +### Injecting DaprClient + +ABP registers the `DaprClient` class to the [dependency injection](../Dependency-Injection.md) system. So, you can inject and use it whenever you need: + +````csharp +public class MyService : ITransientDependency +{ + private readonly DaprClient _daprClient; + + public MyService(DaprClient daprClient) + { + _daprClient = daprClient; + } + + public async Task DoItAsync() + { + // TODO: Use the injected _daprClient object + } +} +```` + +Injecting `DaprClient` is the recommended way of using it in your application code. When you inject it, the `IAbpDaprClientFactory` service is used to create it, which is explained in the next section. + +### IAbpDaprClientFactory + +`IAbpDaprClientFactory` can be used to create `DaprClient` or `HttpClient` objects to perform operations on Dapr. It uses `AbpDaprOptions`, so you can configure the settings in a central place. + +**Example usages:** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDaprClientFactory _daprClientFactory; + + public MyService(IAbpDaprClientFactory daprClientFactory) + { + _daprClientFactory = daprClientFactory; + } + + public async Task DoItAsync() + { + // Create a DaprClient object with default options + DaprClient daprClient = await _daprClientFactory.CreateAsync(); + + /* Create a DaprClient object with configuring + * the DaprClientBuilder object */ + DaprClient daprClient2 = await _daprClientFactory + .CreateAsync(builder => + { + builder.UseDaprApiToken("..."); + }); + + // Create an HttpClient object + HttpClient httpClient = await _daprClientFactory + .CreateHttpClientAsync("target-app-id"); + } +} +```` + +`CreateHttpClientAsync` method also gets optional `daprEndpoint` and `daprApiToken` parameters. + +> ABP uses `IAbpDaprClientFactory` when it needs to create a Dapr client. You can also use Dapr API to create client objects in your application. Using `IAbpDaprClientFactory` is recommended, but not required. + +## C# API Client Proxies Integration + +ABP can [dynamically](../API/Dynamic-CSharp-API-Clients.md) or [statically](../API/Static-CSharp-API-Clients.md) generate proxy classes to invoke your HTTP APIs from a Dotnet client application. It makes perfect sense to consume HTTP APIs in a distributed system. The [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) package configures the client-side proxies system, so it uses Dapr's service invocation building block for the communication between your applications. + +### Installation + +Use the ABP CLI to add the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package to your project (to the client side): + +* 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.Http.Client.Dapr` package to. +* Run the `abp add-package Volo.Abp.Http.Client.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpHttpClientDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. + +### Configuration + +Once you install the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package, all you need to do is to configure ABP's remote services option either in `appsettings.json` or using the `AbpRemoteServiceOptions` [options class](../Options.md). + +**Example:** + +````csharp +{ + "RemoteServices": { + "Default": { + "BaseUrl": "http://dapr-httpapi/" + } + } +} +```` + +`dapr-httpapi` in this example is the application id of the server application in your Dapr configuration. + +The remote service name (`Default` in this example) should match the remote service name specified in the `AddHttpClientProxies` call for dynamic client proxies or the `AddStaticHttpClientProxies` call for static client proxies. Using `Default` is fine if your client communicates to a single server. However, if your client uses multiple servers, you typically have multiple keys in the `RemoteServices` configuration. Once you configure the remote service endpoints as Dapr application ids, it will automatically work and make the HTTP calls through Dapr when you use ABP's client proxy system. + +> See the [dynamic](../API/Dynamic-CSharp-API-Clients.md) and [static](../API/Static-CSharp-API-Clients.md) client proxy documents for details about the ABP's client proxy system. + +## Distributed Event Bus Integration + +[ABP's distributed event bus](../Distributed-Event-Bus.md) system provides a convenient abstraction to allow applications to communicate asynchronously via events. ABP has integration packages with various distributed messaging systems, like RabbitMQ, Kafka, and Azure. Dapr also has a [publish & subscribe building block](https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-overview/) for the same purpose: distributed messaging / events. + +ABP's [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) and [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) packages make it possible to use the Dapr infrastructure for ABP's distributed event bus. + +The [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package can be used by any type of application (e.g., a Console or ASP.NET Core application) to publish events through Dapr. To be able to receive messages (by subscribing to events), you need to have the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package installed, and your application should be an ASP.NET Core application. + +### Installation + +If your application is an ASP.NET Core application and you want to send and receive events, you need to install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package as described below: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.AspNetCore.Mvc.Dapr.EventBus` package to. +* Run the `abp add-package Volo.Abp.AspNetCore.Mvc.Dapr.EventBus` command. + +If you want to do it manually, install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) NuGet package to your project and add `[DependsOn(typeof(AbpAspNetCoreMvcDaprEventBusModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. + +> **If you install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package, you don't need to install the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package, because the first one already has a reference to the latter one.** + +If your application is not an ASP.NET Core application, you can't receive events from Dapr, at least with ABP's integration packages (see [Dapr's document](https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/) if you want to receive events in a different type of application). However, you can still publish messages using the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package. In this case, follow the steps below to install that package to your project: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Dapr` package to. +* Run the `abp add-package Volo.Abp.EventBus.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. + +### Configuration + +You can configure the `AbpDaprEventBusOptions` [options class](../Options.md) for Dapr configuration: + +````csharp +Configure(options => +{ + options.PubSubName = "pubsub"; +}); +```` + +Available properties of the `AbpDaprEventBusOptions` class: + +* `PubSubName` (optional): The `pubsubName` parameter while publishing messages through the `DaprClient.PublishEventAsync` method. Default value: `pubsub`. + +### The ABP Subscription Endpoints + +ABP provides the following endpoints to receive events from Dapr: + +* `dapr/subscribe`: Dapr uses this endpoint to get a list of subscriptions from the application. ABP automatically returns all the subscriptions for your distributed event handler classes and custom controller actions with the `Topic` attribute. +* `api/abp/dapr/event`: The unified endpoint to receive all the events from Dapr. ABP dispatches the events to your event handlers based on the topic name. + +> **Since ABP provides the standard `dapr/subscribe` endpoint, you should not manually call the `app.MapSubscribeHandler()` method of Dapr.** You can use the `app.UseCloudEvents()` middleware in your ASP.NET Core pipeline if you want to support the [CloudEvents](https://cloudevents.io/) standard. + +### Usage + +#### The ABP Way + +You can follow [ABP's distributed event bus documentation](../Distributed-Event-Bus.md) to learn how to publish and subscribe to events in the ABP way. No change required in your application code to use Dapr pub-sub. ABP will automatically subscribe to Dapr for your event handler classes (that implement the `IDistributedEventHandler` interface). + +ABP provides `api/abp/dapr/event` + +**Example: Publish an event using the `IDistributedEventBus` service** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IDistributedEventBus _distributedEventBus; + + public MyService(IDistributedEventBus distributedEventBus) + { + _distributedEventBus = distributedEventBus; + } + + public async Task DoItAsync() + { + await _distributedEventBus.PublishAsync(new StockCountChangedEto + { + ProductCode = "AT837234", + NewStockCount = 42 + }); + } +} +```` + +**Example: Subscribe to an event by implementing the `IDistributedEventHandler` interface** + +````csharp +public class MyHandler : + IDistributedEventHandler, + ITransientDependency +{ + public async Task HandleEventAsync(StockCountChangedEto eventData) + { + var productCode = eventData.ProductCode; + // ... + } +} +```` + +See [ABP's distributed event bus documentation](../Distributed-Event-Bus.md) to learn the details. + +#### Using the Dapr API + +In addition to ABP's standard distributed event bus system, you can also use Dapr's API to publish events. + +> If you directly use the Dapr API to publish events, you may not benefit from ABP's standard distributed event bus features, like the outbox/inbox pattern implementation. + +**Example: Publish an event using `DaprClient`** + +````csharp +public class MyService : ITransientDependency +{ + private readonly DaprClient _daprClient; + + public MyService(DaprClient daprClient) + { + _daprClient = daprClient; + } + + public async Task DoItAsync() + { + await _daprClient.PublishEventAsync( + "pubsub", // pubsub name + "StockChanged", // topic name + new StockCountChangedEto // event data + { + ProductCode = "AT837234", + NewStockCount = 42 + } + ); + } +} +```` + +**Example: Subscribe to an event by creating an ASP.NET Core controller** + +````csharp +public class MyController : AbpController +{ + [HttpPost("/stock-changed")] + [Topic("pubsub", "StockChanged")] + public async Task TestRouteAsync( + [FromBody] StockCountChangedEto model) + { + HttpContext.ValidateDaprAppApiToken(); + + // Do something with the event + return Ok(); + } +} +```` + +`HttpContext.ValidateDaprAppApiToken()` extension method is provided by ABP to check if the request is coming from Dapr. This is optional. You should configure Dapr to send the App API token to your application if you want to enable the validation. If not configured, `ValidateDaprAppApiToken()` does nothing. See [Dapr's App API Token document](https://docs.dapr.io/operations/security/app-api-token/) for more information. Also see the *AbpDaprOptions* and *Security* sections in this document. + +See the [Dapr documentation](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/publish-subscribe) to learn the details of sending & receiving events with the Dapr API. + +## Distributed Lock + +> Dapr's distributed lock feature is currently in the Alpha stage and may not be stable yet. It is not suggested to replace ABP's distributed lock with Dapr in that point. + +ABP provides a [Distributed Locking](../Distributed-Locking.md) abstraction to control access to a shared resource by multiple applications. Dapr also has a [distributed lock building block](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/). The [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) package makes ABP use Dapr's distributed locking system. + +### Installation + +Use the ABP CLI to add the [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) NuGet package to your project (to the client side): + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.DistributedLocking.Dapr` package to. +* Run the `abp add-package Volo.Abp.DistributedLocking.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpDistributedLockingDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. + +### Configuration + +You can use the `AbpDistributedLockDaprOptions` options class in the `ConfigureServices` method of [your module](../Module-Development-Basics.md) to configure the Dapr distributed lock: + +````csharp +Configure(options => +{ + options.StoreName = "mystore"; +}); +```` + +The following options are available: + +* **`StoreName`** (required): The store name used by Dapr. Lock key names are scoped in the same store. That means different applications can acquire the same lock name in different stores. Use the same store name for the same resources you want to control the access of. +* `Owner` (optional): The `owner` value used by the `DaprClient.Lock` method. If you don't specify, ABP uses a random value, which is fine in general. +* `DefaultExpirationTimeout` (optional): Default value of the time after which the lock gets expired. Default value: 2 minutes. + +### Usage + +You can inject and use the `IAbpDistributedLock` service, just like explained in the [Distributed Locking document](../Distributed-Locking.md). + +**Example:** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDistributedLock _distributedLock; + + public MyService(IAbpDistributedLock distributedLock) + { + _distributedLock = distributedLock; + } + + public async Task MyMethodAsync() + { + await using (var handle = + await _distributedLock.TryAcquireAsync("MyLockName")) + { + if (handle != null) + { + // your code that access the shared resource + } + } + } +} +```` + +There are two points we should mention about the `TryAcquireAsync` method, as different from ABP's standard usage: + +* The `timeout` parameter is currently not used (even if you specify it), because Dapr doesn't support waiting to obtain the lock. +* Dapr uses the expiration timeout system (that means the lock is automatically released after that timeout even if you don't release the lock by disposing the handler). However, ABP's `TryAcquireAsync` method has no such a parameter. Currently, you can set `AbpDistributedLockDaprOptions.DefaultExpirationTimeout` as a global value in your application. + +As mentioned first, Dapr's distributed lock feature is currently in the Alpha stage and its API is a candidate to change. You should use it as is if you want, but be ready for the changes in the future. For now, we are recommending to use the [DistributedLock](https://github.com/madelson/DistributedLock) library as explained in ABP's [Distributed Locking document](../Distributed-Locking.md). + +## Security + +If you are using Dapr, most or all the incoming and outgoing requests in your application pass through Dapr. Dapr uses two kinds of API tokens to secure the communication between your application and Dapr. + +### Dapr API Token + +> This token is automatically set by default and generally you don't care about it. + +The [Enable API token authentication in Dapr](https://docs.dapr.io/operations/security/api-token/) document describes what the Dapr API token is and how it is configured. Please read that document if you want to enable it for your application. + +If you enable the Dapr API token, you should send that token in every request to Dapr from your application. `AbpDaprOptions` defines a `DaprApiToken` property as a central point to configure the Dapr API token in your application. + +The default value of the `DaprApiToken` property is set from the `DAPR_API_TOKEN` environment variable and that environment variable is set by Dapr when it runs. So, most of the time, you don't need to configure `AbpDaprOptions.DaprApiToken` in your application. However, if you need to configure (or override) it, you can do in the `ConfigureServices` method of your module class as shown in the following code block: + +````csharp +Configure(options => +{ + options.DaprApiToken = "..."; +}); +```` + +Or you can set it in your `appsettings.json` file: + +````json +"Dapr": { + "DaprApiToken": "..." +} +```` + +Once you set it, it is used when you inject `DaprClient` or use `IAbpDaprClientFactory`. If you need that value in your application, you can inject `IDaprApiTokenProvider` and use its `GetDaprApiToken()` method. + +### App API Token + +> Enabling App API token validation is strongly recommended. Otherwise, for example, any client can directly call your event subscription endpoint, and your application acts like an event has occurred (if there is no other security policy in your event subscription endpoint). + +The [Authenticate requests from Dapr using token authentication](https://docs.dapr.io/operations/security/app-api-token/) document describes what the App API token is and how it is configured. Please read that document if you want to enable it for your application. + +If you enable the App API token, you can validate it to ensure that the request is coming from Dapr. ABP provides useful shortcuts to validate it. + +**Example: Validate the App API token in an event handling HTTP API** + +````csharp +public class MyController : AbpController +{ + [HttpPost("/stock-changed")] + [Topic("pubsub", "StockChanged")] + public async Task TestRouteAsync( + [FromBody] StockCountChangedEto model) + { + // Validate the App API token! + HttpContext.ValidateDaprAppApiToken(); + + // Do something with the event + return Ok(); + } +} +```` + +`HttpContext.ValidateDaprAppApiToken()` is an extension method provided by the ABP Framework. It throws an `AbpAuthorizationException` if the token was missing or wrong in the HTTP header (the header name is `dapr-api-token`). You can also inject `IDaprAppApiTokenValidator` and use its methods to validate the token in any service (not only in a controller class). + +You can configure `AbpDaprOptions.AppApiToken` if you want to set (or override) the App API token value. The default value is set by the `APP_API_TOKEN` environment variable. You can change it in the `ConfigureServices` method of your module class as shown in the following code block: + +````csharp +Configure(options => +{ + options.AppApiToken = "..."; +}); +```` + +Or you can set it in your `appsettings.json` file: + +````json +"Dapr": { + "AppApiToken": "..." +} +```` + +If you need that value in your application, you can inject `IDaprApiTokenProvider` and use its `GetAppApiToken()` method. + +## See Also + +* [Dapr for .NET Developers](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/) +* [The Official Dapr Documentation](https://docs.dapr.io/) diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index 668eac6644..a0ad342c4c 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -244,25 +244,35 @@ One restriction of property injection is that you cannot use the dependency in y Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class. -### Resolve Service from IServiceProvider +#### DisablePropertyInjectionAttribute -You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject IServiceProvider into your class and use ``GetService`` method as shown below: +You can use `[DisablePropertyInjection]` attribute on class or properties to disable property injection for the whole class or some specific properties. ````C# +[DisablePropertyInjection] public class MyService : ITransientDependency { - private readonly IServiceProvider _serviceProvider; + public ITaxCalculator TaxCalculator { get; set; } +} - public MyService(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } +public class MyService : ITransientDependency +{ + public ILogger Logger { get; set; } - public void DoSomething() - { - var taxCalculator = _serviceProvider.GetService(); - //... - } + [DisablePropertyInjection] + public ITaxCalculator TaxCalculator { get; set; } +} + +```` + +### Resolve Service from IServiceProvider + +You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject IServiceProvider into your class and use ``GetService`` method as shown below: + +````C# +public class MyService : ITransientDependency +{ + public ILogger Logger { get; set; } } ```` diff --git a/docs/en/Deploy-azure-app-service.md b/docs/en/Deploy-azure-app-service.md new file mode 100644 index 0000000000..9e0309f3b9 --- /dev/null +++ b/docs/en/Deploy-azure-app-service.md @@ -0,0 +1,452 @@ +# Deploying ABP Project to Azure App Service + +In this document, you will learn how to create and deploy your first ABP web app to [Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/overview). The App Service supports various versions of .NET apps, and provides a highly scalable, self-patching web hosting service. ABP web apps are cross-platform and can be hosted on Linux, Windows or MacOS. + +****Prerequisites**** + +- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/dotnet). +- A GitHub account [Create an account for free](http://github.com/). + + + +## Creating a new ABP application + +Create a repository on [GitHub.com](https://github.com/) (keep all settings as default). + +Open the command prompt and clone the repository into a folder on your computer + +```bash +git clone https://github.com/your-username/your-repository-name.git +``` + +Check your dotnet version. It should be at least 3.1.x + +```bash +dotnet --version +``` + +Install or update the [ABP CLI](https://docs.abp.io/en/abp/latest/cli) with the following command: + +```bash +dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli +``` + +Open the command prompt in the *GitHub repository folder* and create a new ABP Blazor solution with the command below: + +```bash +abp new YourAppName -u blazor +``` + + + +## Running the application + +Open the command prompt in the *[YourAppName].DbMigrator* project and enter the command below to apply the database migrations: + +```bash +dotnet run +``` + +Open the command prompt in the *[YourAppName].HttpApi.Host* project to run the API project: + +```bash +dotnet run +``` + +Navigate to the *applicationUrl* specified in *the launchSettings.json* file of the *[YourAppName].HttpApi.Host project*. You should get the *Swagger window* + +Open the command prompt in the *[YourAppName].Blazor* folder and enter the command below to run the Blazor project: + +```bash +dotnet run +``` + +Navigate to the *applicationUrl* specified in the *launchSettings.json* file of the *[YourAppName].Blazor* project and you should see the landing page. + +Stop both the *API* and the *Blazor* project by pressing **CTRL+C** + + + +## Committing to GitHub + +Before the GitHub commit, you have to delete the line "**/wwwroot/libs/*" at *.gitignore* file. + +![azdevops-23](images/azdevops-23.png) + +Open the command prompt in the root folder of your project and *add, commit and push* all your changes to your GitHub repository: + +```bash +git add . +git commit -m initialcommit +git push +``` + + + +## Configuring Azure database connection string + +Create a SQL database on Azure and change the connection string in all the *appsettings.json* files. + +* Login into [Azure Portal](https://portal.azure.com/) + +* Click **Create a resource** + +* Search for *SQL Database* + +* Click the **Create** button in the *SQL Database window* + +* Create a new resource group. Name it *rg[YourAppName]* + +* Enter *[YourAppName]Db* as database name + +* Create a new Server and name it *[yourappname]server* + +* Enter a serveradmin login and passwords. Click the **OK** button + +* Select your *Location* + +* Check *Allow Azure services to access server* + +* Click **Configure database**. Go to the *Basic* version and click the **Apply** button + +* Click the **Review + create** button. Click **Create** + +* Click **Go to resource** and click **SQL server** when the SQL Database is created + +* Click **Networking** under Security left side menu + +* Select **Selected networks** and click **Add your client IP$ address** at the Firewall rules + +* Select **Allow Azure and resources to access this seerver** and save + +* Go to your **SQL database**, click **Connection strings** and copy the connection string + +* Copy/paste the *appsettings.json* files of the *[YourAppName].HttpApi.Host* and the *[YourAppName].DbMigrator* project + +* Do not forget to replace {your_password} with the correct server password you entered in Azure SQL Database + + + +## Running DB Migrations + +Open the command prompt in the *[YourAppName].DbMigrator* project again and enter the command below to apply the database migrations: + +```bash +dotnet run +``` + +Open the command prompt in the *[YourAppName].HttpApi.Host* project and enter the command below to check your API is working: + +```bash +dotnet run +``` + +Stop the *[YourAppName].HttpApi.Host* by pressing CTRL+C. + + + +## Committing to GitHub + +Open the command prompt in the root folder of your project and add, commit and push all your changes to your GitHub repository + +```bash +git add . +git commit -m initialcommit +git push +``` + + + +## Setting up the Build pipeline in AzureDevops and publish the Build Artifacts + +* Sign in Azure DevOps + +* Click **New organization** and follow the steps to create a new organisation. Name it [YourAppName]org + +* Enter [YourAppName]Proj as project name in the ***Create a project to get started*** window + +* Select **Public visibility** and click the **Create project** button + +* Click the **Pipelines** button to continue + +* Click the **Create Pipeline** button + + Select GitHub in the Select your repository window + +![azdevops-1](images/azdevops-1.png) + +* Enter the Connection name. *[YourAppName]GitHubConnection* and click **Authorize using OAuth** + +* Select your **GitHub** [YourAppName]repo and click Continue + +* Search for **ASP.NET** in the ***Select a template*** window + +![azdevops-2](images/azdevops-2.png) + +* Select the ASP.NET Core template and click the **Apply** button + +* Add the below commands block as a first step in the pipeline + + ``` + - task: UseDotNet@2 + inputs: + packageType: 'sdk' + version: '6.0.106' + ``` + +![azdevops-18](images/azdevops-18.png) + +* Select **Settings** on the second task(Nugetcommand@2) in the pipeline + +* Select **Feeds in my Nuget.config** and type **Nuget.config** in the text box + +![azdevops-3](images/azdevops-3.png) + +* Add the below commands block to the end of the pipeline + + ``` + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + ArtifactName: '$(Parameters.ArtifactName)' + condition: succeededOrFailed() + ``` + + ![azdevops-4](images/azdevops-4.png) + +``` +# ASP.NET +# Build and test ASP.NET projects. +# Add steps that publish symbols, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4 + +trigger: +- main + +pool: + vmImage: 'windows-latest' + +variables: + solution: '**/*.sln' + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + +steps: +- task: UseDotNet@2 + inputs: + packageType: 'sdk' + version: '6.0.106' + +- task: NuGetToolInstaller@1 + +- task: NuGetCommand@2 + inputs: + command: 'restore' + restoreSolution: '$(solution)' + feedsToUse: 'config' + nugetConfigPath: 'NuGet.config' + +- task: VSBuild@1 + inputs: + solution: '$(solution)' + msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: VSTest@2 + inputs: + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + ArtifactName: '$(Parameters.ArtifactName)' + publishLocation: 'Container' + condition: succeededOrFailed() +``` + +* Click **Save & queue** in the top menu. Click **Save & queue** again and click **Save and run** to run the Build pipeline + +* When the Build pipeline has finished. Click **1 published; 1 consumed** + + + +## Creating a Web App in the Azure Portal to deploy [YourAppName].HttpApi.Host project + +* Search for Web App in the *Search the Marketplace* field + +* Click the **Create** button in the Web App window + +* Select rg[YourAppName] in the *Resource Group* dropdown + +* Enter [YourAppName]API in the *Name input* field + +* Select code, .NET Core 3.1 (LTS) and windows as *Operating System* + +* Enter [YourAppName]API in the *Name input* field + +* Select .NET Core 3.1 (LTS) in the *Runtime stack* dropdown + +* Select Windows as *Operating System* + +* Select the same *Region* as in the SQL server you created in Part 3 + +![azdevops-5](images/azdevops-5.png) + +* Click **Create new** in the Windows Plan. Name it [YourAppName]ApiWinPlan + +* Click **Change size** in Sku and size. Go to the Dev/Test Free F1 version and click the **Apply** button + +![azdevops-6](images/azdevops-6.png) + +* Click the **Review + create** button. Click the **Create** button + +* Click **Go to resource** when the Web App has been created + + + +## Creating a release pipeline in the AzureDevops and deploy [YourAppName].HttpApi.Host project + +* Sign in into [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) + +* Click [YourAppName]Proj and click **Releases** in the *Pipelines* menu + +* Click the **New pipeline** button in the *No release pipelines found* window + +* Select *Azure App Service deployment* and click the **Apply** button + +![azdevops-7](images/azdevops-7.png) + +* Enter *[YourAppName]staging* in the *Stage name* field in the *Stage* window. And close the window + +* Click **+ Add an artifact** in the *Pipeline* tab + +* Select the **Build** icon as *Source type* in the *Add an artifact* window + +* Select Build pipeline in the *Source (build pipeline)* dropdown and click the **Add** button + +![azdevops-8](images/azdevops-8.png) + +* Click the **Continuous deployment trigger (thunderbolt icon)** + +* Set the toggle to **Enabled** in the the *Continuous deployment trigger* window + +* Click **+ Add** in *No filters added*. Select **Include** in the *Type* dropdown. Select your branch in the *Build branch* dropdown and close the window + +![azdevops-9](images/azdevops-9.png) + +* Click **the little red circle with the exclamation mark** in the *Tasks* tab menu + +* Select your subscription in the *Azure subscription* dropdown. + +![azdevops-10](images/azdevops-10.png) + +* Click **Authorize** and enter your credentials in the next screens + +* After Authorization, select the **[YourAppName]API** in the *App service name* dropdown + +* Click the **Deploy Azure App Service** task + +* Select **[YourAppName].HttpApi.Host.zip** in the *Package or folder* input field + +![azdevops-11](images/azdevops-11.png) + +* Click the **Save** icon in the top menu and click **OK** + +* Click **Create release** in the top menu. Click **Create** to create a release + +* Click the *Pipeline* tab and wait until the Deployment succeeds + +![azdevops-12](images/azdevops-12.png) + +* Open a browser and navigate to the URL of your Web App + +``` +https://[YourAppName]api.azurewebsites.net +``` + +![azdevops-13](images/azdevops-13.png) + + + +## Creating a Web App in Azure Portal to deploy [YourAppName].Blazor project + +* Login into [Azure Portal](https://portal.azure.com/) + +* Click **Create a resource** + +* Search for *Web App* in the *Search the Marketplace* field + +* Click the **Create** button in the *Web App* window + +* Select *rg[YourAppName]* in the *Resource Group* dropdown + +* Enter *[YourAppName]Blazor* in the *Name* input field + +* Select *.NET Core 3.1 (LTS)* in the *Runtime stack* dropdown + +* Select *Windows* as *Operating System* + +* Select the same region as the SQL server you created in Part 3 + +* Select the [YourAppName]ApiWinPlan in the *Windows Plan* dropdown + +![azdevops-14](images/azdevops-14.png) + +* Click the **Review + create** button. Click **Create** button + +* Click **Go to resource** when the Web App has been created + +* Copy the URL of the Blazor Web App for later use + +``` +https://[YourAppName]blazor.azurewebsites.net +``` + + +## Changing the Web App configuration for the Azure App Service + +Copy the URL of the Api Host and Blazor Web App. Change appsettings.json files in the Web App as follows images. + +![azdevops-19](images/azdevops-19.png) + +![azdevops-20](images/azdevops-20.png) + +![azdevops-21](images/azdevops-21.png) + + + +## Adding an extra Stage in the Release pipeline in the AzureDevops to deploy [YourAppName].Blazor project + +* Go to the *Release* pipeline in [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) and click **Edit** + +* Click the **+ Add** link and add a **New Stage** + +![azdevops-15](images/azdevops-15.png) + +* Select *Azure App Service deployment* and click the **Apply** button + +* Enter *BlazorDeployment* in the *Stage name* input field and close the *Stage* window + +* Click the **little red circle with the exclamation mark** in the BlazorDeployment stage + +* Select your subscription in the *Azure subscription* dropdown + +* Select your Blazor Web App in the *App service name* dropdown + +* Click the **Deploy Azure App Service task** + +* Select *[YourAppName].Blazor.zip* in the *Package or folder* input field + +![azdevops-16](images/azdevops-16.png) + +* Click **Save** in the top menu and click the **OK** button after + +* Click **Create release** in the top menu and click the **Create** button + +![azdevops-17](images/azdevops-17.png) + +![azdevops-22](images/azdevops-22.png) diff --git a/docs/en/Deployment/Index.md b/docs/en/Deployment/Index.md index dc1cca300e..6503f8b664 100644 --- a/docs/en/Deployment/Index.md +++ b/docs/en/Deployment/Index.md @@ -6,4 +6,4 @@ However, there are some topics that you should care about when you are deploying ## Guides -* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently. \ No newline at end of file +* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently. diff --git a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md b/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md index 1961726b8f..4823cbaae9 100644 --- a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md +++ b/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md @@ -86,7 +86,7 @@ Defining multiple connections is allowed. In this case, you can specify the conn This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus. -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. +You can use any of the [ConnectionFactory](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) properties as the connection properties. **Example: Specify the connection port** @@ -152,4 +152,4 @@ Configure(options => }); ```` -Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. \ No newline at end of file +Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. diff --git a/docs/en/Distributed-Locking.md b/docs/en/Distributed-Locking.md index d29b60ad04..a1e653a6a6 100644 --- a/docs/en/Distributed-Locking.md +++ b/docs/en/Distributed-Locking.md @@ -101,6 +101,25 @@ namespace AbpDemo * `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application. * `cancellationToken`: A cancellation token that can be triggered later to cancel the operation. +### Configuration + +#### AbpDistributedLockOptions + +`AbpDistributedLockOptions` is the main options class to configure the distributed locking. + +**Example: Set the distributed lock key prefix for the application** + +Configure(options => +{ + options.KeyPrefix = "MyApp1"; +}); + +> Write that code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md). + +##### Available Options + +* KeyPrefix (string, default: null): Specify the lock name prefix. + ### Using DistributedLock Library's API ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details. diff --git a/docs/en/Entities.md b/docs/en/Entities.md index c57af27457..ef8f52a04f 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -112,6 +112,21 @@ For the example above, the composite key is composed of `UserId` and `RoleId`. F > Also note that Entities with Composite Primary Keys cannot utilize the `IRepository` interface since it requires a single Id property. However, you can always use `IRepository`. See [repositories documentation](Repositories.md) for more. +### EntityEquals + +`Entity.EntityEquals(...)` method is used to check if two Entity Objects are equals. + +Example: + +```csharp +Book book1 = ... +Book book2 = ... + +if (book1.EntityEquals(book2)) //Check equality +{ + ... +} +``` ## AggregateRoot Class diff --git a/docs/en/Getting-Started-Running-Solution.md b/docs/en/Getting-Started-Running-Solution.md index 66230c7aef..44fbd3eb03 100644 --- a/docs/en/Getting-Started-Running-Solution.md +++ b/docs/en/Getting-Started-Running-Solution.md @@ -163,6 +163,8 @@ You can see the application APIs and test them here. Get [more info](https://swa ### Running the Blazor Application (Client Side) +Go to the Blazor project folder, open a command line terminal, type the `abp bundle -f` command (If the project was created by ABP Cli tool, you don't need to do this). + Ensure that the `.Blazor` project is the startup project and run the application. > Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster. diff --git a/docs/en/Migration-Guides/Abp-6_0.md b/docs/en/Migration-Guides/Abp-6_0.md index 1436990e56..f589bca2cd 100644 --- a/docs/en/Migration-Guides/Abp-6_0.md +++ b/docs/en/Migration-Guides/Abp-6_0.md @@ -2,13 +2,13 @@ This document is a guide for upgrading ABP v5.3 solutions to ABP v6.0. There is a change in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. -## Added IsActive property +## The IsActive property is Added -`IsActive` property is added to `IUserData`. This property is set to **true** by default. **Cmskit** and **Blog** modules are affected by this change. You need to add new migration to your existing application if you are using any of these modules. Please see [#11417](https://github.com/abpframework/abp/pull/11417) for more info. +`IsActive` property is added to `IUserData`. This property is set to **true** by default. **Cmskit** and **Blog** modules are affected by this change. You need to add a new migration to your existing application if you are using any of these modules. Please see [#11417](https://github.com/abpframework/abp/pull/11417) for more info. ## Default behavior change in MultiTenancyMiddlewareErrorPageBuilder -If you have customized the `MultiTenancyMiddlewareErrorPageBuilder` of `AbpMultiTenancyOptions`, the pipeline now returns **true** to stop the pipeline as the default behavior. See [AbpMultiTenancyOptions: Handle inactive and non-existent tenants](https://github.com/abpframework/abp/blob/dev/docs/en/Multi-Tenancy.md#abpmultitenancyoptions-handle-inactive-and-non-existent-tenants) for more info. +If you have customized the `MultiTenancyMiddlewareErrorPageBuilder` of the `AbpMultiTenancyOptions`, the pipeline now returns **true** to stop the pipeline as the default behavior. See [AbpMultiTenancyOptions: Handle inactive and non-existent tenants](https://github.com/abpframework/abp/blob/dev/docs/en/Multi-Tenancy.md#abpmultitenancyoptions-handle-inactive-and-non-existent-tenants) for more info. ## Migrating to LeptonX Lite @@ -22,7 +22,7 @@ LeptonX Lite is now being introduced and you can follow the guides below to migr After the [announcement of plan to replace the IdentityServer](https://github.com/abpframework/abp/issues/11989), we have successfully implemented [Openiddict](https://github.com/openiddict/openiddict-core) as a replacement for IdentityServer4 as an OpenID-Provider. -You can follow the [IdentityServer to OpenIddict Step by Step Guide](OpenIddict-Step-by-Step.md) for migrating your existing application in detail with a sample projects. +You can follow the [IdentityServer to OpenIddict Step by Step Guide](OpenIddict-Step-by-Step.md) for migrating your existing application in detail with a sample project. ## See Also diff --git a/docs/en/Migration-Guides/OpenIddict-Angular.md b/docs/en/Migration-Guides/OpenIddict-Angular.md index e8d34cb5fc..c3ca6a7fbb 100644 --- a/docs/en/Migration-Guides/OpenIddict-Angular.md +++ b/docs/en/Migration-Guides/OpenIddict-Angular.md @@ -16,14 +16,14 @@ - In **MyApplication.HttpApi.Host.csproj** replace **project references**: ```csharp - - + + ``` with ```csharp - + ``` - In the **MyApplicationHttpApiHostModule.cs** replace usings and **module dependencies**: @@ -117,13 +117,13 @@ This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refacto - In **MyApplication.IdentityServer.csproj** replace **project references**: ```csharp - + ``` with ```csharp - + ``` - In the **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: diff --git a/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md b/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md index 4db4556479..65011f6124 100644 --- a/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md +++ b/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md @@ -5,14 +5,14 @@ - In the **MyApplication.Blazor.csproj** replace **project references**: ```csharp - - + + ``` with ```csharp - + ``` - In the **MyApplicationBlazorModule.cs** replace usings and **module dependencies**: @@ -108,13 +108,13 @@ This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refacto - In **MyApplication.IdentityServer.csproj** replace **project references**: ```csharp - + ``` with ```csharp - + ``` - In **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: diff --git a/docs/en/Migration-Guides/OpenIddict-Blazor.md b/docs/en/Migration-Guides/OpenIddict-Blazor.md index 0dc1c0f123..4a3e166cb9 100644 --- a/docs/en/Migration-Guides/OpenIddict-Blazor.md +++ b/docs/en/Migration-Guides/OpenIddict-Blazor.md @@ -34,14 +34,14 @@ - In the **MyApplication.HttpApi.Host.csproj** replace **project references**: ```csharp - - + + ``` with ```csharp - + ``` - In the **MyApplicationHttpApiHostModule.cs** replace usings and **module dependencies**: @@ -136,13 +136,13 @@ This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refacto - In **MyApplication.IdentityServer.csproj** replace **project references**: ```csharp - + ``` with ```csharp - + ``` - In the **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: diff --git a/docs/en/Migration-Guides/OpenIddict-Mvc.md b/docs/en/Migration-Guides/OpenIddict-Mvc.md index e772de272c..ee7cb6b08a 100644 --- a/docs/en/Migration-Guides/OpenIddict-Mvc.md +++ b/docs/en/Migration-Guides/OpenIddict-Mvc.md @@ -5,14 +5,14 @@ - In **MyApplication.Web.csproj** replace **project references**: ```csharp - - + + ``` with ```csharp - + ``` - In **MyApplicationWebModule.cs** replace usings and **module dependencies**: @@ -50,6 +50,23 @@ context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); } ``` + + - In the **MyApplicationWebModule.cs** add `PreConfigureServices` like below with your application name as the audience: + + ```csharp + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(builder => + { + builder.AddValidation(options => + { + options.AddAudiences("MyApplication"); // Replace with your application name + options.UseLocalServer(); + options.UseAspNetCore(); + }); + }); + } + ``` - In **MyApplicationWebModule.cs** `OnApplicationInitialization` method **replace IdentityServer and JwtToken midwares**: @@ -99,13 +116,13 @@ This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refacto - In **MyApplication.IdentityServer.csproj** replace **project references**: ```csharp - + ``` with ```csharp - + ``` - In **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: diff --git a/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md b/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md index e8c0f65c7c..ba6b264758 100644 --- a/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md +++ b/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md @@ -7,6 +7,11 @@ We are not removing Identity Server packages and we will continue to release new On the other hand, Identity Server ends support for the open-source Identity Server at the end of 2022. The Identity Server team has decided to move to Duende IDS and ABP will not be migrated to the commercial Duende IDS. You can see the Duende Identity Server announcement from [this link](https://blog.duendesoftware.com/posts/20220111_fair_trade). +## Commercial Template + +If you are using a commercial template, please check [Migrating from IdentityServer to OpenIddict for the Commercial Templates](https://docs.abp.io/en/commercial/6.0/migration-guides/openIddict-step-by-step) guide. +If you are using the microservice template, please check [Migrating the Microservice Template from IdentityServer to OpenIddict](https://docs.abp.io/en/commercial/6.0/migration-guides/openIddict-microservice) guide. + ## OpenIddict Migration Steps Use the `abp update` command to update your existing application. See [Upgrading docs](../Upgrading.md) for more info. Apply required migrations by following the [Migration Guides](Index.md) based on your application version. @@ -15,11 +20,11 @@ Use the `abp update` command to update your existing application. See [Upgrading - In **MyApplication.Domain.Shared.csproj** replace **project reference**: ```csharp - + ``` with ```csharp - + ``` - In **MyApplicationDomainSharedModule.cs** replace usings and **module dependencies:** @@ -40,15 +45,15 @@ Use the `abp update` command to update your existing application. See [Upgrading - In **MyApplication.Domain.csproj** replace **project references**: ```csharp - - + + ``` with ```csharp - - + + ``` - In **MyApplicationDomainModule.cs** replace usings and **module dependencies**: @@ -73,9 +78,11 @@ Use the `abp update` command to update your existing application. See [Upgrading #### OpenIddictDataSeedContributor -- Create a folder named *OpenIddict* under the Domain project and copy the [OpenIddictDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/OpenIddict/OpenIddictDataSeedContributor.cs) under this folder. Rename all the `Ids2OpenId` with your project name. +- Create a folder named *OpenIddict* under the Domain project and copy the [OpenIddictDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/OpenIddict/OpenIddictDataSeedContributor.cs) under this folder. **Rename** all the `Ids2OpenId` with your project name. - Delete *IdentityServer* folder that contains `IdentityServerDataSeedContributor.cs` which is no longer needed. +You can also create a project with the same name and copy the `OpenIddict` folder of the new project into your project. + ### EntityFrameworkCore Layer If you are using MongoDB, skip this step and check the *MongoDB* layer section. @@ -83,13 +90,13 @@ If you are using MongoDB, skip this step and check the *MongoDB* layer section. - In **MyApplication.EntityFrameworkCore.csproj** replace **project reference**: ```csharp - + ``` with ```csharp - + ``` - In **MyApplicationEntityFrameworkCoreModule.cs** replace usings and **module dependencies**: @@ -147,13 +154,13 @@ If you are using EntityFrameworkCore, skip this step and check the *EntityFramew - In **MyApplication.MongoDB.csproj** replace **project reference**: ```csharp - + ``` with ```csharp - + ``` - In **MyApplicationMongoDbModule.cs** replace usings and **module dependencies**: @@ -211,6 +218,30 @@ for creating the host builder. Replace **MyApplication** with your application name. +### Test Project + +- In **MyApplicationTestBaseModule.cs** **remove** the IdentityServer related using and PreConfigurations: + + ```csharp + using Volo.Abp.IdentityServer; + ``` + + and + + ```csharp + PreConfigure(options => + { + options.AddDeveloperSigningCredential = false; + }); + + PreConfigure(identityServerBuilder => + { + identityServerBuilder.AddDeveloperSigningCredential(false, System.Guid.NewGuid().ToString()); + }); + ``` + + from `PreConfigureServices`. + ### UI Layer - [Angular UI Migration](OpenIddict-Angular.md) @@ -226,4 +257,4 @@ for creating the host builder. ## See Also -* [ABP Version 6.0 Migration Guide](Abp-6_0.md) \ No newline at end of file +* [ABP Version 6.0 Migration Guide](Abp-6_0.md) diff --git a/docs/en/Modules/OpenIddict.md b/docs/en/Modules/OpenIddict.md index 9e603f76a8..22cb22bf55 100644 --- a/docs/en/Modules/OpenIddict.md +++ b/docs/en/Modules/OpenIddict.md @@ -1,94 +1,268 @@ -## ABP OpenIddict Modules +## ABP OpenIddict Module + +OpenIddict module provides an integration with the [OpenIddict](https://github.com/openiddict/openiddict-core) which provides advanced authentication features like single sign-on, single log-out, and API access control. This module persists applications, scopes, and other OpenIddict-related objects to the database. + +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as a package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/openiddict). The source code is licensed by [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. ## User Interface -This module implements the domain logic and database integrations, but not provides any UI. Management UI is useful if you need to add applications and scopes on the fly. In this case, you may build the management UI yourself or consider to purchase the [ABP Commercial](https://commercial.abp.io/) which provides the management UI for this module. +This module implements the domain logic and database integrations but does not provide any UI. Management UI is useful if you need to add applications and scopes on the fly. In this case, you may build the management UI yourself or consider purchasing the [ABP Commercial](https://commercial.abp.io/) which provides the management UI for this module. ## Relations to Other Modules -This module is based on the [Identity Module](Identity.md) and have an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) with the [Account Module](Account.md). +This module is based on the [Identity Module](Identity.md) and has an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) with the [Account Module](Account.md). -## The module +## Options -### Demo projects +### OpenIddictBuilder -In the module's `app` directory there are six projects(including `angular`) - -* `OpenIddict.Demo.Server`: An abp application with integrated modules (has two `clients` and a `scope`). -* `OpenIddict.Demo.API`: ASP NET Core API application using JwtBearer authentication -* `OpenIddict.Demo.Client.Mvc`: ASP NET Core MVC application using `OpenIdConnect` for authentication -* `OpenIddict.Demo.Client.Console`: Use `IdentityModel` to test OpenIddict's various endpoints, and call the api of `OpenIddict.Demo.API` -* `OpenIddict.Demo.Client.BlazorWASM:` ASP NET Core Blazor application using `OidcAuthentication` for authentication -* `angular`: An angular application that integrates the abp ng modules and uses oauth for authentication +`OpenIddictBuilder` can be configured in the `PreConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). -#### How to run? +Example: -Confirm the connection string of `appsettings.json` in the `OpenIddict.Demo.Server` project. Running the project will automatically create the database and initialize the data. -After running the `OpenIddict.Demo.API` project, then you can run the rest of the projects to test. +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(builder => + { + //Set options here... + }); +} +``` -### Domain module +`OpenIddictBuilder` contains various extension methods to configure the OpenIddict services: -There are four main entities included in this module. +- `AddServer()` registers the OpenIddict token server services in the DI container. Contains `OpenIddictServerBuilder` configurations. +- `AddCore()` registers the OpenIddict core services in the DI container. Contains `OpenIddictCoreBuilder` configurations. +- `AddValidation()` registers the OpenIddict token validation services in the DI container. Contains `OpenIddictValidationBuilder` configurations. -* OpenIddictApplication: **Represents applications(client)** -* OpenIddictScope: **Represents scopes** -* OpenIddictAuthorization: **Represents authorizations, Track of logical chains of tokens and user consent..** -* OpenIddictToken: **Represents various tokens.** +### OpenIddictCoreBuilder -Domain also implements four store interfaces in OpenIddict, OpenIddict uses store to manage entities, corresponding to the above four entities, Custom entity repository is used in the store. +`OpenIddictCoreBuilder` contains extension methods to configure the OpenIddict core services. +Example: -```cs -//Manager -OpenIddictApplicationManager -OpenIddictScopeManager -OpenIddictAuthorizationManager -OpenIddictTokenManager - -//Store -IOpenIddictApplicationStore -IOpenIddictScopeStore -IOpenIddictAuthorizationStore -IOpenIddictTokenStore - -//Repository -IOpenIddictApplicationRepository -IOpenIddictScopeRepository -IOpenIddictAuthorizationRepository -IOpenIddictTokenRepository +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(builder => + { + //Set options here... + }); +} ``` -We enabled most of OpenIddict's features in the `AddOpenIddict` method, You can change OpenIddict's related builder options via `PreConfigure`. +These services contain: -```cs -PreConfigure(builder => -{ - //builder -}); +- Adding `ApplicationStore`, `AuthorizationStore`, `ScopeStore`, `TokenStore`. +- Replacing `ApplicationManager`, `AuthorizationManager`, `ScopeManager`, `TokenManager`. +- Replacing `ApplicationStoreResolver`, `AuthorizationStoreResolver`, `ScopeStoreResolver`, `TokenStoreResolver`. +- Setting `DefaultApplicationEntity`, `DefaultAuthorizationEntity`, `DefaultScopeEntity`, `DefaultTokenEntity`. + +### OpenIddictServerBuilder -PreConfigure(builder => +`OpenIddictServerBuilder` contains extension methods to configure OpenIddict server services. + +Example: + +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) { - //builder -}); + PreConfigure(builder => + { + //Set options here... + }); +} +``` + +These services contain: + +- Registering claims, scopes. +- Setting the `Issuer` URI that is used as the base address for the endpoint URIs returned from the discovery endpoint. +- Adding development signing keys, encryption/signing keys, credentials, and certificates. +- Adding/removing event handlers. +- Enabling/disabling grant types. +- Setting authentication server endpoint URIs. -PreConfigure(builder => +### OpenIddictValidationBuilder + +`OpenIddictValidationBuilder` contains extension methods to configure OpenIddict validation services. + +Example: + +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) { - //builder -}); + PreConfigure(builder => + { + //Set options here... + }); +} ``` -#### AbpOpenIddictAspNetCoreOptions +These services contain: + +- `AddAudiances()` for resource servers. +- `SetIssuer()` URI that is used to determine the actual location of the OAuth 2.0/OpenID Connect configuration document when using provider discovery. +- `SetConfiguration()` to configure `OpenIdConnectConfiguration`. +- `UseIntrospection()` to use introspection instead of local/direct validation. +- Adding encryption key, credentials, and certificates. +- Adding/removing event handlers. +- `SetClientId() ` to set the client identifier `client_id ` when communicating with the remote authorization server (e.g for introspection). +- `SetClientSecret()` to set the identifier `client_secret` when communicating with the remote authorization server (e.g for introspection). +- `EnableAuthorizationEntryValidation()` to enable authorization validation to ensure the `access token` is still valid by making a database call for each API request. *Note:* This may have a negative impact on performance and can only be used with an OpenIddict-based authorization server. +- `EnableTokenEntryValidation()` to enable authorization validation to ensure the `access token` is still valid by making a database call for each API request. *Note:* This may have a negative impact on performance and it is required when the OpenIddict server is configured to use reference tokens. +- `UseLocalServer()` to register the OpenIddict validation/server integration services. +- `UseAspNetCore()` to register the OpenIddict validation services for ASP.NET Core in the DI container. + +## Internals + +### Domain Layer + +#### Aggregates + +##### OpenIddictApplication + +OpenIddictApplications represent the applications that can request tokens from your OpenIddict Server. + +- `OpenIddictApplications` (aggregate root): Represents an OpenIddict application. + - `ClientId` (string): The client identifier associated with the current application. + - `ClientSecret` (string): The client secret associated with the current application. Maybe hashed or encrypted for security reasons. + - `ConsentType` (string): The consent type associated with the current application. + - `DisplayName` (string): The display name associated with the current application. + - `DisplayNames` (string): The localized display names associated with the current application serialized as a JSON object. + - `Permissions` (string): The permissions associated with the current application, serialized as a JSON array. + - `PostLogoutRedirectUris` (string): The logout callback URLs associated with the current application, serialized as a JSON array. + - `Properties` (string): The additional properties associated with the current application serialized as a JSON object or null. + - `RedirectUris` (string): The callback URLs associated with the current application, serialized as a JSON array. + - `Requirements` (string): The requirements associated with the current application + - `Type` (string): The application type associated with the current application. + - `ClientUri` (string): URI to further information about client. + - `LogoUri` (string): URI to client logo. + +##### OpenIddictAuthorization + +OpenIddictAuthorizations are used to keep the allowed scopes, authorization flow types. + +- `OpenIddictAuthorization` (aggregate root): Represents an OpenIddict authorization. + + - `ApplicationId` (Guid?): The application associated with the current authorization. + + - `Properties` (string): The additional properties associated with the current authorization serialized as a JSON object or null. + + - `Scopes` (string): The scopes associated with the current authorization, serialized as a JSON array. + + - `Status` (string): The status of the current authorization. + + - `Subject` (string): The subject associated with the current authorization. + + - `Type` (string): The type of the current authorization. + +##### OpenIddictScope + +OpenIddictScopes are used to keep the scopes of resources. + +- `OpenIddictScope` (aggregate root): Represents an OpenIddict scope. + + - `Description` (string): The public description associated with the current scope. + + - `Descriptions` (string): The localized public descriptions associated with the current scope, serialized as a JSON object. + + - `DisplayName` (string): The display name associated with the current scope. + + - `DisplayNames` (string): The localized display names associated with the current scope serialized as a JSON object. + + - `Name` (string): The unique name associated with the current scope. + - `Properties` (string): The additional properties associated with the current scope serialized as a JSON object or null. + - `Resources` (string): The resources associated with the current scope, serialized as a JSON array. + +##### OpenIddictToken + +OpenIddictTokens are used to persist the application tokens. + +- `OpenIddictToken` (aggregate root): Represents an OpenIddict token. + + - `ApplicationId` (Guid?): The application associated with the current token. + - `AuthorizationId` (Guid?): The application associated with the current token. + - `CreationDate` (DateTime?): The UTC creation date of the current token. + - `ExpirationDate` (DateTime?): The UTC expiration date of the current token. + - `Payload` (string): The payload of the current token, if applicable. Only used for reference tokens and may be encrypted for security reasons. + + - `Properties` (string): The additional properties associated with the current token serialized as a JSON object or null. + - `RedemptionDate` (DateTime?): The UTC redemption date of the current token. + - `Status` (string): The status of the current authorization. + + - `ReferenceId` (string): The reference identifier associated with the current token, if applicable. Only used for reference tokens and may be hashed or encrypted for security reasons. + + - `Status` (string): The status of the current token. + + - `Subject` (string): The subject associated with the current token. -`UpdateAbpClaimTypes(default: true)`: Updates AbpClaimTypes to be compatible with identity server claims. -`AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate. + - `Type` (string): The type of the current token. -You can also change this options via `PreConfigure`. +#### Stores -#### Automatically removing orphaned tokens/authorizations +This module implements OpenIddict stores: -There is a background task in the `Domain` module (`enabled by default`) that automatically removes orphaned tokens/authorizations, you can configure `TokenCleanupOptions` to manage it. +- `IAbpOpenIdApplicationStore` +- `IOpenIddictAuthorizationStore` +- `IOpenIddictScopeStore` +- `IOpenIddictTokenStore` -### ASP NET Core module +##### Repositories + +The following custom repositories are defined in this module: + +- `IOpenIddictApplicationRepository` +- `IOpenIddictAuthorizationRepository` +- `IOpenIddictScopeRepository` +- `IOpenIddictTokenRepository` + +##### Domain Services + +This module doesn't contain any domain service but overrides the service below: + +- `AbpApplicationManager` used to populate/get `AbpApplicationDescriptor` information that contains `ClientUri` and `LogoUri`. + +### Database Providers + +#### Common + +##### Table/Collection Prefix & Schema + +All tables/collections use the `OpenIddict` prefix by default. Set static properties on the `AbpOpenIddictDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). + +##### Connection String + +This module uses `AbpOpenIddict` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. + +See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. + +#### Entity Framework Core + +##### Tables + +- **OpenIddictApplications** +- **OpenIddictAuthorizations** +- **OpenIddictScopes** +- **OpenIddictTokens** + +#### MongoDB + +##### Collections + +- **OpenIddictApplications** +- **OpenIddictAuthorizations** +- **OpenIddictScopes** +- **OpenIddictTokens** + +## ASP.NET Core Module This module integrates ASP NET Core, with built-in MVC controllers for four protocols. It uses OpenIddict's [Pass-through mode](https://documentation.openiddict.com/guides/index.html#pass-through-mode). @@ -99,22 +273,76 @@ LogoutController -> connect/logout UserInfoController -> connect/userinfo ``` -> We will implement the related functions of **device flow** in the PRO module.. +> **Device flow** implementation will be done in the commercial module. + +#### AbpOpenIddictAspNetCoreOptions + +`AbpOpenIddictAspNetCoreOptions` can be configured in the `PreConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). + +Example: + +```csharp +PreConfigure(options => +{ + //Set options here... +}); +``` + +`AbpOpenIddictAspNetCoreOptions` properties: + +- `UpdateAbpClaimTypes(default: true)`: Updates `AbpClaimTypes` to be compatible with the Openiddict claims. +- `AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate. + +#### Automatically Removing Orphaned Tokens/Authorizations + +The background task that automatically removes orphaned tokens/authorizations. This can be configured by `TokenCleanupOptions` to manage it. + +`TokenCleanupOptions` can be configured in the `PreConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). + +Example: + +```csharp +PreConfigure(options => +{ + //Set options here... +}); +``` + +`TokenCleanupOptions` properties: -#### How to control claims in access_token and id_token +- `IsCleanupEnabled` (default: true): Enable/disable token clean up. +- `CleanupPeriod` (default: 3,600,000 ms): Setting clean up period. +- `DisableAuthorizationPruning`: Setting a boolean indicating whether authorizations pruning should be disabled. +- `DisableTokenPruning`: Setting a boolean indicating whether token pruning should be disabled. +- `MinimumAuthorizationLifespan` (default: 14 days): Setting the minimum lifespan authorizations must have to be pruned. Cannot be less than 10 minutes. +- `MinimumTokenLifespan` (default: 14 days): Setting the minimum lifespan tokens must have to be pruned. Cannot be less than 10 minutes. -You can use the [Claims Principal Factory](https://docs.abp.io/en/abp/latest/Authorization#claims-principal-factory) to add/remove claims to the `ClaimsPrincipal`. +#### Updating Claims In Access_token and Id_token -The `AbpDefaultOpenIddictClaimDestinationsProvider` service will add `Name`, `Email` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`. +[Claims Principal Factory](https://docs.abp.io/en/abp/latest/Authorization#claims-principal-factory) can be used to add/remove claims to the `ClaimsPrincipal`. -You can create a service that inherits from `IAbpOpenIddictClaimDestinationsProvider` and add it to DI to fully control the destinations of claims +The `AbpDefaultOpenIddictClaimDestinationsProvider` service will add `Name`, `Email,` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`. + +Create a service that inherits from `IAbpOpenIddictClaimDestinationsProvider` and add it to DI to fully control the destinations of claims. ```cs public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvider, ITransientDependency { public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context) { - // ... + foreach (var claim in context.Claims) + { + if (claim.Type == MyClaims.MyClaimsType) + { + claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken); + } + + if (claim.Type == MyClaims.MyClaimsType2) + { + claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken); + } + } + return Task.CompletedTask; } } @@ -125,30 +353,11 @@ Configure(options => }); ``` -For detailed information, please refer to: [OpenIddict claim destinations](https://documentation.openiddict.com/configuration/claim-destinations.html) - -### EF Core module - -Implements the above four repository interfaces. - -### MongoDB module - -Implements the above four repository interfaces. +For detailed information, please refer to: [OpenIddict claim destinations](https://documentation.openiddict.com/configuration/claim-destinations.html) +#### Disable AccessToken Encryption -## OpenIddict - -### Documentation - -For more details about OpenIddict, please refer to its official documentation and Github. - -https://documentation.openiddict.com - -https://github.com/openiddict/openiddict-core#resources - -### Disable AccessToken Encryption - -ABP disables the `access token encryption` by default for compatibility, you can manually enable it if needed. +ABP disables the `access token encryption` by default for compatibility, it can be enabled manually if needed. ```cs public override void PreConfigureServices(ServiceConfigurationContext context) @@ -162,22 +371,15 @@ public override void PreConfigureServices(ServiceConfigurationContext context) https://documentation.openiddict.com/configuration/token-formats.html#disabling-jwt-access-token-encryption - -### PKCE - -https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html - -### Request/Response process - -I will briefly introduce the principle of OpenIddict so that everyone can quickly understand it. +### Request/Response Process The `OpenIddict.Server.AspNetCore` adds an authentication scheme(`Name: OpenIddict.Server.AspNetCore, handler: OpenIddictServerAspNetCoreHandler`) and implements the `IAuthenticationRequestHandler` interface. It will be executed first in `AuthenticationMiddleware` and can short-circuit the current request. Otherwise, `DefaultAuthenticateScheme` will be called and continue to execute the pipeline. -`OpenIddictServerAspNetCoreHandler` will call various built-in handlers(Handling requests and responses), And the handler will process according to the context or skip logic that has nothing to do with it. +`OpenIddictServerAspNetCoreHandler` will call various built-in handlers (handling requests and responses), And the handler will process according to the context or skip logic that has nothing to do with it. -Example a token request: +Example of a token request: ``` POST /connect/token HTTP/1.1 @@ -191,11 +393,11 @@ Content-Type: application/x-www-form-urlencoded scope=AbpAPI offline_access ``` -This request will be processed by various handlers. They will confirm the endpoint type of the request, check `http/https`, verify that the request parameters (`client. scope etc`) are valid and exist in the database, etc. Various protocol checks. And build a `OpenIddictRequest` object, If there are any errors, the response content may be set and directly short-circuit the current request. +This request will be processed by various handlers. They will confirm the endpoint type of the request, check `HTTP/HTTPS`, verify that the request parameters (`client. scope, etc`) are valid and exist in the database, etc. Various protocol checks. And build a `OpenIddictRequest` object, If there are any errors, the response content may be set and directly short-circuit the current request. -If everything is ok, the request will go to our processing controller(eg `TokenController`), we can get an `OpenIddictRequest` from the http request at this time. The rest of our work will be based on this object. +If everything is ok, the request will go to our processing controller(eg `TokenController`), we can get an `OpenIddictRequest` from the HTTP request at this time. The rest will be based on this object. -We may check the `username` and `password` in the request. If it is correct we create a `ClaimsPrincipal` object and return a `SignInResult`, which uses the `OpenIddict.Validation.AspNetCore` authentication scheme name, will calls `OpenIddictServerAspNetCoreHandler` for processing. +Check the `username` and `password` in the request. If it is correct create a `ClaimsPrincipal` object and return a `SignInResult`, which uses the `OpenIddict.Validation.AspNetCore` authentication scheme name, will calls `OpenIddictServerAspNetCoreHandler` for processing. `OpenIddictServerAspNetCoreHandler` do some checks to generate json and replace the http response content. @@ -206,6 +408,26 @@ If you need to customize OpenIddict, you need to replace/delete/add new handlers Please refer to: https://documentation.openiddict.com/guides/index.html#events-model -## Sponsor +### PKCE + +https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html + +## Demo projects + +In the module's `app` directory there are six projects(including `angular`) + +* `OpenIddict.Demo.Server`: An abp application with integrated modules (has two `clients` and a `scope`). +* `OpenIddict.Demo.API`: ASP NET Core API application using JwtBearer authentication. +* `OpenIddict.Demo.Client.Mvc`: ASP NET Core MVC application using `OpenIdConnect` for authentication. +* `OpenIddict.Demo.Client.Console`: Use `IdentityModel` to test OpenIddict's various endpoints, and call the api of `OpenIddict.Demo.API`. +* `OpenIddict.Demo.Client.BlazorWASM:` ASP NET Core Blazor application using `OidcAuthentication` for authentication. +* `angular`: An angular application that integrates the abp ng modules and uses oauth for authentication. + +#### How to run? + +Confirm the connection string of `appsettings.json` in the `OpenIddict.Demo.Server` project. Running the project will automatically create the database and initialize the data. +After running the `OpenIddict.Demo.API` project, then you can run the rest of the projects to test. + +## Migrating Guide -Please consider sponsoring this project: https://github.com/sponsors/kevinchalet +[Migrating from IdentityServer to OpenIddict Step by Step Guide ](../Migration-Guides/OpenIddict-Step-by-Step.md) diff --git a/docs/en/Modules/Setting-Management.md b/docs/en/Modules/Setting-Management.md index e54993cf79..354fbdd539 100644 --- a/docs/en/Modules/Setting-Management.md +++ b/docs/en/Modules/Setting-Management.md @@ -118,7 +118,13 @@ The order of the providers are important. Providers are executed in the reverse ## Setting Management UI -Setting Mangement module provided the email setting UI by default, and it is extensible; You can add your tabs to this page for your application settings. +Setting Mangement module provided the email setting UI by default. + +![EmailSettingUi](../images/setting-management-email-ui.png) + +> You can click the Send test email button to send a test email to check your email settings. + +Setting it is extensible; You can add your tabs to this page for your application settings. ### MVC UI @@ -302,5 +308,4 @@ export class AppComponent { Navigate to `/setting-management` route to see the changes: -![Custom Settings Tab](../images/custom-settings.png) - +![Custom Settings Tab](../images/custom-settings.png) \ No newline at end of file diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 569bfeb4ca..7dac3762aa 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -222,7 +222,6 @@ The following resolvers are provided and configured by default; * `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**. * `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default. -* `FormTenantResolveContributor`:Tries to find current tenant id from form parameters. The parameter name is `__tenant` by default. * `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route. * `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default. * `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default. diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index ec89f479ab..6a47363e8c 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/docs/en/Startup-Templates/Application.md @@ -78,7 +78,7 @@ Based on the options you've specified, you will get a slightly different solutio If you don't specify any additional options, you will have a solution as shown below: -![bookstore-visual-studio-solution-v3](../images/bookstore-visual-studio-solution-v3.png) +![bookstore-rider-solution-v6](../images/solution-structure-solution-explorer-rider.png) Projects are organized in `src` and `test` folders. `src` folder contains the actual application which is layered based on [DDD](../Domain-Driven-Design.md) principles as mentioned before. @@ -225,7 +225,7 @@ So, the resulting solution allows a 4-tiered deployment, by comparing to 3-tiere The solution structure is shown below: -![bookstore-visual-studio-solution-v3](../images/bookstore-visual-studio-solution-tiered.png) +![bookstore-rider-solution-v6](../images/bookstore-rider-solution-tiered.png) As different from the default structure, two new projects come into play: `.AuthServer` & `.HttpApi.Host`. @@ -233,9 +233,9 @@ As different from the default structure, two new projects come into play: `.Auth This project is used as an authentication server for other projects. `.Web` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the AuthServer. Then uses the access token to call the HTTP API server. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. -![tiered-solution-applications](../images/tiered-solution-applications.png) +![tiered-solution-applications](../images/tiered-solution-applications-authserver.png) -ABP uses the open source [OpenIddcit](https://github.com/openiddict/openiddict-core) framework for the authentication between applications. See [OpenIddcit documentation](https://documentation.openiddict.com/) for details about the OpenIddict and OpenID Connect protocol. +ABP uses the [OpenIddict Module](../Modules/OpenIddict.md) that uses the open-source [OpenIddict-core](https://github.com/openiddict/openiddict-core) library for the authentication between applications. See [OpenIddict documentation](https://documentation.openiddict.com/) for details about the OpenIddict and OpenID Connect protocol. It has its own `appsettings.json` that contains database connection and other configurations. diff --git a/docs/en/Themes/LeptonXLite/Angular.md b/docs/en/Themes/LeptonXLite/Angular.md index 506ffaf753..4079281f99 100644 --- a/docs/en/Themes/LeptonXLite/Angular.md +++ b/docs/en/Themes/LeptonXLite/Angular.md @@ -82,3 +82,170 @@ To change the logos and brand color of `LeptonX`, simply add the following CSS t ### Server Side In order to migrate to LeptonX on your server side projects (Host and/or AuthServer projects), please follow the [Server Side Migration](AspNetCore.md) document. + +## Customization + + +### Layouts + +The Angular version of LeptonX Lite provides **layout components** for your **user interface** on [ABP Framework Theming](https://docs.abp.io/en/abp/latest/UI/Angular/Theming). You can use layouts to **organize your user interface**. You can replace the **layout components** and some parts of the **layout components** with the [ABP replaceable component system](https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement). + + +The main responsibility of a theme is to **provide** the layouts. There are **three pre-defined layouts that must be implemented by all the themes:** + +* **ApplicationLayoutComponent:** The **default** layout which is used by the **main** application pages. + +* **AccountLayoutComponent:** Mostly used by the **account module** for **login**, **register**, **forgot password**... pages. + +* **EmptyLayoutComponent:** The **Minimal** layout that **has no layout components** at all. + + The **Layout components** and all the replacable components are predefined in `eThemeLeptonXComponents` as enum. + +### How to replace a component + +```js +import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService +import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum +import { eThemeLeptonXComponents } from '@abp/ng.theme.lepton-x'; // imported eThemeLeptonXComponents enum + +//... + +@Component(/* component metadata */) +export class AppComponent { + constructor( + private replaceableComponents: ReplaceableComponentsService, // injected the service + ) { + this.replaceableComponents.add({ + component: YourNewApplicationLayoutComponent, + key: eThemeLeptonXComponents.ApplicationLayout, + }); + } +} +``` +See the [Component Replacement](https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) documentation for more information on how to replace components. + + +### Brand Component + +The **brand component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**. You can change the logo via css but if you want to change logo component, the key is `eThemeLeptonXComponents.Logo` + +```js +///... + this.replaceableComponents.add({ + component: YourNewLogoComponent, + key: eThemeLeptonXComponents.Logo, + }); +///... +``` + +![Brand component](../../images/leptonxlite-brand-component.png) + + + +## Breadcrumb Component + +On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**. + +```js +///... + this.replaceableComponents.add({ + component: YourNewSidebarComponent, + key: eThemeLeptonXComponents.Breadcrumb, + }); +///... +``` + +![Breadcrumb component](../../images/leptonxlite-breadcrumb-component.png) + +## Sidebar Menu Component + +Sidebar menus have been used as a **directory for Related Pages** to a **Service** offering, **Navigation** items to a **specific service** or topic and even just as **Links** the user may be interested in. + +```js +///... + this.replaceableComponents.add({ + component: YourNewSidebarComponent, + key: eThemeLeptonXComponents.Sidebar, + }); +///... +``` +![Sidebar menu component](../../images/leptonxlite-sidebar-menu-component.png) + +## Page Alerts Component + +Provides contextual **feedback messages** for typical user actions with a handful of **available** and **flexible** **alert messages**. Alerts are available for any length of text, as well as an **optional dismiss button**. + +![Page alerts component](../../images/leptonxlite-page-alerts-component.png) +```js +///... + this.replaceableComponents.add({ + component: YourNewPageAlertContainerComponent, + key: eThemeLeptonXComponents.PageAlertContainer, + }); +///... +``` +## Toolbar Component +![Breadcrumb component](../../images/leptonxlite-toolbar-component.png) + +Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**. +```js +///... + this.replaceableComponents.add({ + component: YourNewNavItemsComponent, + key: eThemeLeptonXComponents.NavItems, + }); +///... +``` + +## Toolbar Items +There are two parts to the toolbar. The first is Language-Switch. The second is the User-Profile element. You can swap out each of these parts individually. + +## Language Switch Component + +Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** **without trying to locate it across the website.** + +![Language switch component](../../images/leptonxlite-language-switch-component.png) +```js +///... + this.replaceableComponents.add({ + component: YourNewLanguagesComponent, + key: eThemeLeptonXComponents.Languages, + }); +///... +``` + +## User Menu Component + +The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc. + +![User menu component](../../images/leptonxlite-user-menu-component.png) +```js +///... + this.replaceableComponents.add({ + component: YourNewCurrentUserComponent, + key: eThemeLeptonXComponents.CurrentUser, + }); +///... +``` +Note: The language selection component in the Volo app is not replaceable. It is part of the settings menu. + +## Mobile Navbar Component +The **mobile navbar component** is used to display the **navbar menu on mobile devices**. The mobile navbar component is a **dropdown menu** that contains language selection and user menu. + +![Mobile user menu component](../../images/leptonxlite-mobile-user-menu-component.png) +```js +///... + this.replaceableComponents.add({ + component: YourNewMobileNavbarComponent, + key: eThemeLeptonXComponents.MobileNavbar, + }); +///... +``` +## Mobile Navbar Items. +There are two parts of the mobile navbar. The mobile navbar has Language-Switch and User-Profile. You can swap out each of these parts individually. + +The Mobile language-Selection component key is `eThemeLeptonXComponents.MobileLanguageSelection`. + +The Mobile User-Profile component key is `eThemeLeptonXComponents.MobileUserProfile`. + + diff --git a/docs/en/Themes/LeptonXLite/AspNetCore.md b/docs/en/Themes/LeptonXLite/AspNetCore.md index eeaf55f545..f480141120 100644 --- a/docs/en/Themes/LeptonXLite/AspNetCore.md +++ b/docs/en/Themes/LeptonXLite/AspNetCore.md @@ -9,22 +9,22 @@ LeptonX Lite has implementation for the ABP Framework Razor Pages. It's a simpli This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below: -- Add **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application. +- Add the **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application. ```bash dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite --prerelease ``` -- Remove **Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic** reference from the project since it's not necessary after switching to LeptonX Lite. +- Remove the **Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic** reference from the project since it's not necessary after switching to LeptonX Lite. - Make sure the old theme is removed and LeptonX is added in your Module class. ```diff [DependsOn( - // Remove BasicTheme module from DependsOn attribute + // Remove the BasicTheme module from DependsOn attribute - typeof(AbpAspNetCoreMvcUiBasicThemeModule), - // Add LeptonX Lite module to DependsOn attribute + // Add the LeptonX Lite module to DependsOn attribute + typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), )] ``` @@ -35,9 +35,9 @@ dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite --prerelease Configure(options => { options.StyleBundles.Configure( - // Remove following line + // Remove the following line - BasicThemeBundles.Styles.Global, - // Add following line instead + // Add the following line instead + LeptonXLiteThemeBundles.Styles.Global bundle => { @@ -47,10 +47,24 @@ Configure(options => }); ``` ---- - ## Customization +### Layouts + +LeptonX Lite Mvc provides **layouts** for your **user interface** based [ABP Framework Theming](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming). You can use **layouts** to **organize your user interface**. + +The main responsibility of a theme is to **provide** the layouts. There are **three pre-defined layouts that must be implemented by all the themes:** + +* **Application:** The **default** layout which is used by the **main** application pages. + +* **Account:** Mostly used by the **account module** for **login**, **register**, **forgot password**... pages. + +* **Empty:** The **Minimal** layout that **has no layout components** at all. + +**Layout names** are **constants** defined in the `LeptonXLiteTheme` class in the **Mvc** project **root**. + +> The layout pages define under the `Themes/LeptonXLite/Layouts` folder and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + ### Toolbars LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. @@ -74,3 +88,128 @@ public class MyProjectNameMainToolbarContributor : IToolbarContributor } } ``` +# LeptonX Lite Mvc Components + +Abp **helps** you make **highly customizable UI**. You can easily **customize** your themes to fit your needs. **The Virtual File System** makes it possible to **manage files** that **do not physically** exist on the **file system** (disk). It's mainly used to embed **(js, css, image..)** files into assemblies and **use them like** physical files at runtime. An application (or another module) can **override** a **virtual file of a module** just like placing a file with the **same name** and **extension** into the **same folder** of the **virtual file**. + +LeptonX Lite is built on the [Abp Framework](https://abp.io/), so you can **easily** customize your Asp.Net Core Mvc user interface by following [Abp Mvc UI Customization](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-xUser-Interface). + +## Brand Component + +The **brand component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**. + +![Brand component](../../images/leptonxlite-brand-component.png) + +### How to override the Brand Component in LeptonX Lite Mvc + +* The **brand component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Brand/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **brand component (C# file)** is defined in the `Themes/LeptonXLite/Components/Brand/MainNavbarBrandViewComponent.cs` file and you can **override it** by creating a file with the **same name** and under the **same folder**. + +## Breadcrumb Component + +On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**. + +![Breadcrumb component](../../images/leptonxlite-breadcrumb-component.png) + +### How to override the Breadcrumb Component in LeptonX Lite Mvc + +* The **breadcrumb component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **breadcrumb component (C# file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/BreadcrumbsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +## Sidebar Menu Component + +Sidebar menus have been used as **a directory for Related Pages** to a **Service** offering, **Navigation** items to a **specific service** or topic and even just as **Links** the user may be interested in. + +![Sidebar menu component](../../images/leptonxlite-sidebar-menu-component.png) + +### How to override the Sidebar Menu Component in LeptonX Lite Mvc + +* **Sidebar menu page (.cshtml)** is defined in the `Themes/LeptonXLite/Components/Menu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* If you want to **override the menu component (C#)** you can override the `Themes/LeptonXLite/Components/Menu/MainMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +> The **sidebar menu** renders menu items **dynamically**. The **menu item** is a **partial view** and is defined in the `Themes/LeptonXLite/Components/Menu/_MenuItem.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +## Page Alerts Component + +Provides contextual **feedback messages** for typical user actions with the handful of **available** and **flexible** **alert messages**. Alerts are available for any length of text, as well as an **optional dismiss button**. + +![Page alerts component](../../images/leptonxlite-page-alerts-component.png) + +### How to override the Page Alerts Component in LeptonX Lite Mvc + +* The **page alerts component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **page alerts component (C#)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/PageAlertsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +## Toolbar Component + +Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**. + +### How to override the Toolbar Component in LeptonX Lite Mvc + +* The **toolbar component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Toolbar/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **toolbar component (C#)** is defined in the `Themes/LeptonXLite/Components/Toolbar/ToolbarViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +## Toolbar Item Component + +The toolbar item is a **single item** that **contains** a **link**, an **icon**, a **label** etc.. + +### How to override the Toolbar Item Component in LeptonX Lite Mvc + +* The **toolbar item component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **toolbar item component (C#)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/ToolbarItemsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +You can find the toolbar components below: + +## Language Switch Component + +Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** **without trying to locate it across the website.** + +![Language switch component](../../images/leptonxlite-language-switch-component.png) + +### How to override the Language Switch Component in LeptonX Lite Mvc + +* The **language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/LanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +## Mobile Language Switch Component + +The **mobile** **language switch component** is used to switch the language of the website **on mobile devices**. The mobile language switch component is a **dropdown menu** that **contains all the languages** of the website. + +![Mobil language switch component](../../images/leptonxlite-mobile-language-switch-component.png) + +### How to override the Mobile Language Switch Component in LeptonX Lite Mvc + +* The **mobile language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **mobile language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/MobileLanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +## User Menu Component + +The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc. + +![User menu component](../../images/leptonxlite-user-menu-component.png) + +### How to override the User Menu Component in LeptonX Lite Mvc + +* The **user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/UserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **user menu component (C#)** is defined in the `Themes/LeptonXLite/Components/UserMenu/UserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +## Mobile User Menu Component + +The **mobile user menu component** is used to display the **user menu on mobile devices**. The mobile user menu component is a **dropdown menu** that contains all the **options** of the **user menu**. + +![Mobile user menu component](../../images/leptonxlite-mobile-user-menu-component.png) + +### How to override the Mobile User Menu Component in LeptonX Lite Mvc + +* The **mobile user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileUserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. + +* The **mobile user menu component (C#)** is defined in the `Themes/LeptonLite/Components/MobileUserMenu/MobileUserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. diff --git a/docs/en/Themes/LeptonXLite/Blazor.md b/docs/en/Themes/LeptonXLite/Blazor.md index 636c33ef28..0e32aa9825 100644 --- a/docs/en/Themes/LeptonXLite/Blazor.md +++ b/docs/en/Themes/LeptonXLite/Blazor.md @@ -122,6 +122,37 @@ builder.RootComponents.Add("#ApplicationContainer"); ## Customization +### Layout + +* Create a razor page, like `MyMainLayout.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; +@using Volo.Abp.DependencyInjection + +@inherits MainLayout +@attribute [ExposeServices(typeof(MainLayout))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMainLayout.razor.cs`, in your blazor application as shown below: + +```csharp +[ExposeServices(typeof(MainLayout))] +[Dependency(ReplaceServices = true) +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + public partial class MyMainLayout + { + public string Name = "My Main Layout"; + } +} +``` + +> Don't forget to remove the repeated attributes from the razor page! + ### Toolbars LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. @@ -150,3 +181,299 @@ public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) > _You can visit the [Toolbars Documentation](https://docs.abp.io/en/abp/latest/UI/Blazor/Toolbars) for better understanding._ {{end}} + +## Components + +LeptonX Blazor is built on the basis of components. You can use the components in your application as you wish, or you can customize the components by overriding them. If you want to override a component please follow the steps. + +### Branding Component + +The **brand component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**. + + + +#### How to Override Branding Component + +* Create a razor page, like `MyBrandingComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; +@using Volo.Abp.DependencyInjection + +@inherits Branding +@attribute [ExposeServices(typeof(Branding))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyBrandingComponent.razor.cs`, in your blazor application as shown below: + +```csharp +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + public partial class MyBrandingComponent + { + public string Name = "My Branding Component"; + } +} +``` + +### Breadcrumb Component + +On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**. + + + +#### How to Override the BreadCrumb Component + +* Create a razor page, like `MyBreadcrumbsComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; +@using Volo.Abp.DependencyInjection + +@inherits Breadcrumbs +@attribute [ExposeServices(typeof(Breadcrumbs))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyBreadcrumbsComponent.razor.cs`, in your blazor application as shown below: +```csharp +using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; +using Volo.Abp.DependencyInjection; + +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + [ExposeServices(typeof(Breadcrumbs))] + [Dependency(ReplaceServices = true)] + public partial class MyBreadcrumbsComponent + { + public string Name = "My Breadcrumbs Component"; + } +} +``` + +### Main Menu Component + +Sidebar menus have been used as **a directory for Related Pages** for a **Service** offering, **Navigation** items for a **specific service** or topic and even just as **Links** the user may be interested in. + + + +#### How to Override the Main Menu Component + +* Create a razor page, like `MyMainMenuComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Navigation; +@using Volo.Abp.DependencyInjection + +@inherits MainMenu +@attribute [ExposeServices(typeof(MainMenu))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMainMenu.razor.cs`, in your blazor application as shown below: +```csharp +using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Navigation; +using Volo.Abp.DependencyInjection; + +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + [ExposeServices(typeof(MainMenu))] + [Dependency(ReplaceServices = true)] + public partial class MainMenu + { + public string Name = "My Main Menu Component"; + } +} +``` + +> The **main menu** renders the menu items **dynamically**. The **menu item** is a **razor component** named `MainMenuItem.razor.cs` in the same namespace with **main menu** and you can **override it** like the main menu. + +### Toolbar Items Component + +Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**. + +#### How to Override the Toolbar Items Component + +* Create a razor page, like `MyToolbarItemsComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; +@using Volo.Abp.DependencyInjection + +@inherits ToolbarItemsComponent +@attribute [ExposeServices(typeof(ToolbarItemsComponent))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyToolbarItemsComponent.razor.cs`, in your blazor application as shown below: +```csharp +using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; +using Volo.Abp.DependencyInjection; + +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + [ExposeServices(typeof(ToolbarItemsComponent))] + [Dependency(ReplaceServices = true)] + public partial class MyToolbarItemsComponent + { + public string Name = "My Toolbar Items Component"; + } +} +``` + +### Language Switch Component + +Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** **without trying to locate it across the website.** + + + +#### How to Override the Language Switch Component + +* Create a razor page, like `MyLanguageSwitchComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +@using Volo.Abp.DependencyInjection + +@inherits LanguageSwitchComponent +@attribute [ExposeServices(typeof(LanguageSwitchComponent))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyLanguageSwitchComponent.razor.cs`, in your blazor application as shown below: +```csharp +using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +using Volo.Abp.DependencyInjection; + +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + [ExposeServices(typeof(LanguageSwitchComponent))] + [Dependency(ReplaceServices = true)] + public partial class MyLanguageSwitchComponent + { + public string Name = "My Language Switch Component"; + } +} +``` + +### Mobile Language Switch Component + +The **mobile** **language switch component** is used to switch the language of the website **on mobile devices**. The mobile language switch component is a **dropdown menu** that **contains all the languages** of the website. + + + +#### How to Override the Mobile Language Switch Component + +* Create a razor page, like `MyMobilLanguageSwitchComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +@using Volo.Abp.DependencyInjection + +@inherits MobilLanguageSwitchComponent +@attribute [ExposeServices(typeof(MobilLanguageSwitchComponent))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMobilLanguageSwitchComponent.razor.cs`, in your blazor application as shown below: +```csharp +using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +using Volo.Abp.DependencyInjection; + +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + [ExposeServices(typeof(MobilLanguageSwitchComponent))] + [Dependency(ReplaceServices = true)] + public partial class MyMobilLanguageSwitchComponent + { + public string Name = "My Mobile Language Switch Component"; + } +} +``` + +### User Menu Component + +The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc. + + + +#### How to Override the User Menu Component + +* Create a razor page, like `MyUserMenuComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +@using Volo.Abp.DependencyInjection + +@inherits MobilLanguageSwitchComponent +@attribute [ExposeServices(typeof(MobilLanguageSwitchComponent))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyUserMenuComponent.razor.cs`, in your blazor application as shown below: +```csharp +using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +using Volo.Abp.DependencyInjection; + +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + [ExposeServices(typeof(UserMenuComponent))] + [Dependency(ReplaceServices = true)] + public partial class MyUserMenuComponent + { + public string Name = "My User Menu Component"; + } +} +``` + +### Mobile User Menu Component + +The **mobile user menu component** is used to display the **user menu on mobile devices**. The mobile user menu component is a **dropdown menu** that contains all the **options** of the **user menu**. + + + +#### How to override the Mobile User Menu Component + +* Create a razor page, like `MyMobileUserMenuComponent.razor`, in your blazor application as shown below: + +```html +@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +@using Volo.Abp.DependencyInjection + +@inherits MobilUserMenuComponent +@attribute [ExposeServices(typeof(MobilUserMenuComponent))] +@attribute [Dependency(ReplaceServices = true)] + +@Name +``` + +* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMobileUserMenuComponent.razor.cs`, in your blazor application as shown below: +```csharp +using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; +using Volo.Abp.DependencyInjection; + +namespace LeptonXLite.DemoApp.Blazor.MyComponents +{ + [ExposeServices(typeof(MobileUserMenuComponent))] + [Dependency(ReplaceServices = true)] + public partial class MyMobileUserMenuComponent + { + public string Name = "My Mobile User Menu Component"; + } +} +``` diff --git a/docs/en/UI/Angular/Current-User.md b/docs/en/UI/Angular/Current-User.md new file mode 100644 index 0000000000..820a787b5c --- /dev/null +++ b/docs/en/UI/Angular/Current-User.md @@ -0,0 +1,20 @@ +# Angular UI: Current User + +The current user information stored in Config State. + +### How to Get a Current User Information Configuration + +You can use the `getOne` or `getOne$` method of `ConfigStateService` to get a specific configuration property. For that, the property name should be passed to the method as parameter. + +```js +// this.config is an instance of ConfigStateService + +const currentUser = this.config.getOne("currentUser"); + +// or +this.config.getOne$("currentUser").subscribe(currentUser => { + // use currentUser here +}) +``` + +> See the [ConfigStateService](./Config-State-Service) for more information. diff --git a/docs/en/UI/Angular/Dynamic-Form-Extensions.md b/docs/en/UI/Angular/Dynamic-Form-Extensions.md index d7780ceec0..1764b0cad7 100644 --- a/docs/en/UI/Angular/Dynamic-Form-Extensions.md +++ b/docs/en/UI/Angular/Dynamic-Form-Extensions.md @@ -235,10 +235,35 @@ const options: FormPropOptions = { }, autocomplete: 'off', isExtra: true, + template: undefined | Type // Custom angular component }; const prop = new FormProp(options); ``` +FormProp has the template option since version 6.0. it can accept custom angular component. +The component can access PropData and Prop. +Example of the custom prop component. +```js +import { + EXTENSIBLE_FORM_VIEW_PROVIDER, + EXTENSIONS_FORM_PROP, + EXTENSIONS_FORM_PROP_DATA, +} from '@abp/ng.theme.shared/extensions'; + + +@Component({ + selector: 'my-custom-custom-prop', + templateUrl: './my-custom-custom-prop.component.html', + viewProviders: [EXTENSIBLE_FORM_VIEW_PROVIDER], //you should add this, otherwise form-group doesn't work. +}) +export class MyCustomPropComponent { + constructor( + @Inject(EXTENSIONS_FORM_PROP) private formProp: FormProp, + @Inject(EXTENSIONS_FORM_PROP_DATA) private propData: ProfileDto, + ...) + ... +} +``` It also has two static methods to create its instances: diff --git a/docs/en/UI/Angular/Theme-Configurations.md b/docs/en/UI/Angular/Theme-Configurations.md new file mode 100644 index 0000000000..85d22040df --- /dev/null +++ b/docs/en/UI/Angular/Theme-Configurations.md @@ -0,0 +1,3 @@ +# Angular UI: Theme Configurations + +> This document is in draft version. \ No newline at end of file diff --git a/docs/en/UI/Angular/Theming.md b/docs/en/UI/Angular/Theming.md index c7f303763d..435acc6930 100644 --- a/docs/en/UI/Angular/Theming.md +++ b/docs/en/UI/Angular/Theming.md @@ -16,10 +16,11 @@ In order to accomplish these goals, ABP Framework; ### Current Themes -Currently, two themes are **officially provided**: +Currently, three themes are **officially provided**: * The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. * The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license. +* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/angular) and [lite](../../Themes/LeptonXLite/Angular.md) choices. ## Overall diff --git a/docs/en/UI/AspNetCore/Overall.md b/docs/en/UI/AspNetCore/Overall.md index 8cd9dd3f5e..bc24c56fb8 100644 --- a/docs/en/UI/AspNetCore/Overall.md +++ b/docs/en/UI/AspNetCore/Overall.md @@ -32,10 +32,11 @@ ABP Framework provides a complete [Theming](Theming.md) system with the followin ### Current Themes -Currently, two themes are **officially provided**: +Currently, three themes are **officially provided**: * The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. * The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license. +* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/mvc) and [lite](../../Themes/LeptonXLite/AspNetCore.md) choices. There are also some community-driven themes for the ABP Framework (you can search on the web). diff --git a/docs/en/UI/AspNetCore/Page-Header.md b/docs/en/UI/AspNetCore/Page-Header.md index f6ec3074de..8b045c89ab 100644 --- a/docs/en/UI/AspNetCore/Page-Header.md +++ b/docs/en/UI/AspNetCore/Page-Header.md @@ -23,6 +23,8 @@ Page Title can be set as shown in the example below: ### Breadcrumb > **The [Basic Theme](Basic-Theme.md) currently doesn't implement the breadcrumbs.** +> +> The [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) supports breadcrumbs. Breadcrumb items can be added to the `PageLayout.Content.BreadCrumb`. @@ -48,11 +50,13 @@ Any item that you add is inserted between Home and Current Page items. You can a ### The Selected Menu Item > **The [Basic Theme](Basic-Theme.md) currently doesn't implement the selected menu item since it is not applicable to the top menu which is the only option for the Basic Theme for now.** +> +> The [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) supports selected menu item. You can set the Menu Item name related to this page: -````csharp +```csharp PageLayout.Content.MenuItemName = "BookStore.Books"; -```` +``` Menu item name should match a unique menu item name defined using the [Navigation / Menu](Navigation-Menu.md) system. In this case, it is expected from the theme to make the menu item "active" in the main menu. \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/Toolbars.md b/docs/en/UI/AspNetCore/Toolbars.md index d82150b47e..975157151f 100644 --- a/docs/en/UI/AspNetCore/Toolbars.md +++ b/docs/en/UI/AspNetCore/Toolbars.md @@ -8,6 +8,12 @@ There is only one **standard toolbar** named "Main" (defined as a constant: `Sta In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here. +Also, [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) has 2 different toolbars for desktop and mobile views which defined as constants: `LeptonXLiteToolbars.Main`, `LeptonXLiteToolbars.MainMobile`. + +| LeptonXLiteToolbars.Main | LeptonXLiteToolbars.MainMobile | +| :---: | :---: | +| ![leptonx](../../images/leptonxlite-toolbar-main-example.png) | ![leptonx](../../images/leptonxlite-toolbar-mainmobile-example.png) | + ## Example: Add a Notification Icon In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **view component**. So, first, create a new view component in your project: diff --git a/docs/en/UI/Blazor/Layout-Hooks.md b/docs/en/UI/Blazor/Layout-Hooks.md new file mode 100644 index 0000000000..1118e02d37 --- /dev/null +++ b/docs/en/UI/Blazor/Layout-Hooks.md @@ -0,0 +1,125 @@ +# Blazor UI: Layout Hooks + +ABP Framework theming system places the page layout into the [theme](Theming.md) NuGet packages. That means the final application doesn't include a layout, so you can't directly change the layout code to customize it. + +> If you create a Blazor WASM project, the `index.html` file will be included within the template. You can also customize it to your needs. + +You can copy the theme code into your solution. In this case, you are completely free to customize it. However, then you won't be able to get automatic updates of the theme (by upgrading the theme NuGet package). + +The **Layout Hook System** allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Finally, you can add a **razor component** into a hook point. + +## Example: Add a Simple Announcement Alert + +Assume that you need to add a simple banner to the layout (that will be available for all the pages) to make an announcement about your new product. First, **create a razor component** in your project: + +![bookstore-banner-component](../../images/bookstore-banner-component.png) + +**AnnouncementComponent.razor.cs** + +```csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; + +namespace Acme.BookStore.Blazor.Components; + +public partial class AnnouncementComponent : ComponentBase +{ + private const string AnnouncementLocalStorageKey = "product-announcement-status"; + + [Inject] + public IJSRuntime JsRuntime { get; set; } + + public bool ShowBanner { get; set; } + + protected override async Task OnInitializedAsync() + { + ShowBanner = await ShowAnnouncementBannerAsync(); + } + + private async Task ShowAnnouncementBannerAsync() + { + var announcementLocalStorageValue = await JsRuntime.InvokeAsync("localStorage.getItem", AnnouncementLocalStorageKey); + + return announcementLocalStorageValue != null && + bool.TryParse(announcementLocalStorageValue, out var showAnnouncementBanner) && showAnnouncementBanner; + } + + private async Task HideAnnouncementBannerAsync() + { + await JsRuntime.InvokeVoidAsync("localStorage.setItem", AnnouncementLocalStorageKey, true); + ShowBanner = false; + StateHasChanged(); + } +} +``` + +**AnnouncementComponent.razor** + +```html +@if(ShowBanner) +{ +
+ A brand new product is in sale. Click here to learn more. + +
+} +``` + +Then, you can add this component to any of the hook points in the `ConfigureServices` of your module class: + +```csharp +Configure(options => +{ + options.Add( + LayoutHooks.Body.Last, //The hook name + typeof(AnnouncementComponent) //The component to add + ); +}); +``` + +Now, the `AnnouncementComponent` will be rendered in the `body` of the page as the last item. + +### Specifying the Layout + +The configuration above adds the `AnnouncementComponent` to all layouts. You may want to only add it to a specific layout: + +````csharp +Configure(options => +{ + options.Add( + LayoutHooks.Body.Last, + typeof(AnnouncementComponent), + layout: StandardLayouts.Application //Set the layout to add + ); +}); +```` + +See the *Layouts* section below to learn more about the layout system. + +## Layout Hook Points + +There are some pre-defined layout hook points. The standard hook points are: + +* `LayoutHooks.Body.First`: Used to add a component as the first item in the HTML body tag. +* `LayoutHooks.Body.Last`: Used to add a component as the last item in the HTML body tag. + +> You (or the modules you are using) can add **multiple items to the same hook point**. All of them will be added to the layout in the order they were added. + +## Layouts + +The layout system allows themes to define the standard named layouts and allows any page to select a proper layout for its purpose. There is one pre-defined layout: + +* "**Application**": The main (and the default) layout for an application. It typically contains a header, menu (sidebar), footer, toolbar... etc. + +This layout is defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but this layout is the standard layout and it's implemented by all the themes out of the box. + +> If you don't specify the layout, your razor component will be rendered in all of the layouts. + +### Layout Location + +You can find the `MainLayout.razor` [here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/MainLayout.razor) for the basic theme. You can take it as a reference to build your own layouts or you can override it, if necessary. + +## See Also + +* [Customization / Overriding Components](Customization-Overriding-Components.md) diff --git a/docs/en/UI/Blazor/Overall.md b/docs/en/UI/Blazor/Overall.md index ff361d6784..835bf4eab6 100644 --- a/docs/en/UI/Blazor/Overall.md +++ b/docs/en/UI/Blazor/Overall.md @@ -75,10 +75,11 @@ ABP Framework provides a complete [Theming](Theming.md) system with the followin ### Current Themes -Currently, two themes are **officially provided**: +Currently, three themes are **officially provided**: * The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. * The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license. +* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/blazor) and [lite](../../Themes/LeptonXLite/Blazor.md) choices. ### Base Libraries @@ -93,7 +94,7 @@ These libraries are selected as the base libraries and available to the applicat > Bootstrap's JavaScript part is not used since the Blazorise library already provides the necessary functionalities to the Bootstrap components in a native way. -> Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/announcing-2022-blazorise-plans-and-pricing-updates) to learn more. +> Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/announcing-2022-blazorise-plans-and-pricing-updates) to learn more. The Blazorise license is bundled with ABP Commercial and commercial customers doesn’t need to buy an extra Blazorise license. ### The Layout diff --git a/docs/en/UI/Blazor/Page-Header.md b/docs/en/UI/Blazor/Page-Header.md index 29cb006bd3..1bdc9c93c0 100644 --- a/docs/en/UI/Blazor/Page-Header.md +++ b/docs/en/UI/Blazor/Page-Header.md @@ -16,6 +16,8 @@ Once you add the `PageHeader` component to your page, you can control the relate ## Breadcrumb > **The [Basic Theme](Basic-Theme.md) currently doesn't implement the breadcrumbs.** +> +> The [LeptonX Lite Theme](../../Themes/LeptonXLite/Blazor.md) supports breadcrumbs. Breadcrumbs can be added using the `BreadcrumbItems` property. diff --git a/docs/en/UI/Blazor/Page-Layout.md b/docs/en/UI/Blazor/Page-Layout.md index 49ed1659f4..c6f3d62000 100644 --- a/docs/en/UI/Blazor/Page-Layout.md +++ b/docs/en/UI/Blazor/Page-Layout.md @@ -42,6 +42,10 @@ Menu item name can be set on runtime too. } ``` + +![leptonx selected menu item](../../images/leptonx-selected-menu-item-example.gif) + + > Be aware, The [Basic Theme](../Blazor/Basic-Theme.md) currently doesn't support the selected menu item since it is not applicable to the top menu. ## BreadCrumbs diff --git a/docs/en/UI/Blazor/Toolbars.md b/docs/en/UI/Blazor/Toolbars.md index 3ff1b7f135..c0878f080a 100644 --- a/docs/en/UI/Blazor/Toolbars.md +++ b/docs/en/UI/Blazor/Toolbars.md @@ -8,6 +8,12 @@ There is only one **standard toolbar** named "Main" (defined as a constant: `Sta In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here. +Also, [LeptonX Lite Theme](../../Themes/LeptonXLite/Blazor.md) has 2 different toolbars for desktop and mobile views which defined as constants: `LeptonXLiteToolbars.Main`, `LeptonXLiteToolbars.MainMobile`. + +| LeptonXLiteToolbars.Main | LeptonXLiteToolbars.MainMobile | +| :---: | :---: | +| ![leptonx](../../images/leptonxlite-toolbar-main-example.png) | ![leptonx](../../images/leptonxlite-toolbar-mainmobile-example.png) | + ## Example: Add a Notification Icon In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **Razor Component**. So, first, create a new razor component in your project (the location of the component doesn't matter): diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index db63a7136b..ed9cb37135 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -43,6 +43,10 @@ "text": "WPF Application", "path": "Startup-Templates/WPF.md" }, + { + "text": "MAUI", + "path": "Startup-Templates/MAUI.md" + }, { "text": "Empty Web Project", "path": "Getting-Started-AspNetCore-Application.md" @@ -847,6 +851,10 @@ "text": "Page Header", "path": "UI/Blazor/Page-Header.md" }, + { + "text": "Page Layout", + "path": "UI/Blazor/Page-Layout.md" + }, { "text": "Toolbars", "path": "UI/Blazor/Toolbars.md" @@ -931,6 +939,10 @@ { "text": "PWA Configuration", "path": "UI/Blazor/Pwa-Configuration.md" + }, + { + "text": "Layout Hooks", + "path": "UI/Blazor/Layout-Hooks.md" } ] }, @@ -977,6 +989,10 @@ "text": "Authorization", "path": "UI/Angular/Authorization.md" }, + { + "text": "Current User", + "path": "UI/Angular/Current-User.md" + }, { "text": "HTTP Requests", "path": "UI/Angular/HTTP-Requests.md" @@ -1267,6 +1283,10 @@ } ] }, + { + "text": "Dapr Integration", + "path": "Dapr/Index.md" + }, { "text": "Testing", "path": "Testing.md" diff --git a/docs/en/images/bookstore-banner-component.png b/docs/en/images/bookstore-banner-component.png new file mode 100644 index 0000000000..2363b06fae Binary files /dev/null and b/docs/en/images/bookstore-banner-component.png differ diff --git a/docs/en/images/bookstore-rider-solution-tiered.png b/docs/en/images/bookstore-rider-solution-tiered.png new file mode 100644 index 0000000000..25114bc51c Binary files /dev/null and b/docs/en/images/bookstore-rider-solution-tiered.png differ diff --git a/docs/en/images/breadcrumbs-example.png b/docs/en/images/breadcrumbs-example.png index 17ae5f8ba5..8b4bbc62d7 100644 Binary files a/docs/en/images/breadcrumbs-example.png and b/docs/en/images/breadcrumbs-example.png differ diff --git a/docs/en/images/leptonx-selected-menu-item-example.gif b/docs/en/images/leptonx-selected-menu-item-example.gif new file mode 100644 index 0000000000..8f1170cd55 Binary files /dev/null and b/docs/en/images/leptonx-selected-menu-item-example.gif differ diff --git a/docs/en/images/leptonxlite-brand-component.png b/docs/en/images/leptonxlite-brand-component.png new file mode 100644 index 0000000000..c06a51612f Binary files /dev/null and b/docs/en/images/leptonxlite-brand-component.png differ diff --git a/docs/en/images/leptonxlite-breadcrumb-component.png b/docs/en/images/leptonxlite-breadcrumb-component.png new file mode 100644 index 0000000000..9026ca4c52 Binary files /dev/null and b/docs/en/images/leptonxlite-breadcrumb-component.png differ diff --git a/docs/en/images/leptonxlite-language-switch-component.png b/docs/en/images/leptonxlite-language-switch-component.png new file mode 100644 index 0000000000..027910c23b Binary files /dev/null and b/docs/en/images/leptonxlite-language-switch-component.png differ diff --git a/docs/en/images/leptonxlite-mobile-language-switch-component.png b/docs/en/images/leptonxlite-mobile-language-switch-component.png new file mode 100644 index 0000000000..a058820743 Binary files /dev/null and b/docs/en/images/leptonxlite-mobile-language-switch-component.png differ diff --git a/docs/en/images/leptonxlite-mobile-user-menu-component.png b/docs/en/images/leptonxlite-mobile-user-menu-component.png new file mode 100644 index 0000000000..1a51c341d7 Binary files /dev/null and b/docs/en/images/leptonxlite-mobile-user-menu-component.png differ diff --git a/docs/en/images/leptonxlite-page-alerts-component.png b/docs/en/images/leptonxlite-page-alerts-component.png new file mode 100644 index 0000000000..7976412fb2 Binary files /dev/null and b/docs/en/images/leptonxlite-page-alerts-component.png differ diff --git a/docs/en/images/leptonxlite-sidebar-menu-component.png b/docs/en/images/leptonxlite-sidebar-menu-component.png new file mode 100644 index 0000000000..6afe4029e8 Binary files /dev/null and b/docs/en/images/leptonxlite-sidebar-menu-component.png differ diff --git a/docs/en/images/leptonxlite-toolbar-component.png b/docs/en/images/leptonxlite-toolbar-component.png new file mode 100644 index 0000000000..6076c20ff0 Binary files /dev/null and b/docs/en/images/leptonxlite-toolbar-component.png differ diff --git a/docs/en/images/leptonxlite-toolbar-main-example.png b/docs/en/images/leptonxlite-toolbar-main-example.png new file mode 100644 index 0000000000..30edcf547b Binary files /dev/null and b/docs/en/images/leptonxlite-toolbar-main-example.png differ diff --git a/docs/en/images/leptonxlite-toolbar-mainmobile-example.png b/docs/en/images/leptonxlite-toolbar-mainmobile-example.png new file mode 100644 index 0000000000..95815b5843 Binary files /dev/null and b/docs/en/images/leptonxlite-toolbar-mainmobile-example.png differ diff --git a/docs/en/images/leptonxlite-user-menu-component.png b/docs/en/images/leptonxlite-user-menu-component.png new file mode 100644 index 0000000000..a0039e996c Binary files /dev/null and b/docs/en/images/leptonxlite-user-menu-component.png differ diff --git a/docs/en/images/setting-management-email-ui.png b/docs/en/images/setting-management-email-ui.png new file mode 100644 index 0000000000..f8f1270c15 Binary files /dev/null and b/docs/en/images/setting-management-email-ui.png differ diff --git a/docs/en/images/solution-structure-solution-explorer-rider.png b/docs/en/images/solution-structure-solution-explorer-rider.png new file mode 100644 index 0000000000..8a09cec74e Binary files /dev/null and b/docs/en/images/solution-structure-solution-explorer-rider.png differ diff --git a/docs/en/images/tiered-solution-applications-authserver.png b/docs/en/images/tiered-solution-applications-authserver.png new file mode 100644 index 0000000000..6d96bf31b4 Binary files /dev/null and b/docs/en/images/tiered-solution-applications-authserver.png differ diff --git a/docs/pt-BR/Validation.md b/docs/pt-BR/Validation.md index 5818566cdd..662235431a 100644 --- a/docs/pt-BR/Validation.md +++ b/docs/pt-BR/Validation.md @@ -1,3 +1,182 @@ -## Validation +# Validação -Façam \ No newline at end of file +O sistema de validação é utilizado para validar a entrada do usuário ou a requisição do cliente para uma ação de um controller ou por um serviço. + +O ABP é compatível com o sistema de Validação de Modelos do ASP.NET Core e tudo escrito na [sua documentação](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation) é válido para aplicações baseadas no ABP. Logo, esse documento foca nas funcionalidades do ABP ao invés de repetir a documentação da Microsoft. + +Além disso, o ABP adiciona os seguintes benefícios: + +* Define `IValidationEnabled` para adicionar validação automática para uma classe qualquer. Como todos os [serviços de aplicação](Application-Services.md) já o implementam, eles também são validados automaticamente. +* Automaticamente traduz os erros de validação para os atributos de anotação de dados. +* Provê serviços extensíveis para validar a chamada de um método ou o estado de um objeto. +* Provê integração com o [FluentValidation](https://fluentvalidation.net/) + +## Validando DTOs + +Essa seção introduz brevemente o sistema de validação. Para mais detalhes, veja a [Documentação da Validação de Modelo em ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation). + +### Atributos de anotação de dados + +Utilizar anotações de dados é uma maneira simples de implementar uma validação formal para um [DTO](Data-Transfer-Objects.md) de uma forma declarativa. Exemplo: + +````csharp +public class CreateBookDto +{ + [Required] + [StringLength(100)] + public string Name { get; set; } + + [Required] + [StringLength(1000)] + public string Description { get; set; } + + [Range(0, 999.99)] + public decimal Price { get; set; } +} +```` +Quando você utilizar essa classe como parâmetro para um [serviço da aplicação](Application-Services.md) ou um controller, ele será automaticamente validado e a validação traduzida será lançada ([e tratada](Exception-Handling.md) pelo ABP framework). + +### IValidatableObject + +`IValidatableObject` pode ser implementado por um DTO para executar uma lógica customizada de validação. O `CreateBookDto` no exemplo a seguir implementa essa interface e verifica se o `Name` é igual a `Description` e retorna um erro de validação nesse caso. + +````csharp +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Acme.BookStore +{ + public class CreateBookDto : IValidatableObject + { + [Required] + [StringLength(100)] + public string Name { get; set; } + + [Required] + [StringLength(1000)] + public string Description { get; set; } + + [Range(0, 999.99)] + public decimal Price { get; set; } + + public IEnumerable Validate( + ValidationContext validationContext) + { + if (Name == Description) + { + yield return new ValidationResult( + "Name and Description can not be the same!", + new[] { "Name", "Description" } + ); + } + } + } +} +```` + +#### Resolvendo um serviço. + +Se você precisar resolver um serviço do [sistema de injeção de dependências](Dependency-Injection.md), você pode utilizar o objeto `ValidationContext`. + +````csharp +var myService = validationContext.GetRequiredService(); +```` + +> Enquanto resolver os serviços no método `Validate` permite várias possibilidades, não é um boa prática implementar sua lógica de validação do domínio nos DTOs. Mantenha os DTOs simples. Seu propósito é transferir dados (DTO: Data Transfer Object, ou Objeto de Transferência de Dados). + +## Infraestrutura de Validação. + +Essa seção explica alguns serviços adicionais fornecidos pelo ABP Framework. + +### Interface IValidationEnabled + +`IValidationEnabled` é um marcador vazio de interface que pode ser implementado por qualquer classe (registrada e resolvida a partir do [DI](Dependency-Injection.md)) para permitir que o ABP framework realize o sistema de validação para os métodos da classe. Por exemplo: + +````csharp +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Validation; + +namespace Acme.BookStore +{ + public class MyService : ITransientDependency, IValidationEnabled + { + public virtual async Task DoItAsync(MyInput input) + { + //... + } + } +} +```` + +> O ABP framework utiliza o sistema de [Proxying Dinâmico / Interceptadores](Dynamic-Proxying-Interceptors.md) para realizar a validação. Para fazê-lo funcionar, seu método deve ser **virtual** ou seu serviço deve ser injetado e utilizado através de uma **interface** (como `IMyService`). + +#### Habilitando e Desabilitando Validações + +Você pode utilizar o `[DisableValidation]` e desabilitar a validação para métodos, classes e propriedades. + +````csharp +[DisableValidation] +public Void MyMethod() +{ +} + +[DisableValidation] +public class InputClass +{ + public string MyProperty { get; set; } +} + +public class InputClass +{ + [DisableValidation] + public string MyProperty { get; set; } +} +```` + +### AbpValidationException + +Uma vez que o ABP determina um erro de validação, é lançada uma validação do tipo `AbpValidationException`. O código da sua aplicação poderá lançar o `AbpValidationException`, mas na maioria das vezes não será necessário. + +* A propriedade `ValidationErrors` do `AbpValidationException` contem a lista com os erros de validação. +* O nível de log do `AbpValidationException` é definido como `Warning`. Todos os erros de validação são logados no [Sistema de Logging](Logging.md). +* `AbpValidationException` é tratado automaticamente pelo ABP framework e é convertido em um erro utilizável com o código de status HTTP 400. Veja a documentação de [Manipulação de Exceção](Exception-Handling.md) para mais informações. + +## Tópicos Avançados + +### IObjectValidator + +Além da validação automática, você pode querer validar um objeto manualmente. Nesse caso, [injete](Dependency-Injection.md) e use o serviço `IObjectValidator`: + +* O método `ValidateAsync` valida o objeto informado baseado nas regras de validação e lança uma `AbpValidationException` se não estiver em um estado válido. + +* `GetErrorsAsync` não lança uma exceção, somente retorna os erros de validação. + +`IObjectValidator` é implementado pelo `ObjectValidator` por padrão. `ObjectValidator` é extensível; você pode implementar a interface `IObjectValidationContributor` para contribuir com uma lógica customizada. Exemplo: + +````csharp +public class MyObjectValidationContributor + : IObjectValidationContributor, ITransientDependency +{ + public Task AddErrorsAsync(ObjectValidationContext context) + { + //Get the validating object + var obj = context.ValidatingObject; + + //Add the validation errors if available + context.Errors.Add(...); + return Task.CompletedTask; + } +} +```` + +* Lembre-se de registrar sua classe no [DI](Dependency-Injection.md) (implementar `ITransientDependency` faz isso no exemplo anterior) +* ABP vai automaticamente descobrir sua classe e utilizá-la em qualquer tipo de validação de objetos (incluindo chamadas de métodos de validação automáticas). + +### IMethodInvocationValidator + +`IMethodInvocationValidator` é utilizado para validar a chamada de um método. Ele utiliza internamente o `IObjectValidator` para validar os objetos passados na chamada do método. Você normalmente não precisa deste serviço, já que ele é utilizado automaticamente pelo framework, mas você pode querer reutilizar ou substituir na sua aplicação em alguns casos raros. + +## Integração com FluentValidation + +O pacote Volo.Abp.FluentValidation integra a biblioteca FluentValidation com o sistema de validação (implementando o `IObjectValidationContributor`). Veja o [documento de Integração com o FluentValidation](FluentValidation.md) para mais informações. diff --git a/docs/zh-Hans/Audit-Logging.md b/docs/zh-Hans/Audit-Logging.md index a8b6e66449..5463fdb03c 100644 --- a/docs/zh-Hans/Audit-Logging.md +++ b/docs/zh-Hans/Audit-Logging.md @@ -42,6 +42,7 @@ Configure(options => * `AlwaysLogOnException`(默认值: `true`): 如果设置为 `true`,将始终在异常/错误情况下保存审计日志,不检查其他选项(`IsEnabled` 除外,它完全禁用了审计日志). * `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统. * `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志. +* `DisableLogActionInfo` (默认值: `false`): 如果设置为 `true`, 将不再记录 `AuditLogActionInfo`. * `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表. * `EntityHistorySelectors`:选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分. * `Contributors`: `AuditLogContributor` 实现的列表. 贡献者是扩展审计日志系统的一种方式. 有关详细信息请参阅下面的"审计日志贡献者"部分. diff --git a/docs/zh-Hans/Background-Jobs-Hangfire.md b/docs/zh-Hans/Background-Jobs-Hangfire.md index b1bf7b8c79..fb899758cc 100644 --- a/docs/zh-Hans/Background-Jobs-Hangfire.md +++ b/docs/zh-Hans/Background-Jobs-Hangfire.md @@ -66,6 +66,41 @@ public class YourModule : AbpModule } ```` +### 指定队列 + +你可以使用 [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) 来指定队列. + +````csharp +using System.Threading.Tasks; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Emailing; + +namespace MyProject +{ + [Queue("alpha")] + public class EmailSendingJob + : AsyncBackgroundJob, ITransientDependency + { + private readonly IEmailSender _emailSender; + + public EmailSendingJob(IEmailSender emailSender) + { + _emailSender = emailSender; + } + + public override async Task ExecuteAsync(EmailSendingArgs args) + { + await _emailSender.SendAsync( + args.EmailAddress, + args.Subject, + args.Body + ); + } + } +} +```` + 1. 如果你想要使用Hangfire的面板,你可以在 `Module` 类的 `OnApplicationInitialization` 方法添加: `UseHangfireDashboard` ````csharp diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index f890534795..f108bbcfec 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -123,6 +123,7 @@ abp new Acme.BookStore * `module`: [Module template](Startup-Templates/Module.md). 其他选项: * `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI). * **`console`**: [Console template](Startup-Templates/Console.md). + * **`maui`**: [Maui template](Startup-Templates/MAUI.md). * **`app-nolayers`**: 应用程序单层模板 * `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项: * `mvc`: ASP.NET Core MVC. diff --git a/docs/zh-Hans/Dapr/Index.md b/docs/zh-Hans/Dapr/Index.md new file mode 100644 index 0000000000..30936eb6da --- /dev/null +++ b/docs/zh-Hans/Dapr/Index.md @@ -0,0 +1,469 @@ +# ABP Dpar 集成 + +> 这个文档假设你已经熟悉[Dapr](https://dapr.io/)并且想在你的ABP应用中使用它. + +[Dapr](https://dapr.io/) (分布式应用运行时)提供了简化微服务连接的API.它是一个开源项目,主要由微软支持.它也是CNCF(云原生计算基金会)项目,受到社区的信任. + +ABP和Dapr有一些相似的特性,如服务到服务通信,分布式消息总线和分布式锁.然而,它们的目的完全不同.ABP的目标是通过提供自以为是的架构并提供必要的基础架构库,可重用模块和工具来正确实现该架构来提供端到端的开发人员体验.另一方面,Dapr的目的是提供一个运行时,将常见的微服务通信模式与应用程序逻辑解耦. + +ABP和Dapr可以完美地在同一个应用程序中一起工作.ABP提供了一些包来提供更好的集成,其中Dapr功能与ABP相似.你可以根据[Dapr文档](https://docs.dapr.io/)使用其他Dapr功能,而不需要ABP集成包. + +## ABP Dpar 集成包 + +ABP提供了以下NuGet包用于Dapr集成: + +* [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr): 主要的Dapr集成包.所有其他包都依赖于此包. +* [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr): 与Dapr的[服务调用](https://docs.dapr.io/developing-applications/building-blocks/service-invocation/service-invocation-overview/)集成的ABP的[动态](../API/Dynamic-CSharp-API-Clients.md)和[静态](../API/Static-CSharp-API-Clients.md)C# API客户端代理系统集成包. +* [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr): 使用Dapr的[发布和订阅](https://docs.dapr.io/developing-applications/building-blocks/pubsub/)构建块实现ABP的分布式事件总线.使用此包,你可以发送事件,但不能接收. +* [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus): 提供从Dapr的[发布和订阅](https://docs.dapr.io/developing-applications/building-blocks/pubsub/)构建块接收事件的端点.使用此包发送和接收事件. +* [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr): 使用Dapr的[分布式锁](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/)构建块为ABP框架的[分布式锁定](../Distributed-Locking.md)服务. + +在以下部分中,我们将看到如何使用这些包在ABP基础解决方案中使用Dapr. + +## 基础 + +### 安装 + +> 这个部分解释了如何将[Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr)添加到你的项目中.如果你使用的是其他Dapr集成包,你可以跳过这个部分,因为这个包会被间接添加. + +使用ABP CLI将[Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet包添加到你的项目中: + +* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)如果你之前没有安装过. +* 在你想要添加`Volo.Abp.Dapr`包的`.csproj`文件所在的目录中打开命令行(终端). +* 运行`abp add-package Volo.Abp.Dapr`命令. + +如果你想手动添加,安装 [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet包到你的项目中,并在项目内的[ABP模块](../Module-Development-Basics.md)类中添加`[DependsOn(typeof(AbpDaprModule))]`. + +### AbpDaprOptions + +`AbpDaprOptions` 是配置全局Dapr设置的主要[选项类](../Options.md).**所有设置都是可选的,你大多数情况下不需要配置它们.** 如果你需要,你可以在[模块类](../Module-Development-Basics.md)的`ConfigureServices`方法中配置它: + +````csharp +Configure(options => +{ + // ... +}); +```` + +可用的`AbpDaprOptions`类属性: + +* `HttpEndpoint` (可选):创建`DaprClient`对象时使用的HTTP端点.如果你没有指定,将使用默认值. +* `GrpcEndpoint` (可选):创建`DaprClient`对象时使用的gRPC端点.如果你没有指定,将使用默认值. +* `DaprApiToken` (可选):应用程序向Dapr发送请求时使用的[Dapr API token](https://docs.dapr.io/operations/security/api-token/).默认情况下,它从`DAPR_API_TOKEN`环境变量中填充(配置后由 Dapr 设置).有关详细信息,请参阅本文档的*安全*部分. +* `AppApiToken` (可选):用于验证来自Dapr的请求的[应用程序API token](https://docs.dapr.io/operations/security/app-api-token/).默认情况下,它从`APP_API_TOKEN`环境变量中填充(配置后由 Dapr 设置).有关详细信息,请参阅本文档的*安全*部分. + +或者, 你可以在 `appsettings.json` 文件的 `Dapr` 部分中配置选项.示例: + +````csharp +"Dapr": { + "HttpEndpoint": "http://localhost:3500/" +} +```` + +### 注入DaprClient + +ABP 将 `DaprClient` 类注册到 [依赖注入](../Dependency-Injection.md) 系统中.因此,你可以在需要时注入并使用它: + +````csharp +public class MyService : ITransientDependency +{ + private readonly DaprClient _daprClient; + + public MyService(DaprClient daprClient) + { + _daprClient = daprClient; + } + + public async Task DoItAsync() + { + // TODO: Use the injected _daprClient object + } +} +```` + +注入 `DaprClient` 是在应用程序代码中使用它的推荐方法.当你注入它时,将使用 `IAbpDaprClientFactory` 服务创建它,这会在下一节中将进行说明. + +### IAbpDaprClientFactory + +`IAbpDaprClientFactory` 可用于创建 `DaprClient` 或 `HttpClient` 对象来执行对 Dapr 的操作.它使用 `AbpDaprOptions`,因此你可以配置设置. + +**示例用法:** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDaprClientFactory _daprClientFactory; + + public MyService(IAbpDaprClientFactory daprClientFactory) + { + _daprClientFactory = daprClientFactory; + } + + public async Task DoItAsync() + { + // Create a DaprClient object with default options + DaprClient daprClient = await _daprClientFactory.CreateAsync(); + + /* Create a DaprClient object with configuring + * the DaprClientBuilder object */ + DaprClient daprClient2 = await _daprClientFactory + .CreateAsync(builder => + { + builder.UseDaprApiToken("..."); + }); + + // Create an HttpClient object + HttpClient httpClient = await _daprClientFactory + .CreateHttpClientAsync("target-app-id"); + } +} +```` + +`CreateHttpClientAsync` 方法还获取可选的 `daprEndpoint` 和 `daprApiToken` 参数. + +> ABP使用`IAbpDaprClientFactory`创建Dapr客户端.你也可以在应用程序中使用Dapr API创建客户端对象.推荐使用`IAbpDaprClientFactory`,但不是必需的. + +## C# API 客户端代理集成 + +ABP可以[动态](../API/Dynamic-CSharp-API-Clients.md)或[静态](../API/Static-CSharp-API-Clients.md)生成代理类,以便从Dotnet客户端应用程序调用HTTP API.在分布式系统中使用HTTP API是非常合理的.[Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr)包配置了客户端代理系统,因此它使用Dapr的服务调用构建块进行应用程序之间的通信. + +### 安装 + +使用ABP CLI将[Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet包添加到项目(客户端): + +* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)如果你之前没有安装过. +* 在你想要添加`Volo.Abp.Http.Client.Dapr`包的`.csproj`文件所在的目录中打开命令行(终端). +* 运行`abp add-package Volo.Abp.Http.Client.Dapr`命令. + +如果你想手动添加,安装 [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet包到你的项目中,并在项目内的[ABP模块](../Module-Development-Basics.md)类中添加`[DependsOn(typeof(AbpHttpClientDaprModule))]`. + +### 配置 + +当你安装了[Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet 包,所有你需要做的就是在`appsettings.json`或使用`AbpRemoteServiceOptions`[选项类](../Options.md)中配置ABP的远程服务选项. + +**示例:** + +````csharp +{ + "RemoteServices": { + "Default": { + "BaseUrl": "http://dapr-httpapi/" + } + } +} +```` + +`dapr-httpapi` 在这个例子中是你的Dapr配置中服务器应用程序的应用程序ID. + +远程服务名称(示例中是`Default`)应该匹配动态客户端代理中`AddHttpClientProxies`调用或静态客户端代理中`AddStaticHttpClientProxies`调用中指定的远程服务名称.如果你的客户端只与一个服务器通信,使用`Default`是可以的.但是,如果你的客户端使用多个服务器,你通常在`RemoteServices`配置中有多个键. 你将远程服务端点配置为Dapr应用程序ID,在你使用ABP的客户端代理系统时它将自动工作并通过Dapr进行HTTP调用, + +> 参阅[动态](../API/Dynamic-CSharp-API-Clients.md) 和 [static](../API/Static-CSharp-API-Clients.md)客户端代理文档,了解ABP的客户端代理系统的详细信息. + +## 分布式事件总线集成 + +[ABP的分布式事件总线](../Distributed-Event-Bus.md)系统提供了一个方便的抽象,允许应用程序通过事件异步通信.ABP提供了各种分布式消息系统(如RabbitMQ,Kafka和Azure)的集成包.Dapr也有一个[发布和订阅构建块](https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-overview/),用于相同的目的:分布式消息/事件. + +ABP的[Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr)和[Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus)包可以使用Dapr基础设施来实现ABP的分布式事件总线. + +任何类型的应用程序(例如,控制台或ASP.NET Core应用程序)都可以使用[Volo.Abp.EventBus.Dapr]包通过Dapr发布事件.为了能够接收消息(通过订阅事件),你需要安装[Volo.Abp.AspNetCore.Mvc.Dapr.EventBus]包,并且你的应用程序应该是ASP.NET Core应用程序. + +### 安装 + +如果你的应用程序是ASP.NET Core应用程序并且你想发送和接收事件,你需要按照下面的描述安装[Volo.Abp.AspNetCore.Mvc.Dapr.EventBus]包: + +* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)如果你之前没有安装过. +* 在你想要添加`Volo.Abp.AspNetCore.Mvc.Dapr.EventBus`包的`.csproj`文件所在的目录中打开命令行(终端). +* 运行`abp add-package Volo.Abp.AspNetCore.Mvc.Dapr.EventBus`命令. + +如果你想手动添加,安装 [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) NuGet包到你的项目中,并在项目内的[ABP模块](../Module-Development-Basics.md)类中添加`[DependsOn(typeof(AbpAspNetCoreMvcDaprEventBusModule))]`. + +> **如果你安装了[Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus)包, 那么你不需要安装[Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr)包,因为它已经由第一个包引用** + +如果你的应用程序不是ASP.NET Core应用程序,你不能从Dapr接收事件,至少使用ABP的集成包(如果你想在不同类型的应用程序中接收事件,请参阅[Dapr的文档](https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/)).但是你仍然可以使用[Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr)包发布消息.在这种情况下,请按照下面的步骤将该包安装到你的项目中: + +* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)如果你之前没有安装过. +* 在你想要添加`Volo.Abp.EventBus.Dapr`包的`.csproj`文件所在的目录中打开命令行(终端). +* 运行`abp add-package Volo.Abp.EventBus.Daprs`命令. + +如果你想手动添加,安装 [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) NuGet包到你的项目中,并在项目内的[ABP模块](../Module-Development-Basics.md)类中添加`[DependsOn(typeof(AbpEventBusDaprModule))]`. + +### 配置 + +你可以为Dapr配置`AbpDaprEventBusOptions`[选项类](../Options.md): + +````csharp +Configure(options => +{ + options.PubSubName = "pubsub"; +}); +```` + +可用的`AbpDaprEventBusOptions`类的属性: + +* `PubSubName` (可选): 通过`DaprClient.PublishEventAsync`方法发布消息时的`pubsubName`参数.默认值:`pubsub`. + +### ABP订阅端点 + +ABP提供了以下端点来接收来自Dapr的事件: + +* `dapr/subscribe`: Dapr使用此端点从应用程序获取订阅列表.ABP会自动返回所有分布式事件处理程序类和具有`Topic`属性的自定义控制器操作的订阅. +* `api/abp/dapr/event`: 用于接收来自Dapr的所有事件的统一端点.ABP根据主题名称将事件分派给您的事件处理程序. + +> **由于ABP提供了标准的`dapr/subscribe`端点,所以你不应该手动调用Dapr的`app.MapSubscribeHandler()`方法.** 如果你想支持[CloudEvents](https://cloudevents.io/)标准,你可以在你的ASP.NET Core管道中使用`app.UseCloudEvents()`中间件. + +### 用法 + +#### ABP的方式 + +你可以按照[ABP的分布式事件总线文档](../Distributed-Event-Bus.md)来学习如何以ABP的方式发布和订阅事件.你的应用程序代码不需要做任何改变就可以使用Dapr的发布-订阅功能.ABP将自动为你的事件处理程序类(实现`IDistributedEventHandler`接口)订阅Dapr. + +ABP提供了 `api/abp/dapr/event` + +**示例:使用`IDistributedEventBus`服务发布事件** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IDistributedEventBus _distributedEventBus; + + public MyService(IDistributedEventBus distributedEventBus) + { + _distributedEventBus = distributedEventBus; + } + + public async Task DoItAsync() + { + await _distributedEventBus.PublishAsync(new StockCountChangedEto + { + ProductCode = "AT837234", + NewStockCount = 42 + }); + } +} +```` + +**示例:通过实现`IDistributedEventHandler`接口来订阅事件** + +````csharp +public class MyHandler : + IDistributedEventHandler, + ITransientDependency +{ + public async Task HandleEventAsync(StockCountChangedEto eventData) + { + var productCode = eventData.ProductCode; + // ... + } +} +```` + +参阅[ABP的分布式事件总线文档](../Distributed-Event-Bus.md)来了解细节. + +#### 使用Dapr API + +在ABP的标准分布式事件总线系统之外,你还可以使用Dapr的API来发布事件. + +> 如果你直接使用Dapr API来发布事件,你可能无法从ABP的标准分布式事件总线功能中受益,比如outbox/inbox模式的实现. + +**示例:使用`DaprClient`发布事件** + +````csharp +public class MyService : ITransientDependency +{ + private readonly DaprClient _daprClient; + + public MyService(DaprClient daprClient) + { + _daprClient = daprClient; + } + + public async Task DoItAsync() + { + await _daprClient.PublishEventAsync( + "pubsub", // pubsub name + "StockChanged", // topic name + new StockCountChangedEto // event data + { + ProductCode = "AT837234", + NewStockCount = 42 + } + ); + } +} +```` + +**示例:通过创建ASP.NET Core控制器来订阅事件** + +````csharp +public class MyController : AbpController +{ + [HttpPost("/stock-changed")] + [Topic("pubsub", "StockChanged")] + public async Task TestRouteAsync( + [FromBody] StockCountChangedEto model) + { + HttpContext.ValidateDaprAppApiToken(); + + // Do something with the event + return Ok(); + } +} +```` + +`HttpContext.ValidateDaprAppApiToken()` 扩展方法由ABP提供,用于检查请求是否来自Dapr.这是可选的.如果你想启用验证,你应该配置Dapr将App API令牌发送到你的应用程序.如果没有配置,`ValidateDaprAppApiToken()`不会执行任何操作.参阅[Dapr的App API令牌文档](https://docs.dapr.io/operations/security/app-api-token/)了解更多信息.还可以参阅本文档中的**AbpDaprOptions**和**安全**部分. + +参阅[Dapr的文档](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/publish-subscribe)来了解使用Dapr API发送和接收事件的细节. + +## 分布式锁 + +> Dapr的分布式锁功能目前处于Alpha阶段,可能还不稳定.在这一点上,不建议用Dapr来替换ABP的分布式锁. + +ABP提供了一个[分布式锁](../Distributed-Locking.md)抽象来控制多个应用程序对共享资源的访问.Dapr也有一个[分布式锁构建块](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/).[Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr)包使ABP使用Dapr的分布式锁系统. + +### 安装 + +使用ABP CLI将[Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr)NuGet包添加到项目(客户端): + +* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)如果你之前没有安装过. +* 在你想要添加`Volo.Abp.DistributedLocking.Dapr`包的`.csproj`文件所在的目录中打开命令行(终端). +* 运行`abp add-package Volo.Abp.DistributedLocking.Dapr`命令. + +如果你想手动添加,安装 [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) NuGet包到你的项目中,并在项目内的[ABP模块](../Module-Development-Basics.md)类中添加`[DependsOn(typeof(AbpDistributedLockingDaprModule))]`. + +### 配置 + +你可以在[你的模块](../Module-Development-Basics.md)的`ConfigureServices`方法中使用`AbpDistributedLockDaprOptions`选项类来配置Dapr分布式锁: + +````csharp +Configure(options => +{ + options.StoreName = "mystore"; +}); +```` + +以下选项可用: + +* **`StoreName`** (必需):Dapr使用的存储库名称.锁键名称在同一存储库中范围内.这意味着不同的应用程序可以在不同的存储库中获取相同的锁名称.对于要控制访问的相同资源,请使用相同的存储库名称. +* `Owner` (可选):`DaprClient.Lock`方法使用的`owner`值.如果你不指定,ABP使用一个随机值,这在一般情况下是可以的. +* `DefaultExpirationTimeout` (可选):锁过期后的默认值.默认值:2分钟. + +### 用法 + +你可以注入并使用`IAbpDistributedLock`服务,就像在[分布式锁文档](../Distributed-Locking.md)中解释的那样. + +**示例:** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDistributedLock _distributedLock; + + public MyService(IAbpDistributedLock distributedLock) + { + _distributedLock = distributedLock; + } + + public async Task MyMethodAsync() + { + await using (var handle = + await _distributedLock.TryAcquireAsync("MyLockName")) + { + if (handle != null) + { + // your code that access the shared resource + } + } + } +} +```` + +这里有两点关于`TryAcquireAsync`方法我们应该提到,与ABP的标准用法不同: + +* `timeout` 参数目前没有使用(即使你指定了它),因为Dapr不支持等待获取锁. +* Dapr 使用过期超时系统(这意味着即使你不通过释放处理程序来释放锁,锁也会在超时后自动释放).但是,ABP的`TryAcquireAsync`方法没有这样的参数.目前,你可以在应用程序中将`AbpDistributedLockDaprOptions.DefaultExpirationTimeout`设置为全局值. + +Dapr的分布式锁功能目前处于Alpha阶段,其API是可能会改变的候选者.如果你想要使用它,你可以这样做,但是要准备好未来的变化.目前,我们建议使用ABP的[分布式锁文档](../Distributed-Locking.md)中提到的[DistributedLock](https://github.com/madelson/DistributedLock)库. + +## 安全 + +如果你使用Dapr,你的应用程序中的大部分或全部传入和传出请求都会通过Dapr.Dapr使用两种API令牌来保护应用程序与Dapr之间的通信. + +### Dapr API Token + +> 这个令牌默认情况下是自动设置的,通常你不需要关心它. + +[在Dapr中启用API令牌身份验证](https://docs.dapr.io/operations/security/api-token/)文档描述了Dapr API令牌是什么以及如何配置.如果你想为你的应用程序启用它,请阅读该文档. + +如果你启用了Dapr API令牌,你应该在你的应用程序中向Dapr发送该令牌.`AbpDaprOptions`定义了一个`DaprApiToken`属性,作为在你的应用程序中配置Dapr API令牌的中心点. + +`DaprApiToken`属性的默认值是从`DAPR_API_TOKEN`环境变量设置的,并且该环境变量是在Dapr运行时设置的.所以,大多数情况下,你不需要在你的应用程序中配置`AbpDaprOptions.DaprApiToken`.但是,如果你需要配置(或覆盖它),你可以在模块类的`ConfigureServices`方法中这样做,如下面的代码块所示: + +````csharp +Configure(options => +{ + options.DaprApiToken = "..."; +}); +```` + +或者你可以在`appsettings.json`文件中设置它: + +````json +"Dapr": { + "DaprApiToken": "..." +} +```` + +一旦你设置了它,它就会在你注入`DaprClient`或使用`IAbpDaprClientFactory`时使用.如果你需要在应用程序中使用该值,你可以注入`IDaprApiTokenProvider`并使用其`GetDaprApiToken()`方法. + +### App API Token + +> 启用App API令牌验证是强烈推荐的.否则,例如,任何客户端都可以直接调用你的事件订阅端点,你的应用程序就像发生了事件一样(如果你的事件订阅端点中没有其他安全策略). + +[在Dapr中使用令牌身份验证请求身份验证](https://docs.dapr.io/operations/security/app-api-token/)文档描述了App API令牌是什么以及如何配置.如果你想为你的应用程序启用它,请阅读该文档. + +如果你启用了App API令牌,你可以验证它以确保请求来自Dapr.ABP提供了有用的快捷方式来验证它. + +**示例:在事件处理HTTP API中验证App API令牌** + +````csharp +public class MyController : AbpController +{ + [HttpPost("/stock-changed")] + [Topic("pubsub", "StockChanged")] + public async Task TestRouteAsync( + [FromBody] StockCountChangedEto model) + { + // Validate the App API token! + HttpContext.ValidateDaprAppApiToken(); + + // Do something with the event + return Ok(); + } +} +```` + +`HttpContext.ValidateDaprAppApiToken()` 是ABP框架提供的扩展方法.如果HTTP头中缺少或错误的令牌,则会抛出`AbpAuthorizationException`(头名称为`dapr-api-token`).你也可以注入`IDaprAppApiTokenValidator`并使用其方法在任何服务中验证令牌(不仅仅是在控制器类中). + +你可以配置`AbpDaprOptions.AppApiToken`,如果你想设置(或覆盖)App API令牌值.默认值由`APP_API_TOKEN`环境变量设置.你可以在模块类的`ConfigureServices`方法中这样做,如下面的代码块所示: + +````csharp +Configure(options => +{ + options.AppApiToken = "..."; +}); +```` + +或者你可以在`appsettings.json`文件中设置它: + +````json +"Dapr": { + "AppApiToken": "..." +} +```` + +如果你需要在应用程序中使用该值,你可以注入`IDaprApiTokenProvider`并使用其`GetAppApiToken()`方法. + +## 另请参阅 + +* [Dapr for .NET Developers](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/) +* [Dapr官方文档](https://docs.dapr.io/) diff --git a/docs/zh-Hans/Distributed-Locking.md b/docs/zh-Hans/Distributed-Locking.md index 2176230403..a570f598af 100644 --- a/docs/zh-Hans/Distributed-Locking.md +++ b/docs/zh-Hans/Distributed-Locking.md @@ -101,6 +101,25 @@ namespace AbpDemo * `timeout` (`TimeSpan`): 等待获取锁的超时值. 默认值为`TimeSpan.Zero`, 这意味着如果锁已经被另一个应用程序拥有, 它不会等待. * `cancellationToken`: 取消令牌可在触发后取消操作. +### 配置 + +#### AbpDistributedLockOptions + +`AbpDistributedLockOptions` 是配置分布式锁的主要选项类. + +**示例: 设置应用程序的分布式锁Key前缀** + +Configure(options => +{ + options.KeyPrefix = "MyApp1"; +}); + +> 在你的[模块类](Module-Development-Basics.md)中的 `ConfigureServices` 方法进行配置. + +##### 可用选项 + +* KeyPrefix (string, 默认值: null): 指定分布式锁名称前缀. + ### 使用DistributedLock库的API ABP的`IAbpDistributedLock`服务非常有限, 主要用于ABP框架的内部使用. 对于你自己的应用程序, 可以使用DistributedLock库自己的API. 参见[文档](https://github.com/madelson/DistributedLock)详细信息. diff --git a/docs/zh-Hans/Entities.md b/docs/zh-Hans/Entities.md index 8fbf16ddd3..b9c236e346 100644 --- a/docs/zh-Hans/Entities.md +++ b/docs/zh-Hans/Entities.md @@ -105,6 +105,22 @@ public class UserRole : Entity > 需要注意,复合主键实体不可以使用 `IRepository` 接口,因为它需要一个唯一的Id属性. 但你可以使用 `IRepository`.更多信息请参见[仓储](Repositories.md)的文档. +### EntityEquals + +`Entity.EntityEquals(...)` 方法用于检查两个实体对象是否相等. + +示例: + +```csharp +Book book1 = ... +Book book2 = ... + +if (book1.EntityEquals(book2)) //Check equality +{ + ... +} +``` + ## 聚合根 "*聚合是域驱动设计中的一种模式.DDD的聚合是一组可以作为一个单元处理的域对象.例如,订单及订单系列的商品,这些是独立的对象,但将订单(连同订单系列的商品)视为一个聚合通常是很有用的*"( [查看详细介绍](http://martinfowler.com/bliki/DDD_Aggregate.html)) diff --git a/docs/zh-Hans/Modules/Setting-Management.md b/docs/zh-Hans/Modules/Setting-Management.md index c99397e4b4..242553c2c9 100644 --- a/docs/zh-Hans/Modules/Setting-Management.md +++ b/docs/zh-Hans/Modules/Setting-Management.md @@ -87,7 +87,13 @@ namespace Demo ## Setting Management UI. -设置管理模块默认提供了邮件设置页面并且它是可扩展的; 你可以为你的应用程序设置添加设置标签到设置页面. +设置管理模块默认提供了邮件设置页面. + +![EmailSettingUi](../images/setting-management-email-ui.png) + +> 你可以点击发送测试邮件按钮发送一封测试邮件来检查你的邮件设置. + +设置UI是可扩展的; 你可以为你的应用程序设置添加设置标签到设置页面. ### MVC UI diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md index 472dde9eab..6db625e7d9 100644 --- a/docs/zh-Hans/Multi-Tenancy.md +++ b/docs/zh-Hans/Multi-Tenancy.md @@ -322,7 +322,6 @@ Volo.Abp.AspNetCore.MultiTenancy 添加了下面这些租户解析器,从当前W * **CurrentUserTenantResolveContributor**: 如果当前用户已登录,从当前用户的声明中获取租户Id. **出于安全考虑,应该始终将其做为第一个Contributor**. * **QueryStringTenantResolveContributor**: 尝试从query string参数中获取当前租户,默认参数名为"__tenant". -* **FormTenantResolveContributor**: 尝试从form参数中获取当前租户,默认参数名为"__tenant". * **RouteTenantResolveContributor**:尝试从当前路由中获取(URL路径),默认是变量名是"__tenant".所以,如果你的路由中定义了这个变量,就可以从路由中确定当前租户. * **HeaderTenantResolveContributor**: 尝试从HTTP header中获取当前租户,默认的header名称是"__tenant". * **CookieTenantResolveContributor**: 尝试从当前cookie中获取当前租户.默认的Cookie名称是"__tenant". diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 203000c6c6..be30753fb3 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -61,6 +61,10 @@ { "text": "WPF", "path": "Startup-Templates/WPF.md" + }, + { + "text": "MAUI", + "path": "Startup-Templates/MAUI.md" } ] }, diff --git a/docs/zh-Hans/images/setting-management-email-ui.png b/docs/zh-Hans/images/setting-management-email-ui.png new file mode 100644 index 0000000000..f8f1270c15 Binary files /dev/null and b/docs/zh-Hans/images/setting-management-email-ui.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index fadd40bcc5..4ce47bdbc8 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -409,6 +409,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.RemoteServices", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.PlugIn", "test\Volo.Abp.AspNetCore.Mvc.PlugIn\Volo.Abp.AspNetCore.Mvc.PlugIn.csproj", "{C6D6D878-208A-4FD2-822E-365545D8681B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Dapr", "src\Volo.Abp.Dapr\Volo.Abp.Dapr.csproj", "{192A829F-D608-4E41-8DE0-058E943E453F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EventBus.Dapr", "src\Volo.Abp.EventBus.Dapr\Volo.Abp.EventBus.Dapr.csproj", "{DCC41E99-EBC7-4F19-BA0D-A6F770D8E431}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client.Dapr", "src\Volo.Abp.Http.Client.Dapr\Volo.Abp.Http.Client.Dapr.csproj", "{18B796D2-D45D-41AE-9A42-75C9B14B20DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Dapr", "src\Volo.Abp.AspNetCore.Mvc.Dapr\Volo.Abp.AspNetCore.Mvc.Dapr.csproj", "{5EED625D-8D86-492A-BCB8-F6C8CD8D4AA1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Dapr.EventBus", "src\Volo.Abp.AspNetCore.Mvc.Dapr.EventBus\Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj", "{B02EF042-C39E-45C4-A92D-BF7554E1889D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.DistributedLocking.Dapr", "src\Volo.Abp.DistributedLocking.Dapr\Volo.Abp.DistributedLocking.Dapr.csproj", "{CAE48068-233C-47A9-BEAB-DDF521730E7A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1219,6 +1231,30 @@ Global {C6D6D878-208A-4FD2-822E-365545D8681B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C6D6D878-208A-4FD2-822E-365545D8681B}.Release|Any CPU.ActiveCfg = Release|Any CPU {C6D6D878-208A-4FD2-822E-365545D8681B}.Release|Any CPU.Build.0 = Release|Any CPU + {192A829F-D608-4E41-8DE0-058E943E453F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {192A829F-D608-4E41-8DE0-058E943E453F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {192A829F-D608-4E41-8DE0-058E943E453F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {192A829F-D608-4E41-8DE0-058E943E453F}.Release|Any CPU.Build.0 = Release|Any CPU + {DCC41E99-EBC7-4F19-BA0D-A6F770D8E431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DCC41E99-EBC7-4F19-BA0D-A6F770D8E431}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DCC41E99-EBC7-4F19-BA0D-A6F770D8E431}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DCC41E99-EBC7-4F19-BA0D-A6F770D8E431}.Release|Any CPU.Build.0 = Release|Any CPU + {18B796D2-D45D-41AE-9A42-75C9B14B20DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18B796D2-D45D-41AE-9A42-75C9B14B20DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18B796D2-D45D-41AE-9A42-75C9B14B20DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18B796D2-D45D-41AE-9A42-75C9B14B20DF}.Release|Any CPU.Build.0 = Release|Any CPU + {5EED625D-8D86-492A-BCB8-F6C8CD8D4AA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5EED625D-8D86-492A-BCB8-F6C8CD8D4AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5EED625D-8D86-492A-BCB8-F6C8CD8D4AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5EED625D-8D86-492A-BCB8-F6C8CD8D4AA1}.Release|Any CPU.Build.0 = Release|Any CPU + {B02EF042-C39E-45C4-A92D-BF7554E1889D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B02EF042-C39E-45C4-A92D-BF7554E1889D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B02EF042-C39E-45C4-A92D-BF7554E1889D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B02EF042-C39E-45C4-A92D-BF7554E1889D}.Release|Any CPU.Build.0 = Release|Any CPU + {CAE48068-233C-47A9-BEAB-DDF521730E7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAE48068-233C-47A9-BEAB-DDF521730E7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAE48068-233C-47A9-BEAB-DDF521730E7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAE48068-233C-47A9-BEAB-DDF521730E7A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1425,6 +1461,12 @@ Global {3683340D-92F5-4B14-B77B-34A163333309} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {EDFFDA74-090D-439C-A58D-06CCF86D4423} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {C6D6D878-208A-4FD2-822E-365545D8681B} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {192A829F-D608-4E41-8DE0-058E943E453F} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {DCC41E99-EBC7-4F19-BA0D-A6F770D8E431} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {18B796D2-D45D-41AE-9A42-75C9B14B20DF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {5EED625D-8D86-492A-BCB8-F6C8CD8D4AA1} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {B02EF042-C39E-45C4-A92D-BF7554E1889D} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {CAE48068-233C-47A9-BEAB-DDF521730E7A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor new file mode 100644 index 0000000000..f512232b86 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor @@ -0,0 +1,7 @@ +@if (LayoutHookViewModel.Hooks.Any()) +{ + foreach (var hook in LayoutHookViewModel.Hooks) + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs new file mode 100644 index 0000000000..18fbcd89c2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Options; +using Volo.Abp.Ui.LayoutHooks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Components.LayoutHooks; + +public partial class LayoutHook : ComponentBase +{ + [Parameter] + public string Name { get; set; } + + [Parameter] + public string Layout { get; set; } + + [Inject] + protected IOptions LayoutHookOptions { get; set; } + + protected LayoutHookViewModel LayoutHookViewModel { get; private set; } + + protected override Task OnInitializedAsync() + { + if (LayoutHookOptions.Value.Hooks.TryGetValue(Name, out var layoutHooks)) + { + layoutHooks = layoutHooks + .WhereIf(string.IsNullOrWhiteSpace(Layout), x => x.Layout == Layout) + .ToList(); + } + + layoutHooks ??= new List(); + + LayoutHookViewModel = new LayoutHookViewModel(layoutHooks.ToArray(), Layout); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs new file mode 100644 index 0000000000..ea73a5673f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout; + +public static class StandardLayouts +{ + public const string Application = "Application"; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs index 523a770a17..c3ba260a88 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs @@ -9,26 +9,43 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly; public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency { - protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; } + protected AbpApplicationConfigurationClientProxy ApplicationConfigurationClientProxy { get; } + + protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; } protected ApplicationConfigurationCache Cache { get; } protected ICurrentTenantAccessor CurrentTenantAccessor { get; } public WebAssemblyCachedApplicationConfigurationClient( - AbpApplicationConfigurationClientProxy applicationConfigurationAppService, + AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy, ApplicationConfigurationCache cache, - ICurrentTenantAccessor currentTenantAccessor) + ICurrentTenantAccessor currentTenantAccessor, + AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy) { - ApplicationConfigurationAppService = applicationConfigurationAppService; + ApplicationConfigurationClientProxy = applicationConfigurationClientProxy; Cache = cache; CurrentTenantAccessor = currentTenantAccessor; + ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; } public virtual async Task InitializeAsync() { - var configurationDto = await ApplicationConfigurationAppService.GetAsync(); + var configurationDto = await ApplicationConfigurationClientProxy.GetAsync( + new ApplicationConfigurationRequestOptions { + IncludeLocalizationResources = false + } + ); + + var localizationDto = await ApplicationLocalizationClientProxy.GetAsync( + new ApplicationLocalizationRequestDto { + CultureName = configurationDto.Localization.CurrentCulture.Name, + OnlyDynamics = true + } + ); + configurationDto.Localization.Resources = localizationDto.Resources; + Cache.Set(configurationDto); CurrentTenantAccessor.Current = new BasicTenantInfo( diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs index ad36cf0f11..e0d8ecd806 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs @@ -15,7 +15,6 @@ public class AbpAspNetCoreMultiTenancyModule : AbpModule Configure(options => { options.TenantResolvers.Add(new QueryStringTenantResolveContributor()); - options.TenantResolvers.Add(new FormTenantResolveContributor()); options.TenantResolvers.Add(new RouteTenantResolveContributor()); options.TenantResolvers.Add(new HeaderTenantResolveContributor()); options.TenantResolvers.Add(new CookieTenantResolveContributor()); diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs index 67ef0a4c2c..d55343c94c 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs @@ -1,10 +1,11 @@ -using System.Linq; +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Volo.Abp.MultiTenancy; namespace Volo.Abp.AspNetCore.MultiTenancy; +[Obsolete("This may make some features of ASP NET Core unavailable, Will be removed in future versions.")] public class FormTenantResolveContributor : HttpTenantResolveContributorBase { public const string ContributorName = "Form"; diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs index 8fc6a3995c..00b3f87056 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs @@ -118,6 +118,12 @@ public class MultiTenancyMiddleware : IMiddleware, ITransientDependency uiCulture = defaultLanguage; } - return new RequestCulture(culture, uiCulture); + if (CultureHelper.IsValidCultureCode(culture) && + CultureHelper.IsValidCultureCode(uiCulture)) + { + return new RequestCulture(culture, uiCulture); + } + + return null; } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs index 198cfce711..2d57dc66ab 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs @@ -15,8 +15,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; [ExposeServices(typeof(IAbpApplicationConfigurationAppService), typeof(AbpApplicationConfigurationClientProxy))] public partial class AbpApplicationConfigurationClientProxy : ClientProxyBase, IAbpApplicationConfigurationAppService { - public virtual async Task GetAsync() + public virtual async Task GetAsync(ApplicationConfigurationRequestOptions options) { - return await RequestAsync(nameof(GetAsync)); + return await RequestAsync(nameof(GetAsync), new ClientProxyRequestTypeValue + { + { typeof(ApplicationConfigurationRequestOptions), options } + }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs new file mode 100644 index 0000000000..74db054d18 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs @@ -0,0 +1,25 @@ +// This file is automatically generated by ABP framework to use MVC Controllers from CSharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Http.Client; +using Volo.Abp.Http.Modeling; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +// ReSharper disable once CheckNamespace +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(IAbpApplicationLocalizationAppService), typeof(AbpApplicationLocalizationClientProxy))] +public partial class AbpApplicationLocalizationClientProxy : ClientProxyBase, IAbpApplicationLocalizationAppService +{ + public virtual async Task GetAsync(ApplicationLocalizationRequestDto input) + { + return await RequestAsync(nameof(GetAsync), new ClientProxyRequestTypeValue + { + { typeof(ApplicationLocalizationRequestDto), input } + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs new file mode 100644 index 0000000000..11bef4cecf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs @@ -0,0 +1,7 @@ +// This file is part of AbpApplicationLocalizationClientProxy, you can customize it here +// ReSharper disable once CheckNamespace +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; + +public partial class AbpApplicationLocalizationClientProxy +{ +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json index bf15515078..ce18e3eefd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json @@ -7,6 +7,8 @@ "Pages.Abp.MultiTenancy.AbpTenantController": { "controllerName": "AbpTenant", "controllerGroupName": "AbpTenant", + "isRemoteService": true, + "apiVersion": null, "type": "Pages.Abp.MultiTenancy.AbpTenantController", "interfaces": [ { @@ -93,6 +95,8 @@ "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationController": { "controllerName": "AbpApplicationConfiguration", "controllerGroupName": "AbpApplicationConfiguration", + "isRemoteService": true, + "apiVersion": null, "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationController", "interfaces": [ { @@ -100,14 +104,36 @@ } ], "actions": { - "GetAsync": { - "uniqueName": "GetAsync", + "GetAsyncByOptions": { + "uniqueName": "GetAsyncByOptions", "name": "GetAsync", "httpMethod": "GET", "url": "api/abp/application-configuration", "supportedVersions": [], - "parametersOnMethod": [], - "parameters": [], + "parametersOnMethod": [ + { + "name": "options", + "typeAsString": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions, Volo.Abp.AspNetCore.Mvc.Contracts", + "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions", + "typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "options", + "name": "IncludeLocalizationResources", + "jsonName": null, + "type": "System.Boolean", + "typeSimple": "boolean", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "options" + } + ], "returnValue": { "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationDto", "typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationDto" @@ -117,9 +143,74 @@ } } }, + "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationLocalizationController": { + "controllerName": "AbpApplicationLocalization", + "controllerGroupName": "AbpApplicationLocalization", + "isRemoteService": true, + "apiVersion": null, + "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationLocalizationController", + "interfaces": [ + { + "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.IAbpApplicationLocalizationAppService" + } + ], + "actions": { + "GetAsyncByInput": { + "uniqueName": "GetAsyncByInput", + "name": "GetAsync", + "httpMethod": "GET", + "url": "api/abp/application-localization", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto, Volo.Abp.AspNetCore.Mvc.Contracts", + "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto", + "typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "input", + "name": "CultureName", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "OnlyDynamics", + "jsonName": null, + "type": "System.Boolean", + "typeSimple": "boolean", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + } + ], + "returnValue": { + "type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationDto", + "typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationDto" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.IAbpApplicationLocalizationAppService" + } + } + }, "Volo.Abp.AspNetCore.Mvc.ApiExploring.AbpApiDefinitionController": { "controllerName": "AbpApiDefinition", "controllerGroupName": "AbpApiDefinition", + "isRemoteService": true, + "apiVersion": null, "type": "Volo.Abp.AspNetCore.Mvc.ApiExploring.AbpApiDefinitionController", "interfaces": [], "actions": { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs new file mode 100644 index 0000000000..c5548acc62 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; +using Volo.Abp.Localization.External; + +namespace Volo.Abp.AspNetCore.Mvc.Client; + +public class RemoteExternalLocalizationStore : IExternalLocalizationStore, ITransientDependency +{ + protected ICachedApplicationConfigurationClient ConfigurationClient { get; } + protected AbpLocalizationOptions LocalizationOptions { get; } + + public RemoteExternalLocalizationStore( + ICachedApplicationConfigurationClient configurationClient, + IOptions localizationOptions) + { + ConfigurationClient = configurationClient; + LocalizationOptions = localizationOptions.Value; + } + + public virtual LocalizationResourceBase GetResourceOrNull(string resourceName) + { + var configurationDto = ConfigurationClient.Get(); + return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto); + } + + public virtual async Task GetResourceOrNullAsync(string resourceName) + { + var configurationDto = await ConfigurationClient.GetAsync(); + return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto); + } + + public virtual async Task GetResourceNamesAsync() + { + var configurationDto = await ConfigurationClient.GetAsync(); + return configurationDto + .Localization + .Resources + .Keys + .Where(x => !LocalizationOptions.Resources.ContainsKey(x)) + .ToArray(); +; } + + public virtual async Task GetResourcesAsync() + { + var configurationDto = await ConfigurationClient.GetAsync(); + var resources = new List(); + + foreach (var resource in configurationDto.Localization.Resources) + { + if (LocalizationOptions.Resources.ContainsKey(resource.Key)) + { + continue; + } + + resources.Add(CreateNonTypedLocalizationResource(resource.Key, resource.Value)); + } + + return resources.ToArray(); + } + + protected virtual LocalizationResourceBase CreateLocalizationResourceFromConfigurationOrNull( + string resourceName, + ApplicationConfigurationDto configurationDto) + { + var resourceDto = configurationDto.Localization.Resources.GetOrDefault(resourceName); + + if (resourceDto == null) + { + return null; + } + + return CreateNonTypedLocalizationResource(resourceName, resourceDto); + } + + protected virtual NonTypedLocalizationResource CreateNonTypedLocalizationResource( + string resourceName, + ApplicationLocalizationResourceDto resourceDto) + { + return new NonTypedLocalizationResource(resourceName) + .AddBaseResources(resourceDto.BaseResources); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs index c01fc6a1af..e6e180c5f2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs @@ -1,15 +1,20 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.Localization; namespace Volo.Abp.AspNetCore.Mvc.Client; public class RemoteLocalizationContributor : ILocalizationResourceContributor { - private LocalizationResource _resource; + public bool IsDynamic => true; + + private LocalizationResourceBase _resource; private ICachedApplicationConfigurationClient _applicationConfigurationClient; private ILogger _logger; @@ -21,50 +26,137 @@ public class RemoteLocalizationContributor : ILocalizationResourceContributor ?? NullLogger.Instance; } - public LocalizedString GetOrNull(string cultureName, string name) + public virtual LocalizedString GetOrNull(string cultureName, string name) + { + /* cultureName is not used because remote localization can only + * be done in the current culture. */ + + return GetOrNullInternal(_resource.ResourceName, name); + } + + protected virtual LocalizedString GetOrNullInternal(string resourceName, string name) { - var resource = GetResourceOrNull(); + var resource = GetResourceOrNull(resourceName); if (resource == null) { return null; } - var value = resource.GetOrDefault(name); - if (value == null) + var value = resource.Texts.GetOrDefault(name); + if (value != null) { - return null; + return new LocalizedString(name, value); + } + + foreach (var baseResource in resource.BaseResources) + { + value = GetOrNullInternal(baseResource, name); + if (value != null) + { + return new LocalizedString(name, value); + } + } + + return null; + } + + public virtual void Fill(string cultureName, Dictionary dictionary) + { + /* cultureName is not used because remote localization can only + * be done in the current culture. */ + + FillInternal(_resource.ResourceName, dictionary); + } + + protected virtual void FillInternal(string resourceName, Dictionary dictionary) + { + var resource = GetResourceOrNull(resourceName); + if (resource == null) + { + return; + } + + foreach (var baseResource in resource.BaseResources) + { + FillInternal(baseResource, dictionary); } - return new LocalizedString(name, value); + foreach (var keyValue in resource.Texts) + { + dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value); + } } - public void Fill(string cultureName, Dictionary dictionary) + public virtual async Task FillAsync(string cultureName, Dictionary dictionary) + { + /* cultureName is not used because remote localization can only + * be done in the current culture. */ + + await FillInternalAsync(_resource.ResourceName, dictionary); + } + + protected virtual async Task FillInternalAsync(string resourceName, Dictionary dictionary) { - var resource = GetResourceOrNull(); + var resource = await GetResourceOrNullAsync(resourceName); if (resource == null) { return; } + + foreach (var baseResource in resource.BaseResources) + { + await FillInternalAsync(baseResource, dictionary); + } - foreach (var keyValue in resource) + foreach (var keyValue in resource.Texts) { dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value); } } - private Dictionary GetResourceOrNull() + public virtual Task> GetSupportedCulturesAsync() + { + /* This contributor does not know all the supported cultures by the + remote localization resource, and it is not needed on the client side */ + return Task.FromResult((IEnumerable)Array.Empty()); + } + + protected virtual ApplicationLocalizationResourceDto GetResourceOrNull(string resourceName) { var applicationConfigurationDto = _applicationConfigurationClient.Get(); + return GetResourceOrNull(applicationConfigurationDto, resourceName); + } + + protected virtual async Task GetResourceOrNullAsync(string resourceName) + { + var applicationConfigurationDto = await _applicationConfigurationClient.GetAsync(); + return GetResourceOrNull(applicationConfigurationDto, resourceName); + } - var resource = applicationConfigurationDto + protected virtual ApplicationLocalizationResourceDto GetResourceOrNull( + ApplicationConfigurationDto applicationConfigurationDto, + string resourceName) + { + var resource = applicationConfigurationDto.Localization.Resources.GetOrDefault(resourceName); + if (resource != null) + { + return resource; + } + + var legacyResource = applicationConfigurationDto .Localization.Values - .GetOrDefault(_resource.ResourceName); + .GetOrDefault(resourceName); - if (resource == null) + if (legacyResource != null) { - _logger.LogWarning($"Could not find the localization resource {_resource.ResourceName} on the remote server!"); + return new ApplicationLocalizationResourceDto + { + Texts = legacyResource, + BaseResources = Array.Empty() + }; } - return resource; + _logger.LogWarning($"Could not find the localization resource {resourceName} on the remote server!"); + return null; } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs index 7c21b9fe09..ae5421d947 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs @@ -1,8 +1,5 @@ -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Volo.Abp.EventBus; using Volo.Abp.Modularity; -using Volo.Abp.Threading; namespace Volo.Abp.AspNetCore.Mvc.Client; @@ -12,13 +9,5 @@ namespace Volo.Abp.AspNetCore.Mvc.Client; )] public class AbpAspNetCoreMvcClientModule : AbpModule { - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); - } - - public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) - { - await context.ServiceProvider.GetRequiredService().InitializeAsync(); - } + } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs index 59690b2d03..405d43906d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs @@ -15,6 +15,7 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu { protected IHttpContextAccessor HttpContextAccessor { get; } protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; } + protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; } protected ICurrentUser CurrentUser { get; } protected IDistributedCache Cache { get; } @@ -22,19 +23,16 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu IDistributedCache cache, AbpApplicationConfigurationClientProxy applicationConfigurationAppService, ICurrentUser currentUser, - IHttpContextAccessor httpContextAccessor) + IHttpContextAccessor httpContextAccessor, + AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy) { ApplicationConfigurationAppService = applicationConfigurationAppService; CurrentUser = currentUser; HttpContextAccessor = httpContextAccessor; + ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; Cache = cache; } - public async Task InitializeAsync() - { - await GetAsync(); - } - public async Task GetAsync() { var cacheKey = CreateCacheKey(); @@ -45,10 +43,9 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu return configuration; } - configuration = await Cache.GetOrAddAsync( cacheKey, - async () => await ApplicationConfigurationAppService.GetAsync(), + async () => await GetRemoteConfigurationAsync(), () => new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300) //TODO: Should be configurable. @@ -63,6 +60,27 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu return configuration; } + private async Task GetRemoteConfigurationAsync() + { + var config = await ApplicationConfigurationAppService.GetAsync( + new ApplicationConfigurationRequestOptions + { + IncludeLocalizationResources = false + } + ); + + var localizationDto = await ApplicationLocalizationClientProxy.GetAsync( + new ApplicationLocalizationRequestDto { + CultureName = config.Localization.CurrentCulture.Name, + OnlyDynamics = true + } + ); + + config.Localization.Resources = localizationDto.Resources; + + return config; + } + public ApplicationConfigurationDto Get() { var cacheKey = CreateCacheKey(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs new file mode 100644 index 0000000000..a01534775a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +public class ApplicationConfigurationRequestOptions +{ + /// + /// Set to true to fill the Values property in . + /// + public bool IncludeLocalizationResources { get; set; } = true; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs index f92c9d7bb9..b1c0c16e76 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs @@ -7,8 +7,23 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; [Serializable] public class ApplicationLocalizationConfigurationDto { + /// + /// This is not filled if is false. + /// public Dictionary> Values { get; set; } + /// + /// This property will never be filled by the application configuration endpoint + /// (by AbpApplicationConfigurationAppService). However, it is here to be filled + /// using the application localization endpoint (AbpApplicationLocalizationAppService). + /// This is an ugly design, but it is the best solution for backward-compability and + /// simple implementation. + /// + /// It's client's responsibility to fill this property + /// using the application localization endpoint. + /// + public Dictionary Resources { get; set; } = new(); + public List Languages { get; set; } public CurrentCultureDto CurrentCulture { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs new file mode 100644 index 0000000000..d301490bed --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +[Serializable] +public class ApplicationLocalizationDto +{ + public Dictionary Resources { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs new file mode 100644 index 0000000000..535d572b3f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +public class ApplicationLocalizationRequestDto +{ + [Required] + public string CultureName { get; set; } + + public bool OnlyDynamics { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs new file mode 100644 index 0000000000..a7200423d2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +[Serializable] +public class ApplicationLocalizationResourceDto +{ + public Dictionary Texts { get; set; } + + public string[] BaseResources { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs index 8f1896e31f..83f1e0c803 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using System; +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +[Serializable] public class CurrentCultureDto { public string DisplayName { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs index 9c8fd3d414..ae4f22cc06 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs @@ -5,5 +5,5 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; public interface IAbpApplicationConfigurationAppService : IApplicationService { - Task GetAsync(); -} + Task GetAsync(ApplicationConfigurationRequestOptions options); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs new file mode 100644 index 0000000000..af72c8f7ee --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +public interface IAbpApplicationLocalizationAppService : IApplicationService +{ + Task GetAsync(ApplicationLocalizationRequestDto input); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/studio/source-codes/Volo.Abp.AuditLogging.SourceCode/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/FodyWeavers.xsd similarity index 100% rename from studio/source-codes/Volo.Abp.AuditLogging.SourceCode/FodyWeavers.xsd rename to framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/FodyWeavers.xsd diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj new file mode 100644 index 0000000000..78b46cad27 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj @@ -0,0 +1,18 @@ + + + + + + + net6.0 + enable + enable + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs new file mode 100644 index 0000000000..333a5aae9a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http.Json; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.SystemTextJson; +using Volo.Abp.EventBus.Dapr; +using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; + +[DependsOn( + typeof(AbpAspNetCoreMvcDaprModule), + typeof(AbpEventBusDaprModule) +)] +public class AbpAspNetCoreMvcDaprEventBusModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + // TODO: Add NewtonsoftJson json converter. + + Configure(options => + { + options.SerializerOptions.Converters.Add(new AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter()); + }); + + Configure(options => + { + options.JsonSerializerOptions.Converters.Add(new AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter()); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusOptions.cs new file mode 100644 index 0000000000..02ca4c8e22 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusOptions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; + +public class AbpAspNetCoreMvcDaprEventBusOptions +{ + public List Contributors { get; } + + public AbpAspNetCoreMvcDaprEventBusOptions() + { + Contributors = new List(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubConsts.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubConsts.cs new file mode 100644 index 0000000000..e785f2e737 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubConsts.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; + +public class AbpAspNetCoreMvcDaprPubSubConsts +{ + public const string DaprSubscribeUrl = "dapr/subscribe"; + + public const string DaprEventCallbackUrl = "api/abp/dapr/event"; +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubProvider.cs new file mode 100644 index 0000000000..e797bd2fc0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubProvider.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; +using Volo.Abp.EventBus.Dapr; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; + +public class AbpAspNetCoreMvcDaprPubSubProvider : ITransientDependency +{ + protected IServiceProvider ServiceProvider { get; } + protected AbpAspNetCoreMvcDaprEventBusOptions AspNetCoreMvcDaprEventBusOptions { get; } + protected AbpDaprEventBusOptions DaprEventBusOptions { get; } + protected AbpDistributedEventBusOptions DistributedEventBusOptions { get; } + + public AbpAspNetCoreMvcDaprPubSubProvider( + IServiceProvider serviceProvider, + IOptions aspNetCoreDaprEventBusOptions, + IOptions daprEventBusOptions, + IOptions distributedEventBusOptions) + { + ServiceProvider = serviceProvider; + AspNetCoreMvcDaprEventBusOptions = aspNetCoreDaprEventBusOptions.Value; + DaprEventBusOptions = daprEventBusOptions.Value; + DistributedEventBusOptions = distributedEventBusOptions.Value; + } + + public virtual async Task> GetSubscriptionsAsync() + { + var subscriptions = new List(); + foreach (var handler in DistributedEventBusOptions.Handlers) + { + foreach (var @interface in handler.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDistributedEventHandler<>))) + { + var eventType = @interface.GetGenericArguments()[0]; + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + + subscriptions.Add(new AbpAspNetCoreMvcDaprSubscriptionDefinition() + { + PubSubName = DaprEventBusOptions.PubSubName, + Topic = eventName, + Route = AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl + }); + } + } + + if (AspNetCoreMvcDaprEventBusOptions.Contributors.Any()) + { + using (var scope = ServiceProvider.CreateScope()) + { + var context = new AbpAspNetCoreMvcDaprPubSubProviderContributorContext(scope.ServiceProvider, subscriptions); + foreach (var contributor in AspNetCoreMvcDaprEventBusOptions.Contributors) + { + await contributor.ContributeAsync(context); + } + } + } + + return subscriptions; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubProviderContributorContext.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubProviderContributorContext.cs new file mode 100644 index 0000000000..564b541dec --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubProviderContributorContext.cs @@ -0,0 +1,16 @@ +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; + +public class AbpAspNetCoreMvcDaprPubSubProviderContributorContext +{ + public IServiceProvider ServiceProvider { get; } + + public List Subscriptions { get; } + + public AbpAspNetCoreMvcDaprPubSubProviderContributorContext(IServiceProvider serviceProvider, List daprSubscriptionModels) + { + ServiceProvider = serviceProvider; + Subscriptions = daprSubscriptionModels; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprPubSubController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprPubSubController.cs new file mode 100644 index 0000000000..674e227cd0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprPubSubController.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; +using Volo.Abp.Dapr; +using Volo.Abp.EventBus.Dapr; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Controllers; + +[Area("abp")] +[RemoteService(Name = "abp")] +public class AbpAspNetCoreMvcDaprPubSubController : AbpController +{ + [HttpGet(AbpAspNetCoreMvcDaprPubSubConsts.DaprSubscribeUrl)] + public virtual async Task> SubscribeAsync() + { + return await HttpContext.RequestServices.GetRequiredService().GetSubscriptionsAsync(); + } + + [HttpPost(AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl)] + public virtual async Task EventsAsync() + { + this.HttpContext.ValidateDaprAppApiToken(); + + var bodyJsonDocument = await JsonDocument.ParseAsync(HttpContext.Request.Body); + var request = JsonSerializer.Deserialize(bodyJsonDocument.RootElement.GetRawText(), + HttpContext.RequestServices.GetRequiredService>().Value.JsonSerializerOptions); + + var distributedEventBus = HttpContext.RequestServices.GetRequiredService(); + var daprSerializer = HttpContext.RequestServices.GetRequiredService(); + + var eventData = daprSerializer.Deserialize(bodyJsonDocument.RootElement.GetProperty("data").GetRawText(), distributedEventBus.GetEventType(request.Topic)); + await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(request.Topic), eventData); + + return Ok(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/IAbpAspNetCoreMvcDaprPubSubProviderContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/IAbpAspNetCoreMvcDaprPubSubProviderContributor.cs new file mode 100644 index 0000000000..87047cd7ec --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/IAbpAspNetCoreMvcDaprPubSubProviderContributor.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; + +public interface IAbpAspNetCoreMvcDaprPubSubProviderContributor +{ + Task ContributeAsync(AbpAspNetCoreMvcDaprPubSubProviderContributorContext context); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionDefinition.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionDefinition.cs new file mode 100644 index 0000000000..287e78e01a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionDefinition.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; + +public class AbpAspNetCoreMvcDaprSubscriptionDefinition +{ + public string PubSubName { get; set; } + + public string Topic { get; set; } + + public string Route { get; set; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionRequest.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionRequest.cs new file mode 100644 index 0000000000..46c04b5a44 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionRequest.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; + +public class AbpAspNetCoreMvcDaprSubscriptionRequest +{ + public string PubSubName { get; set; } + + public string Topic { get; set; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprPubSubJsonNamingPolicy.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprPubSubJsonNamingPolicy.cs new file mode 100644 index 0000000000..0aa6bd4f7c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprPubSubJsonNamingPolicy.cs @@ -0,0 +1,11 @@ +using System.Text.Json; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.SystemTextJson; + +public class AbpAspNetCoreMvcDaprPubSubJsonNamingPolicy : JsonNamingPolicy +{ + public override string ConvertName(string name) + { + return name.ToLower(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter.cs new file mode 100644 index 0000000000..bcd2a0e1be --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter.cs @@ -0,0 +1,25 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.SystemTextJson; + +public class AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter : JsonConverter +{ + private JsonSerializerOptions? _writeJsonSerializerOptions; + + public override AbpAspNetCoreMvcDaprSubscriptionDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + + public override void Write(Utf8JsonWriter writer, AbpAspNetCoreMvcDaprSubscriptionDefinition value, JsonSerializerOptions options) + { + _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(new JsonSerializerOptions(options) + { + PropertyNamingPolicy = new AbpAspNetCoreMvcDaprPubSubJsonNamingPolicy() + }, x => x == this); + + JsonSerializer.Serialize(writer, value, _writeJsonSerializerOptions); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo.Abp.AspNetCore.Mvc.Dapr.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo.Abp.AspNetCore.Mvc.Dapr.csproj new file mode 100644 index 0000000000..d792a30b24 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo.Abp.AspNetCore.Mvc.Dapr.csproj @@ -0,0 +1,22 @@ + + + + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/AbpAspNetCoreMvcDaprModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/AbpAspNetCoreMvcDaprModule.cs new file mode 100644 index 0000000000..f8ba3fcfc2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/AbpAspNetCoreMvcDaprModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Dapr; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr; + +[DependsOn( + typeof(AbpAspNetCoreMvcModule), + typeof(AbpDaprModule) +)] +public class AbpAspNetCoreMvcDaprModule : AbpModule +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs new file mode 100644 index 0000000000..817eb9824d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Authorization; +using Volo.Abp.Dapr; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr; + +public class DaprAppApiTokenValidator : IDaprAppApiTokenValidator, ISingletonDependency +{ + protected IHttpContextAccessor HttpContextAccessor { get; } + protected HttpContext HttpContext => GetHttpContext(); + + public DaprAppApiTokenValidator(IHttpContextAccessor httpContextAccessor) + { + HttpContextAccessor = httpContextAccessor; + } + + public virtual void CheckDaprAppApiToken() + { + var expectedAppApiToken = GetConfiguredAppApiTokenOrNull(); + if (expectedAppApiToken.IsNullOrWhiteSpace()) + { + return; + } + + var headerAppApiToken = GetDaprAppApiTokenOrNull(); + if (headerAppApiToken.IsNullOrWhiteSpace()) + { + throw new AbpAuthorizationException("Expected Dapr App API Token is not provided! Dapr should set the 'dapr-api-token' HTTP header."); + } + + if (expectedAppApiToken != headerAppApiToken) + { + throw new AbpAuthorizationException("The Dapr App API Token (provided in the 'dapr-api-token' HTTP header) doesn't match the expected value!"); + } + } + + public virtual bool IsValidDaprAppApiToken() + { + var expectedAppApiToken = GetConfiguredAppApiTokenOrNull(); + if (expectedAppApiToken.IsNullOrWhiteSpace()) + { + return true; + } + + var headerAppApiToken = GetDaprAppApiTokenOrNull(); + return expectedAppApiToken == headerAppApiToken; + } + + public virtual string? GetDaprAppApiTokenOrNull() + { + string apiTokenHeader = HttpContext.Request.Headers["dapr-api-token"]; + if (string.IsNullOrEmpty(apiTokenHeader) || apiTokenHeader.Length < 1) + { + return null; + } + + return apiTokenHeader; + } + + protected virtual string? GetConfiguredAppApiTokenOrNull() + { + return HttpContext + .RequestServices + .GetRequiredService() + .GetAppApiToken(); + } + + protected virtual HttpContext GetHttpContext() + { + return HttpContextAccessor.HttpContext ?? throw new AbpException("HttpContext is not available!"); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs new file mode 100644 index 0000000000..15663d0c3c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr; + +public static class DaprHttpContextExtensions +{ + public static void ValidateDaprAppApiToken(this HttpContext httpContext) + { + httpContext + .RequestServices + .GetRequiredService() + .CheckDaprAppApiToken(); + } + + public static bool IsValidDaprAppApiToken(this HttpContext httpContext) + { + return httpContext + .RequestServices + .GetRequiredService() + .IsValidDaprAppApiToken(); + } + + public static string? GetDaprAppApiTokenOrNull(HttpContext httpContext) + { + return httpContext + .RequestServices + .GetRequiredService() + .GetDaprAppApiTokenOrNull(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs new file mode 100644 index 0000000000..ed5f281ea8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Mvc.Dapr; + +public interface IDaprAppApiTokenValidator +{ + void CheckDaprAppApiToken(); + bool IsValidDaprAppApiToken(); + string? GetDaprAppApiTokenOrNull(); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSize.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSize.cs index f022dacce5..452a616365 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSize.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSize.cs @@ -5,5 +5,11 @@ public enum AbpModalSize Default, Small, Large, - ExtraLarge + ExtraLarge, + Fullscreen, + FullscreenSmDown, + FullscreenMdDown, + FullscreenLgDown, + FullscreenXlDown, + FullscreenXxlDown } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSizeExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSizeExtensions.cs index 7b0de7a595..9477783557 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSizeExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalSizeExtensions.cs @@ -14,6 +14,18 @@ public static class AbpModalSizeExtensions return "modal-xl"; case AbpModalSize.Default: return ""; + case AbpModalSize.Fullscreen: + return "modal-fullscreen"; + case AbpModalSize.FullscreenSmDown: + return "modal-fullscreen-sm-down"; + case AbpModalSize.FullscreenMdDown: + return "modal-fullscreen-md-down"; + case AbpModalSize.FullscreenLgDown: + return "modal-fullscreen-lg-down"; + case AbpModalSize.FullscreenXlDown: + return "modal-fullscreen-xl-down"; + case AbpModalSize.FullscreenXxlDown: + return "modal-fullscreen-xxl-down"; default: throw new AbpException($"Unknown {nameof(AbpModalSize)}: {size}"); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs index f6b709c897..7f218e7fbf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs @@ -16,7 +16,7 @@ public class BundleFileContributor : BundleContributor { foreach (var file in Files) { - context.Files.AddIfNotContains(file); + context.Files.AddIfNotContains(x => x.Equals(file, StringComparison.OrdinalIgnoreCase), () => file); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en-GB.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en-GB.json index e93e7ef25e..0e526b3df6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en-GB.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en-GB.json @@ -1,7 +1,7 @@ { "culture": "en-GB", "texts": { - "GivenTenantIsNotExist": "Given tenant is not exist: {0}", + "GivenTenantIsNotExist": "Given tenant does not exist: {0}", "GivenTenantIsNotAvailable": "Given tenant is not available: {0}", "Tenant": "Tenant", "Switch": "switch", diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/hu.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/hu.json index 108b3520d7..0e9d2b2281 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/hu.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/hu.json @@ -1,13 +1,13 @@ { - "culture": "hu", - "texts": { - "GivenTenantIsNotExist": "Adott bérlő nem létezik: {0}", - "GivenTenantIsNotAvailable": "A megadott előfizető nem érhető el: {0}", - "Tenant": "Előfizető", - "Switch": "váltás", - "Name": "Neve", - "SwitchTenantHint": "Hagyja üresen az előfizető nevet hogy a host oldalra kapcsoljon.", - "SwitchTenant": "Előfizető váltás", - "NotSelected": "Nincs kiválasztva" - } + "culture": "hu", + "texts": { + "GivenTenantIsNotExist": "Adott bérlő nem létezik: {0}", + "GivenTenantIsNotAvailable": "A megadott előfizető nem érhető el: {0}", + "Tenant": "Előfizető", + "Switch": "váltás", + "Name": "Neve", + "SwitchTenantHint": "Hagyja üresen az előfizető nevet hogy a host oldalra kapcsoljon.", + "SwitchTenant": "Előfizető váltás", + "NotSelected": "Nincs kiválasztva" } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs index 0cbd89e91c..5eeac96fd7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs @@ -1,6 +1,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Moment; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -25,6 +26,15 @@ public class AbpAspNetCoreMvcUiPackagesModule : AbpModule options.AddLanguageFilesMapOrUpdate(BootstrapDatepickerScriptContributor.PackageName, new NameValue("zh-Hans", "zh-CN"), new NameValue("zh-Hant", "zh-TW")); + + //moment + options.AddLanguagesMapOrUpdate(MomentScriptContributor.PackageName, + new NameValue("zh-Hans", "zh-CN"), + new NameValue("zh-Hant", "zh-TW")); + + options.AddLanguageFilesMapOrUpdate(MomentScriptContributor.PackageName, + new NameValue("zh-Hans", "zh-CN"), + new NameValue("zh-Hant", "zh-TW")); //Timeago options.AddLanguageFilesMapOrUpdate(TimeagoScriptContributor.PackageName, diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDaterangepicker/BootstrapDaterangepickerScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDaterangepicker/BootstrapDaterangepickerScriptContributor.cs new file mode 100644 index 0000000000..4328f74b7a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDaterangepicker/BootstrapDaterangepickerScriptContributor.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Moment; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDaterangepicker; + +[DependsOn(typeof(JQueryScriptContributor))] +[DependsOn(typeof(MomentScriptContributor))] +public class BootstrapDaterangepickerScriptContributor : BundleContributor +{ + public const string PackageName = "bootstrap-daterangepicker"; + + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/libs/bootstrap-daterangepicker/daterangepicker.js"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDaterangepicker/BootstrapDaterangepickerStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDaterangepicker/BootstrapDaterangepickerStyleContributor.cs new file mode 100644 index 0000000000..f1211fb887 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDaterangepicker/BootstrapDaterangepickerStyleContributor.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDaterangepicker; + +public class BootstrapDaterangepickerStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/libs/bootstrap-daterangepicker/daterangepicker.css"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Moment/MomentScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Moment/MomentScriptContributor.cs new file mode 100644 index 0000000000..a58340902c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Moment/MomentScriptContributor.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Moment; + +public class MomentScriptContributor : BundleContributor +{ + public const string PackageName = "moment"; + + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/libs/moment/moment.min.js"); + } + + public override void ConfigureDynamicResources(BundleConfigurationContext context) + { + var fileName = context.LazyServiceProvider.LazyGetRequiredService>().Value.GetCurrentUICultureLanguageFilesMap(PackageName); + var filePath = $"/libs/moment/locale/{fileName}.js"; + if (context.FileProvider.GetFileInfo(filePath).Exists) + { + context.Files.AddIfNotContains(filePath); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs index 45185b525a..2127b3f43a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs @@ -1,6 +1,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap; using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDaterangepicker; using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs5; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryForm; @@ -29,7 +30,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; typeof(MalihuCustomScrollbarPluginScriptBundleContributor), typeof(LuxonScriptContributor), typeof(TimeagoScriptContributor), - typeof(BootstrapDatepickerScriptContributor) + typeof(BootstrapDatepickerScriptContributor), + typeof(BootstrapDaterangepickerScriptContributor) )] public class SharedThemeGlobalScriptContributor : BundleContributor { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs index dffd1717ed..25669a33da 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs @@ -1,6 +1,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap; using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDaterangepicker; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Core; using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs5; using Volo.Abp.AspNetCore.Mvc.UI.Packages.FontAwesome; @@ -19,7 +20,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; typeof(Select2StyleContributor), typeof(MalihuCustomScrollbarPluginStyleBundleContributor), typeof(DatatablesNetBs5StyleContributor), - typeof(BootstrapDatepickerStyleContributor) + typeof(BootstrapDatepickerStyleContributor), + typeof(BootstrapDaterangepickerStyleContributor) )] public class SharedThemeGlobalStyleContributor : BundleContributor { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs index 097fad5f40..56ae47f0c8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button; @@ -15,7 +16,7 @@ public class AbpPageToolbarButtonViewComponent : AbpViewComponent StringLocalizerFactory = stringLocalizerFactory; } - public IViewComponentResult Invoke( + public async Task InvokeAsync( ILocalizableString text, string name, string icon, @@ -31,11 +32,11 @@ public class AbpPageToolbarButtonViewComponent : AbpViewComponent return View( "~/Pages/Shared/Components/AbpPageToolbar/Button/Default.cshtml", new AbpPageToolbarButtonViewModel( - text.Localize(StringLocalizerFactory), + await text.LocalizeAsync(StringLocalizerFactory), name, icon, id, - busyText?.Localize(StringLocalizerFactory), + busyText == null ? null : await busyText.LocalizeAsync(StringLocalizerFactory), iconType, type, size, diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index f87b7c27b1..47e89dcaae 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -36,7 +36,7 @@ var abp = abp || {}; var $a = $(''); if (fieldItem.displayNameHtml) { - $a.html(fieldItem.text); + $a.html(abp.utils.isFunction(fieldItem.text) ? fieldItem.text(record, tableInstance) : fieldItem.text); } else { if (fieldItem.icon !== undefined && fieldItem.icon) { @@ -45,7 +45,7 @@ var abp = abp || {}; $a.append($("").addClass(fieldItem.iconClass + " me-1")); } - $a.append(htmlEncode(fieldItem.text)); + $a.append(htmlEncode(abp.utils.isFunction(fieldItem.text) ? fieldItem.text(record, tableInstance) : fieldItem.text)); } if (fieldItem.action) { @@ -81,14 +81,14 @@ var abp = abp || {}; var $button = $(''); if (firstItem.displayNameHtml) { - $button.html(firstItem.text); + $button.html(abp.utils.isFunction(firstItem.text) ? firstItem.text(record, tableInstance) : firstItem.text); } else { if (firstItem.icon !== undefined && firstItem.icon) { $button.append($("").addClass("fa fa-" + firstItem.icon + " me-1")); } else if (firstItem.iconClass) { $button.append($("").addClass(firstItem.iconClass + " me-1")); } - $button.append(htmlEncode(firstItem.text)); + $button.append(htmlEncode(abp.utils.isFunction(firstItem.text) ? firstItem.text(record, tableInstance) : firstItem.text)); } if (firstItem.enabled && !firstItem.enabled({ record: record, table: tableInstance })) { @@ -132,7 +132,7 @@ var abp = abp || {}; } if (field.text) { - $dropdownButton.append(htmlEncode(field.text)); + $dropdownButton.append(htmlEncode(abp.utils.isFunction(field.text) ? field.text(record, tableInstance) : field.text)); } else { $dropdownButton.append(htmlEncode(localize("DatatableActionDropdownDefaultText"))); } @@ -272,7 +272,7 @@ var abp = abp || {}; } }); - //Delay for processing indicator + //Delay for processing indicator var defaultDelayForProcessingIndicator = 500; var _existingDefaultFnPreDrawCallback = $.fn.dataTable.defaults.fnPreDrawCallback; $.extend(true, diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js index d62dba1809..78e98071ea 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js @@ -99,7 +99,7 @@ // add unchecked checkboxes because serializeArray ignores them $(this).find("input[type=checkbox]").each(function () { if (!$(this).is(':checked')) { - data.push({name: this.name, value: this.checked}); + data.push({ name: this.name, value: this.checked }); } }); @@ -118,26 +118,7 @@ }); //map to object - - var getVarName = function (v) { - return v.toString().replace(/\(\)\s?=\>\s?/, ''); - } - - var getNames = function (index, variable) { - var name = ''; - for (var i = 0; i <= index; i++) { - if (i == 0) { - name = variable + '[' + i + ']' - } else { - name += '][' + variable + '[' + i + ']' - } - } - return name; - } - var obj = {}; - var objName = getVarName(() => obj); - if (camelCase !== undefined ? camelCase : true) { data.forEach(function (d) { d.name = toCamelCase(d.name); @@ -146,18 +127,19 @@ data.map(function (x) { var names = x.name.split("."); - var xName = getVarName(() => x); - var namesName = getVarName(() => names); + for (var i = 0; i < names.length; i++) { + var o = obj; + for (var j = 0; j <= i; j++) { + if ($.isEmptyObject(o[names[j]])) { + o[names[j]] = {}; + } - var i = obj ? 0 : 1; - for (i = 0; i < names.length; i++) { - if (eval('!' + objName + '[' + getNames(i, '' + namesName + '') + ']')) { - eval('' + objName + '[' + getNames(i, '' + namesName + '') + '] = {}'); - } - } + if (i == names.length - 1 && j == i) { + o[names[j]] = x.value; + } - if ($.isEmptyObject(eval('' + objName + '[' + getNames(names.length - 1, '' + namesName + '') + ']'))) { - eval('' + objName + '[' + getNames(names.length - 1, '' + namesName + '') + '] = ' + xName + '.value'); + o = o[names[j]] + } } }); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml index 61e9afbf6e..34536ec373 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml @@ -1,5 +1,4 @@ -@using Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook -@model LayoutHookViewModel +@model Volo.Abp.Ui.LayoutHooks.LayoutHookViewModel @if (Model.Hooks.Any()) { foreach (var hook in Model.Hooks) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs index 45eaa2b6a3..5bd18e4c31 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using Volo.Abp.Ui.LayoutHooks; namespace Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs index 4bc4673860..24c9b071bd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs @@ -43,4 +43,14 @@ public static class ActionDescriptorExtensions { return actionDescriptor is PageActionDescriptor; } + + public static PageActionDescriptor AsPageAction(this ActionDescriptor actionDescriptor) + { + if (!actionDescriptor.IsPageAction()) + { + throw new AbpException($"{nameof(actionDescriptor)} should be type of {typeof(PageActionDescriptor).AssemblyQualifiedName}"); + } + + return actionDescriptor as PageActionDescriptor; + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index 528a04583d..0deae4b5b3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -17,6 +17,7 @@ using Volo.Abp.Data; using Volo.Abp.Features; using Volo.Abp.GlobalFeatures; using Volo.Abp.Localization; +using Volo.Abp.Localization.External; using Volo.Abp.MultiTenancy; using Volo.Abp.Settings; using Volo.Abp.Timing; @@ -82,7 +83,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp _multiTenancyOptions = multiTenancyOptions.Value; } - public virtual async Task GetAsync() + public virtual async Task GetAsync(ApplicationConfigurationRequestOptions options) { //TODO: Optimize & cache..? @@ -93,7 +94,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp Auth = await GetAuthConfigAsync(), Features = await GetFeaturesConfigAsync(), GlobalFeatures = await GetGlobalFeaturesConfigAsync(), - Localization = await GetLocalizationConfigAsync(), + Localization = await GetLocalizationConfigAsync(options), CurrentUser = GetCurrentUser(), Setting = await GetSettingConfigAsync(), MultiTenancy = GetMultiTenancy(), @@ -171,7 +172,8 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp foreach (var policyName in policyNames) { - if (await _defaultAuthorizationPolicyProvider.GetPolicyAsync(policyName) == null && _permissionDefinitionManager.GetOrNull(policyName) != null) + if (await _defaultAuthorizationPolicyProvider.GetPolicyAsync(policyName) == null && + await _permissionDefinitionManager.GetOrNullAsync(policyName) != null) { abpPolicyNames.Add(policyName); } @@ -204,26 +206,42 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp return authConfig; } - protected virtual async Task GetLocalizationConfigAsync() + protected virtual async Task GetLocalizationConfigAsync( + ApplicationConfigurationRequestOptions options) { var localizationConfig = new ApplicationLocalizationConfigurationDto(); localizationConfig.Languages.AddRange(await _languageProvider.GetLanguagesAsync()); - foreach (var resource in _localizationOptions.Resources.Values) + if (options.IncludeLocalizationResources) { - var dictionary = new Dictionary(); - - var localizer = _serviceProvider.GetRequiredService( - typeof(IStringLocalizer<>).MakeGenericType(resource.ResourceType) - ) as IStringLocalizer; - - foreach (var localizedString in localizer.GetAllStrings()) + var resourceNames = _localizationOptions + .Resources + .Values + .Select(x => x.ResourceName) + .Union( + await LazyServiceProvider + .LazyGetRequiredService() + .GetResourceNamesAsync() + ); + + foreach (var resourceName in resourceNames) { - dictionary[localizedString.Name] = localizedString.Value; - } + var dictionary = new Dictionary(); + + var localizer = await StringLocalizerFactory + .CreateByResourceNameOrNullAsync(resourceName); + + if (localizer != null) + { + foreach (var localizedString in await localizer.GetAllStringsAsync()) + { + dictionary[localizedString.Name] = localizedString.Value; + } + } - localizationConfig.Values[resource.ResourceName] = dictionary; + localizationConfig.Values[resourceName] = dictionary; + } } localizationConfig.CurrentCulture = GetCurrentCultureInfo(); @@ -290,7 +308,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp { var result = new ApplicationFeatureConfigurationDto(); - foreach (var featureDefinition in _featureDefinitionManager.GetAll()) + foreach (var featureDefinition in await _featureDefinitionManager.GetAllAsync()) { if (!featureDefinition.IsVisibleToClients) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs index 6c410a8a7b..427aaf65a6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs @@ -21,9 +21,10 @@ public class AbpApplicationConfigurationController : AbpControllerBase, IAbpAppl } [HttpGet] - public virtual async Task GetAsync() + public virtual async Task GetAsync( + ApplicationConfigurationRequestOptions options) { _antiForgeryManager.SetCookie(); - return await _applicationConfigurationAppService.GetAsync(); + return await _applicationConfigurationAppService.GetAsync(options); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs index 92a4bdef0d..7fafee261c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs @@ -17,14 +17,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; [ApiExplorerSettings(IgnoreApi = true)] public class AbpApplicationConfigurationScriptController : AbpController { - private readonly IAbpApplicationConfigurationAppService _configurationAppService; + private readonly AbpApplicationConfigurationAppService _configurationAppService; private readonly IJsonSerializer _jsonSerializer; private readonly AbpAspNetCoreMvcOptions _options; private readonly IJavascriptMinifier _javascriptMinifier; private readonly IAbpAntiForgeryManager _antiForgeryManager; public AbpApplicationConfigurationScriptController( - IAbpApplicationConfigurationAppService configurationAppService, + AbpApplicationConfigurationAppService configurationAppService, IJsonSerializer jsonSerializer, IOptions options, IJavascriptMinifier javascriptMinifier, @@ -41,7 +41,13 @@ public class AbpApplicationConfigurationScriptController : AbpController [Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)] public async Task Get() { - var script = CreateAbpExtendScript(await _configurationAppService.GetAsync()); + var script = CreateAbpExtendScript( + await _configurationAppService.GetAsync( + new ApplicationConfigurationRequestOptions { + IncludeLocalizationResources = false + } + ) + ); _antiForgeryManager.SetCookie(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs new file mode 100644 index 0000000000..48d7f949c3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using Volo.Abp.Application.Services; +using Volo.Abp.Localization; +using Volo.Abp.Localization.External; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +public class AbpApplicationLocalizationAppService : + ApplicationService, + IAbpApplicationLocalizationAppService +{ + protected IExternalLocalizationStore ExternalLocalizationStore { get; } + protected AbpLocalizationOptions LocalizationOptions { get; } + + public AbpApplicationLocalizationAppService( + IExternalLocalizationStore externalLocalizationStore, + IOptions localizationOptions) + { + ExternalLocalizationStore = externalLocalizationStore; + LocalizationOptions = localizationOptions.Value; + } + + public async Task GetAsync(ApplicationLocalizationRequestDto input) + { + using (CultureHelper.Use(input.CultureName)) + { + var resources = LocalizationOptions + .Resources + .Values + .Union( + await ExternalLocalizationStore.GetResourcesAsync() + ).ToArray(); + + var localizationConfig = new ApplicationLocalizationDto { + Resources = new Dictionary(resources.Length) + }; + + foreach (var resource in resources) + { + var dictionary = new Dictionary(); + var localizer = await StringLocalizerFactory.CreateByResourceNameOrNullAsync(resource.ResourceName); + if (localizer != null) + { + Dictionary staticLocalizedStrings = null; + + if (input.OnlyDynamics) + { + staticLocalizedStrings = (await localizer.GetAllStringsAsync( + includeParentCultures: true, + includeBaseLocalizers: false, + includeDynamicContributors: false + )).ToDictionary(x => x.Name); + } + + var localizedStringsWithDynamics = await localizer.GetAllStringsAsync( + includeParentCultures: true, + includeBaseLocalizers: false, + includeDynamicContributors: true + ); + + foreach (var localizedString in localizedStringsWithDynamics) + { + if (input.OnlyDynamics) + { + var staticLocalizedString = staticLocalizedStrings.GetOrDefault(localizedString.Name); + if (staticLocalizedString != null && + localizedString.Value == staticLocalizedString.Value) + { + continue; + } + } + + dictionary[localizedString.Name] = localizedString.Value; + } + } + + localizationConfig.Resources[resource.ResourceName] = + new ApplicationLocalizationResourceDto { + Texts = dictionary, + BaseResources = resource.BaseResourceNames.ToArray() + }; + } + + return localizationConfig; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs new file mode 100644 index 0000000000..a056746022 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +[Area("abp")] +[RemoteService(Name = "abp")] +[Route("api/abp/application-localization")] +public class AbpApplicationLocalizationController: AbpControllerBase, IAbpApplicationLocalizationAppService +{ + private readonly IAbpApplicationLocalizationAppService _localizationAppService; + + public AbpApplicationLocalizationController(IAbpApplicationLocalizationAppService localizationAppService) + { + _localizationAppService = localizationAppService; + } + + [HttpGet] + public virtual async Task GetAsync(ApplicationLocalizationRequestDto input) + { + return await _localizationAppService.GetAsync(input); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs index 8acfcefd3d..0f9b05830d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs @@ -204,9 +204,7 @@ public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoServic { return new LocalizableStringDto( localizableStringInstance.Name, - localizableStringInstance.ResourceType != null - ? LocalizationResourceNameAttribute.GetName(localizableStringInstance.ResourceType) - : null + localizableStringInstance.ResourceName ); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs index 47ed4c4614..82105693ec 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs @@ -41,8 +41,12 @@ public class AbpAuditActionFilter : IAsyncActionFilter, ITransientDependency finally { stopwatch.Stop(); - auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); - auditLog.Actions.Add(auditLogAction); + + if (auditLogAction != null) + { + auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); + auditLog.Actions.Add(auditLogAction); + } } } } @@ -70,18 +74,41 @@ public class AbpAuditActionFilter : IAsyncActionFilter, ITransientDependency } var auditingHelper = context.GetRequiredService(); - if (!auditingHelper.ShouldSaveAudit(context.ActionDescriptor.GetMethodInfo(), true)) + if (!auditingHelper.ShouldSaveAudit( + context.ActionDescriptor.GetMethodInfo(), + defaultValue: GetDefaultAuditBehavior(options, context.ActionDescriptor))) { return false; } auditLog = auditLogScope.Log; - auditLogAction = auditingHelper.CreateAuditLogAction( - auditLog, - context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(), - context.ActionDescriptor.AsControllerActionDescriptor().MethodInfo, - context.ActionArguments - ); + + if (!options.DisableLogActionInfo) + { + auditLogAction = auditingHelper.CreateAuditLogAction( + auditLog, + context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(), + context.ActionDescriptor.AsControllerActionDescriptor().MethodInfo, + context.ActionArguments + ); + } + + return true; + } + + private static bool GetDefaultAuditBehavior( + AbpAuditingOptions abpAuditingOptions, + ActionDescriptor actionDescriptor) + { + if (!abpAuditingOptions.IsEnabledForIntegrationServices && + IntegrationServiceAttribute.IsDefinedOrInherited( + actionDescriptor + .AsControllerActionDescriptor() + .ControllerTypeInfo) + ) + { + return false; + } return true; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs index 2abfd477db..51137d3812 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs @@ -46,8 +46,12 @@ public class AbpAuditPageFilter : IAsyncPageFilter, ITransientDependency finally { stopwatch.Stop(); - auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); - auditLog.Actions.Add(auditLogAction); + + if (auditLogAction != null) + { + auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); + auditLog.Actions.Add(auditLogAction); + } } } } @@ -75,18 +79,22 @@ public class AbpAuditPageFilter : IAsyncPageFilter, ITransientDependency } var auditingHelper = context.GetRequiredService(); - if (!auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, true)) + if (!auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, defaultValue: true)) { return false; } auditLog = auditLogScope.Log; - auditLogAction = auditingHelper.CreateAuditLogAction( - auditLog, - context.HandlerMethod.MethodInfo.DeclaringType, - context.HandlerMethod.MethodInfo, - context.HandlerArguments - ); + + if (!options.DisableLogActionInfo) + { + auditLogAction = auditingHelper.CreateAuditLogAction( + auditLog, + context.HandlerMethod.MethodInfo.DeclaringType, + context.HandlerMethod.MethodInfo, + context.HandlerArguments + ); + } return true; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs index d1b0eef6ab..bb059a6674 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Versioning; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reflection; using Volo.Abp.Reflection; @@ -16,7 +17,7 @@ public class ConventionalControllerSetting public Assembly Assembly { get; } [NotNull] - public HashSet ControllerTypes { get; } //TODO: Internal? + internal HashSet ControllerTypes { get; } /// /// Set true to use the old style URL path style. @@ -47,6 +48,11 @@ public class ConventionalControllerSetting [CanBeNull] public Func TypePredicate { get; set; } + /// + /// Default value: All. + /// + public ApplicationServiceTypes ApplicationServiceTypes { get; set; } = ApplicationServiceTypes.All; + [CanBeNull] public Action ControllerModelConfigurer { get; set; } @@ -77,6 +83,7 @@ public class ConventionalControllerSetting { var types = Assembly.GetTypes() .Where(IsRemoteService) + .Where(IsPreferredApplicationServiceType) .WhereIf(TypePredicate != null, TypePredicate); foreach (var type in types) @@ -84,6 +91,11 @@ public class ConventionalControllerSetting ControllerTypes.Add(type); } } + + public IReadOnlyList GetControllerTypes() + { + return ControllerTypes.ToImmutableList(); + } private static bool IsRemoteService(Type type) { @@ -105,4 +117,19 @@ public class ConventionalControllerSetting return false; } -} + + private bool IsPreferredApplicationServiceType(Type type) + { + if (ApplicationServiceTypes == ApplicationServiceTypes.ApplicationServices) + { + return !IntegrationServiceAttribute.IsDefinedOrInherited(type); + } + + if (ApplicationServiceTypes == ApplicationServiceTypes.IntegrationServices) + { + return IntegrationServiceAttribute.IsDefinedOrInherited(type); + } + + return true; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs index 3891827c0f..f9c31467b5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs @@ -26,9 +26,11 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { - var controllerNameInUrl = NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration); + var apiRoutePrefix = GetApiRoutePrefix(action, configuration); + var controllerNameInUrl = + NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration); - var url = $"api/{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}"; + var url = $"{apiRoutePrefix}/{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}"; //Add {id} path if needed var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); @@ -69,6 +71,16 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep return url; } + protected virtual string GetApiRoutePrefix(ActionModel actionModel, ConventionalControllerSetting configuration) + { + if (IntegrationServiceAttribute.IsDefinedOrInherited(actionModel.Controller.ControllerType)) + { + return AbpAspNetCoreConsts.DefaultIntegrationServiceApiPrefix; + } + + return AbpAspNetCoreConsts.DefaultApiPrefix; + } + protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { @@ -108,7 +120,8 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep ); } - protected virtual string NormalizeControllerNameCase(string controllerName, [CanBeNull] ConventionalControllerSetting configuration) + protected virtual string NormalizeControllerNameCase(string controllerName, + [CanBeNull] ConventionalControllerSetting configuration) { if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle) { @@ -120,7 +133,8 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep } } - protected virtual string NormalizeActionNameCase(string actionName, [CanBeNull] ConventionalControllerSetting configuration) + protected virtual string NormalizeActionNameCase(string actionName, + [CanBeNull] ConventionalControllerSetting configuration) { if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle) { @@ -132,13 +146,15 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep } } - protected virtual string NormalizeIdPropertyNameCase(PropertyInfo property, [CanBeNull] ConventionalControllerSetting configuration) + protected virtual string NormalizeIdPropertyNameCase(PropertyInfo property, + [CanBeNull] ConventionalControllerSetting configuration) { return property.Name; } - protected virtual string NormalizeSecondaryIdNameCase(ParameterModel secondaryId, [CanBeNull] ConventionalControllerSetting configuration) + protected virtual string NormalizeSecondaryIdNameCase(ParameterModel secondaryId, + [CanBeNull] ConventionalControllerSetting configuration) { return secondaryId.ParameterName; } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs new file mode 100644 index 0000000000..d8da85f871 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.Auditing; +using Volo.Abp.Http; +using Volo.Abp.Json; +using Volo.Abp.Localization; +using Volo.Abp.Minify.Scripts; + +namespace Volo.Abp.AspNetCore.Mvc.Localization; + +[Area("Abp")] +[Route("Abp/ApplicationLocalizationScript")] +[DisableAuditing] +[RemoteService(false)] +[ApiExplorerSettings(IgnoreApi = true)] +public class AbpApplicationLocalizationScriptController : AbpController +{ + protected AbpApplicationLocalizationAppService LocalizationAppService { get; } + protected AbpAspNetCoreMvcOptions Options { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IJavascriptMinifier JavascriptMinifier { get; } + + public AbpApplicationLocalizationScriptController( + AbpApplicationLocalizationAppService localizationAppService, + IOptions options, + IJsonSerializer jsonSerializer, + IJavascriptMinifier javascriptMinifier) + { + LocalizationAppService = localizationAppService; + JsonSerializer = jsonSerializer; + JavascriptMinifier = javascriptMinifier; + Options = options.Value; + } + + [HttpGet] + [Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)] + public async Task GetAsync(ApplicationLocalizationRequestDto input) + { + var script = CreateScript( + await LocalizationAppService.GetAsync(input) + ); + + return Content( + Options.MinifyGeneratedScript == true + ? JavascriptMinifier.Minify(script) + : script, + MimeTypes.Application.Javascript + ); + } + + private string CreateScript(ApplicationLocalizationDto localizationDto) + { + var script = new StringBuilder(); + + script.AppendLine("(function(){"); + script.AppendLine(); + script.AppendLine( + $"$.extend(true, abp.localization, {JsonSerializer.Serialize(localizationDto, indented: true)})"); + script.AppendLine(); + script.Append("})();"); + + return script.ToString(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs index dc44f12124..8a4f82953a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs @@ -1,14 +1,17 @@ using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc; using System; +using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.RequestLocalization; +using Volo.Abp.Auditing; using Volo.Abp.Localization; namespace Volo.Abp.AspNetCore.Mvc.Localization; [Area("Abp")] [Route("Abp/Languages/[action]")] +[DisableAuditing] [RemoteService(false)] [ApiExplorerSettings(IgnoreApi = true)] public class AbpLanguagesController : AbpController @@ -28,6 +31,11 @@ public class AbpLanguagesController : AbpController throw new AbpException("The selected culture is not valid! Make sure you enter a valid culture code."); } + if (!CultureHelper.IsValidCultureCode(uiCulture)) + { + throw new AbpException("The selected uiCulture is not valid! Make sure you enter a valid culture code."); + } + AbpRequestCultureCookieHelper.SetCultureCookie( HttpContext, new RequestCulture(culture, uiCulture) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs index 20ac2e4cb8..3501882e3a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs @@ -5,7 +5,7 @@ using Volo.Abp.Http.ProxyScripting.Generators.JQuery; namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting; -public class ServiceProxyGenerationModel //: TODO: IShouldNormalize +public class ServiceProxyGenerationModel { public string Type { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs index 3f5e9ad4b8..aa3ca0621f 100644 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs @@ -79,7 +79,7 @@ public class AbpAspNetCoreAsyncIntegratedTestBase /// The type of the controller. protected virtual string GetUrl() { - return "/" + typeof(TController).Name.RemovePostFix("Controller", "AppService", "ApplicationService", "Service"); + return "/" + typeof(TController).Name.RemovePostFix("Controller", "AppService", "ApplicationService", "IntService", "IntegrationService", "Service"); } /// diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs index f3e60979bb..20afedf680 100644 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs @@ -59,7 +59,7 @@ public abstract class AbpAspNetCoreIntegratedTestBase : AbpTestBaseWit /// The type of the controller. protected virtual string GetUrl() { - return "/" + typeof(TController).Name.RemovePostFix("Controller", "AppService", "ApplicationService", "Service"); + return "/" + typeof(TController).Name.RemovePostFix("Controller", "AppService", "ApplicationService", "IntService", "IntegrationService", "Service"); } /// diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs index 1324df0865..6fd02a0470 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs @@ -27,7 +27,9 @@ public class AbpRequestLocalizationMiddleware : IMiddleware, ITransientDependenc { var middleware = new RequestLocalizationMiddleware( next, - new OptionsWrapper(await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()), + new OptionsWrapper( + await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync() + ), _loggerFactory ); diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs new file mode 100644 index 0000000000..dff987b6b3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.AspNetCore; + +public static class AbpAspNetCoreConsts +{ + public const string DefaultApiPrefix = "api"; + public const string DefaultIntegrationServiceApiPrefix = "integration-api"; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingOptions.cs index 7977205096..693945e903 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingOptions.cs @@ -10,5 +10,5 @@ public class AbpAspNetCoreAuditingOptions /// will be disabled for URLs /// starting with an ignored URL. /// - public List IgnoredUrls { get; } = new List(); + public List IgnoredUrls { get; } = new(); } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index 9cae64501a..83deaef26a 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -94,8 +94,23 @@ public class AbpAuditingMiddleware : IMiddleware, ITransientDependency private bool IsIgnoredUrl(HttpContext context) { - return context.Request.Path.Value != null && - AspNetCoreAuditingOptions.IgnoredUrls.Any(x => context.Request.Path.Value.StartsWith(x)); + if (context.Request.Path.Value == null) + { + return false; + } + + if (!AuditingOptions.IsEnabledForIntegrationServices && + context.Request.Path.Value.StartsWith($"/{AbpAspNetCoreConsts.DefaultIntegrationServiceApiPrefix}/")) + { + return true; + } + + if (AspNetCoreAuditingOptions.IgnoredUrls.Any(x => context.Request.Path.Value.StartsWith(x))) + { + return true; + } + + return false; } private async Task ShouldWriteAuditLogAsync(AuditLogInfo auditLogInfo, HttpContext httpContext, bool hasError) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs index dd9e6cfe62..c0c56053d3 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs @@ -1,11 +1,14 @@ using System; +using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.WebClientInfo; using Volo.Abp.Auditing; using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; namespace Volo.Abp.AspNetCore.Auditing; @@ -57,28 +60,46 @@ public class AspNetCoreAuditLogContributor : AuditLogContributor, ITransientDepe public override void PostContribute(AuditLogContributionContext context) { + if (context.AuditInfo.HttpStatusCode != null) + { + return; + } + var httpContext = context.ServiceProvider.GetRequiredService().HttpContext; if (httpContext == null) { return; } - if (context.AuditInfo.HttpStatusCode == null) + if (context.AuditInfo.Exceptions.Any()) { - context.AuditInfo.HttpStatusCode = httpContext.Response.StatusCode; + var httpExceptionStatusCodeFinder = context.ServiceProvider.GetRequiredService(); + foreach (var auditInfoException in context.AuditInfo.Exceptions) + { + var statusCode = httpExceptionStatusCodeFinder.GetStatusCode(httpContext, auditInfoException); + context.AuditInfo.HttpStatusCode = (int) statusCode; + } + + if (context.AuditInfo.HttpStatusCode != null) + { + return; + } } + + context.AuditInfo.HttpStatusCode = httpContext.Response.StatusCode; } protected virtual string BuildUrl(HttpContext httpContext) { //TODO: Add options to include/exclude query, schema and host - var uriBuilder = new UriBuilder(); - - uriBuilder.Scheme = httpContext.Request.Scheme; - uriBuilder.Host = httpContext.Request.Host.Host; - uriBuilder.Path = httpContext.Request.Path.ToString(); - uriBuilder.Query = httpContext.Request.QueryString.ToString(); + var uriBuilder = new UriBuilder + { + Scheme = httpContext.Request.Scheme, + Host = httpContext.Request.Host.Host, + Path = httpContext.Request.Path.ToString(), + Query = httpContext.Request.QueryString.ToString() + }; return uriBuilder.Uri.AbsolutePath; } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs index 7eaa8b55e8..583b12efcb 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs @@ -19,6 +19,9 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency /*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a ,