diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs b/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs index b3ebccd72f..fe24f4d4ec 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs @@ -84,4 +84,4 @@ namespace AbpIoLocalization }); } } -} \ No newline at end of file +} diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/fi.json index a7a52d3811..79dd25aa26 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/fi.json @@ -4,13 +4,14 @@ "Account": "ABP-tili - Kirjaudu ja rekisteröidy | ABP.IO", "Welcome": "Tervetuloa", "UseOneOfTheFollowingLinksToContinue": "Käytä yhtä seuraavista linkeistä jatkaaksesi", - "FrameworkHomePage": "Kehyksen kotisivu", - "FrameworkDocumentation": "Puitteet", + "FrameworkHomePage": "Frameworkin kotisivu", + "FrameworkDocumentation": "Frameworkin dokumentaatio", "OfficialBlog": "Virallinen blogi", "CommercialHomePage": "Kaupallinen kotisivu", "CommercialSupportWebSite": "Kaupallisen tuen verkkosivusto", "CommunityWebSite": "ABP-yhteisön verkkosivusto", "ManageAccount": "Oma tili | ABP.IO", - "ManageYourAccount": "Hallitse tiliäsi" + "ManageYourProfile": "Hallinnoi profiiliasi", + "ReturnToApplication": "Palaa sovellukseen" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json index a773568a31..2bf4bb3837 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json @@ -11,6 +11,7 @@ "CommercialSupportWebSite": "商业版支持网站", "CommunityWebSite": "ABP社区网站", "ManageAccount": "我的帐户 | ABP.IO", - "ManageYourProfile": "管理您的个人资料" + "ManageYourProfile": "管理您的个人资料", + "ReturnToApplication": "返回应用" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json index 6e9253891e..9b95078f66 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json @@ -348,6 +348,11 @@ "Volo.AbpIo.Commercial:030008": "يمكن تحديد تاريخ الشراء فقط عند شراء الحالة!", "Volo.AbpIo.Commercial:030009": "لم يتم العثور على المستخدم!", "Volo.AbpIo.Commercial:030010": "لشراء الترخيص التجريبي ، تحتاج أولاً إلى تنشيط الترخيص التجريبي الخاص بك!", - "Volo.AbpIo.Commercial:030011": "لا يمكنك حذف ترخيص تجريبي عند شرائه!" + "Volo.AbpIo.Commercial:030011": "لا يمكنك حذف ترخيص تجريبي عند شرائه!", + "MoveWaitList": "الانتقال إلى قائمة الانتظار", + "CommunityLinkTitle": "افتح على موقع المجتمع", + "CommunityLink": "رابط المجتمع", + "ReloadFromSource": "إعادة التحميل من المصدر", + "ReloadFromSourceConfirmationMessage": "سيتم تحديث هذه المشاركة من \"{0}\". هل تريد الاستمرار؟" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/cs.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/cs.json index c9b4bacc6d..b594121f0f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/cs.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/cs.json @@ -348,6 +348,11 @@ "Volo.AbpIo.Commercial:030008": "Datum nákupu lze nastavit pouze ve stavu Zakoupeno!", "Volo.AbpIo.Commercial:030009": "Uživatel nenalezen!", "Volo.AbpIo.Commercial:030010": "Chcete-li zakoupit zkušební licenci, musíte nejprve aktivovat zkušební licenci!", - "Volo.AbpIo.Commercial:030011": "Po zakoupení zkušební licence nelze odstranit!" + "Volo.AbpIo.Commercial:030011": "Po zakoupení zkušební licence nelze odstranit!", + "MoveWaitList": "Přejít na čekací listinu", + "CommunityLinkTitle": "Otevřít na webu komunity", + "CommunityLink": "Odkaz na komunitu", + "ReloadFromSource": "Znovu načíst ze zdroje", + "ReloadFromSourceConfirmationMessage": "Tento příspěvek bude obnoven z „{0}“. Chceš pokračovat?" } } \ 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 0caa284764..9f4fe44349 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -421,7 +421,7 @@ "ContentCacheSlidingExpirationByDay": "Content Cache Sliding Expiration By Day", "MaxDaysForCaching": "Max Days For Caching", "Enabled": "Enabled", - "Menu:NugetPackagesContentCache": "NuGet Packages Content Cache", + "Menu:NugetPackagesContentCache": "NuGet Cache", "NugetPackagesContentCache": "NuGet Content Cache", "SlidingExpritionByDayInfo": "Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. This will not extend the entry lifetime beyond the absolute expiration.", "MaxDaysForCachingInfo": "Gets or sets an absolute expiration time, relative to now.", @@ -452,6 +452,11 @@ "WhoWeAreItem": "Who We Are Item", "FieldIsRequired": "{0} is required.", "FieldIsNotValid": " {0} is not valid.", - "InterestedLicenseType": "Interested License Type" + "InterestedLicenseType": "Interested License Type", + "MoveWaitList": "Move to wait list", + "CommunityLinkTitle": "Open on the community website", + "CommunityLink": "Community Link", + "ReloadFromSource": "Reload From the Source", + "ReloadFromSourceConfirmationMessage": "This post will be refreshed from \"{0}\". Do you want to continue?" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json index 6f43725360..3248ec9863 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json @@ -6,22 +6,22 @@ "Permission:DiscountRequests": "Alennushakemukset", "Permission:DiscountManage": "Hallitse alennuspyyntöjä", "Permission:Disable": "Poista käytöstä", - "Permission:Enable": "ota käyttöön", + "Permission:Enable": "Ota käyttöön", "Permission:EnableSendEmail": "Ota Lähetä sähköposti käyttöön", - "Permission:SendEmail": "Lähettää sähköpostia", + "Permission:SendEmail": "Lähetä sähköpostia", "Permission:NpmPackages": "NPM-paketit", "Permission:NugetPackages": "Nuget-paketit", "Permission:Maintenance": "Huolto", - "Permission:Maintain": "Ylläpitää", + "Permission:Maintain": "Ylläpito", "Permission:ClearCaches": "Tyhjennä välimuistit", "Permission:Modules": "Moduulit", "Permission:Packages": "Paketit", - "Permission:Edit": "Muokata", - "Permission:Delete": "Poistaa", - "Permission:Create": "Luoda", + "Permission:Edit": "Muokkaus", + "Permission:Delete": "Poisto", + "Permission:Create": "Luonti", "Permission:Accounting": "Kirjanpito", - "Permission:Accounting:Quotation": "Tarjous", - "Permission:Accounting:Invoice": "Lasku", + "Permission:Accounting:Quotation": "Tarjoukset", + "Permission:Accounting:Invoice": "Laskutus", "Menu:Organizations": "Organisaatiot", "Menu:Accounting": "Kirjanpito", "Menu:Packages": "Paketit", @@ -52,9 +52,9 @@ "NugetPackageTarget.Web": "Web", "NugetPackageTarget.EntityFrameworkCore": "PoistaAllEntityFramework Core", "NugetPackageTarget.MongoDB": "MongoDB", - "Edit": "Muokata", - "Delete": "Poistaa", - "Refresh": "virkistää", + "Edit": "Muokkaa", + "Delete": "Poista", + "Refresh": "Virkistä", "NpmPackages": "NPM-paketit", "NugetPackages": "Nuget-paketit", "NpmPackageCount": "NPM-pakettimäärä", @@ -87,7 +87,7 @@ "Email": "Sähköposti", "Developers": "Kehittäjät", "AddDeveloper": "Lisää kehittäjä", - "Create": "Luoda", + "Create": "Luo", "UserNotFound": "Käyttäjää ei löydy", "{0}WillBeRemovedFromDevelopers": "{0} Poistetaanko kehittäjiltä, vahvistatko?", "{0}WillBeRemovedFromOwners": "{0} Poistetaanko omistajilta, vahvistatko?", @@ -153,7 +153,7 @@ "DiscountRequests": "Alennuspyyntö", "Copylink": "Kopioi linkki", "Disable": "Poista käytöstä", - "Enable": "ota käyttöön", + "Enable": "Ota käyttöön", "EnableSendEmail": "Ota Lähetä sähköposti käyttöön", "SendEmail": "Lähettää sähköpostia", "SuccessfullyDisabled": "Poistettu onnistuneesti käytöstä", @@ -168,12 +168,12 @@ "TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCountin on oltava suurempi kuin RemainingQuestionCount!", "QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount ja RemainingQuestionCount on oltava nolla tai suurempi kuin nolla!", "UnlimitedQuestionCount": "Rajoittamaton kysymysten määrä", - "Notes": "Huomautuksia", + "Notes": "Huomautukset", "Menu:Community": "Yhteisö", "Menu:Posts": "Artikkelit", "Wait": "Odota", - "Approve": "Hyväksyä", - "Reject": "Hylätä", + "Approve": "Hyväksy", + "Reject": "Hylkää", "Details": "Yksityiskohdat", "Url": "URL-osoite", "Title": "Otsikko", @@ -184,7 +184,7 @@ "PostHasBeenApproved": "Artikkeli on hyväksytty", "PostHasBeenRejected": "Artikkeli on hylätty", "Permission:Community": "Yhteisö", - "Permission:CommunityPost": "Artikla", + "Permission:CommunityPost": "Artikkeli", "Link": "Linkki", "Enum:ContentSource:0": "Github", "Enum:ContentSource:1": "Ulkoinen", @@ -215,24 +215,27 @@ "Gateway": "Yhdyskäytävä", "State": "Osavaltio", "FailReason": "Epäonnistunut syy", - "ReIndexAllPosts": "Reindex Kaikki viestit", + "ReIndexAllPosts": "Indeksoi uudelleen kaikki viestit", "ReIndexAllPostsConfirmationMessage": "Haluatko varmasti indeksoida kaikki viestit uudelleen?", "SuccessfullyReIndexAllPosts": "Kaikki viestit on indeksoitu uudelleen.", "Permission:FullSearch": "Koko tekstihaku", - "Menu:CliAnalytics": "Cli Analytics", + "Menu:CliAnalytics": "Cli-analytiikka", + "Menu:Reports": "Raportit", "TemplateName": "Mallin nimi", "TemplateVersion": "Malliversio", "DatabaseProvider": "Tietokannan tarjoaja", - "IsTiered": "Onko porrastettu", + "IsTiered": "Onko monikerros", "ProjectName": "Projektin nimi", "Username": "Käyttäjänimi", "Tool": "Työkalu", "Command": "Komento", "UiFramework": "Ui-kehys", "Options": "Vaihtoehdot", - "CliAnalytics": "Cli Analytics", - "Permission:CliAnalyticses": "Cli Analyticses", - "Permission:CliAnalytics": "Cli Analytics", + "CliAnalytics": "Cli-analytiikka", + "Reports": "Raportit", + "Permission:CliAnalyticses": "Cli-analytiikat", + "Permission:CliAnalytics": "Cli-analytiikka", + "Permission:Reports": "Raportit", "Search": "Hae", "ClearFilter": "Tyhjennä suodatin", "LicensePrivateKey": "Lisenssin yksityinen avain", @@ -316,14 +319,12 @@ "TrialLicenseStatusFilter": "Tila", "TrialLicenseStartDateFilter": "Aloituspäivämäärä", "TrialLicenseEndDateFilter": "Päättymispäivä", - "FirsName": "Etunimi", + "FirstName": "Etunimi", "LastName": "Sukunimi", "StartDate": "Aloituspäivämäärä", "EndDate": "Päättymispäivä", "PurchasedDate": "Ostopäivämäärä", "OrganizationDetail": "Organisaation tiedot", - "SendActivationMail": "Lähetä aktivointiviesti", - "ActivationMailSentSuccessfully": "Aktivointiviesti lähetetty onnistuneesti!", "TrialLicenseStatus": "Kokeilulisenssin tila", "TrialLicenseDetail": "Kokeilulisenssin tiedot", "AcceptsMarketingCommunications": "Markkinointiviestintä", @@ -337,17 +338,125 @@ "Expired": "Vanhentunut", "TrialLicenseDeletionWarningMessage": "Haluatko varmasti poistaa koekäyttöluvan? Kokeilulisenssi, organisaatio, tukitilit poistetaan!", "LicenseCategoryFilter": "Lisenssiluokka", - "Volo.AbpIo.Commercial:030000": "Olet jo käyttänyt kokeilujaksosi.", - "Volo.AbpIo.Commercial:030001": "Tämä organisaation nimi on jo olemassa.", - "Volo.AbpIo.Commercial:030002": "Kun kokeilukäyttöoikeus on aktivoitu, sitä ei voi asettaa pyydettyksi!", - "Volo.AbpIo.Commercial:030003": "Sellaista statusta ei ole!", - "Volo.AbpIo.Commercial:030004": "Tilaa ei voitu muuttaa odottamattoman virheen vuoksi!", - "Volo.AbpIo.Commercial:030005": "Alkamis- ja lopetuspäivämäärät voidaan päivittää, kun koekäyttölisenssi on -aktivoitu-tilassa!", - "Volo.AbpIo.Commercial:030006": "Päättymispäivän on aina oltava aloituspäivää suurempi!", - "Volo.AbpIo.Commercial:030007": "Tämä kokeiluversio on jo aktivoitu kerran!", - "Volo.AbpIo.Commercial:030008": "Ostopäivä voidaan asettaa vain, kun tila on Ostettu!", - "Volo.AbpIo.Commercial:030009": "Käyttäjää ei löydy!", - "Volo.AbpIo.Commercial:030010": "Kokeilulisenssin ostamiseksi sinun on ensin aktivoitava kokeilulisenssi!", - "Volo.AbpIo.Commercial:030011": "Kokeilulisenssiä ei voi poistaa, kun se on ostettu!" + "Permission:SendWelcomeEmail": "Lähetä tervetulosähköposti", + "SendWelcomeEmail": "Lähetä tervetulosähköposti", + "SendWelcomeEmailWarningMessage": "Haluatko varmasti lähettää tervetuloviestin organisaation jäsenille?", + "SendWelcomeEmailSuccessMessage": "Tervetulosähköposti lähetetty onnistuneesti!", + "Activate": "Aktivoi", + "ActivateTrialLicenseWarningMessage": " Kun aktivoit kokeilulisenssin, käyttäjälle lähetetään tervetulosähköposti. Haluatko aktivoida sen?", + "ActivateTrialLicenseSuccessMessage": "Aktivointi onnistui ja tervetulosähköposti lähetetty organisaation jäsenille.", + "PaymentRequestId": "Maksupyynnön tunnus", + "AdditionalDeveloperCount": "Lisäkehittäjien määrä", + "LicensePrice": "Lisenssin hinta", + "PurchaseDate": "Ostopäivä", + "IsAbpBookDownloaded": "Mastering ABP Book ladattu?", + "IsMasteringAbpBookDownloadEnabled": "ABP-kirjan lataus käytössä", + "Permission:Accounting:CustomPaymentLinkGenerator": "Mukautettu maksulinkki", + "CustomPaymentLink": "Mukautettu maksulinkki", + "Menu:CustomPaymentLink": "Mukautettu maksulinkki", + "Amount": "Määrä", + "GenerateCustomPaymentLink": "Luo mukautettu maksulinkki", + "GeneratedPaymentLink": "Luotu maksulinkki", + "CopyText": "Kopioi teksti", + "Permission:CommunityEvents": "Tapahtumat", + "Menu:Events": "Tapahtumat", + "Events": "Tapahtumat", + "EventType": "Tapahtumatyyppi", + "Number": "Määrä", + "RegistrationURL": "Rekisteröinnin URL-osoite", + "URL": "URL-osoite", + "EventDeletionConfirmationMessage": "Haluatko varmasti poistaa tämän tapahtuman?", + "Enum:EventType:0": "Yhteisön keskustelut", + "CreateAnEvent": "Luo tapahtuma", + "Permission:CommunitySpeakers": "Esiintyjät", + "CreateASpeaker": "Luo esiintyjä", + "Speakers": "Esiintyjät", + "Image": "Kuva", + "GithubURL": "Githubin URL-osoite", + "SpeakerDeletionConfirmationMessage": "Haluatko varmasti poistaa tämän esiintyjän?", + "Menu:Speakers": "Esiintyjät", + "ChooseSpeakerImage": "Valitse esiintyjän kuva...", + "SpeakerImage": "Esiintyjän kuva", + "AddSpeaker": "Lisää esiintyjä", + "ShowPurchaseItemsOfOrganizations": "Osta kohteita", + "Enum:OrganizationPurchaseState:0": "Ei toimitettu", + "Enum:OrganizationPurchaseState:1": "Toimitettu", + "PurchaseItems": "Osta kohteita", + "SuccessfullyUpdated": "Päivitetty onnistuneesti", + "SuccessfullyAdded": "Lisätty onnistuneesti", + "PurchaseState": "Ostoksen tila", + "ShowBetweenDayCount": "Näytä päivien välissä", + "PurchaseOrder": "Ostotilaus", + "ShowCreateInvoiceOfOrganization": "Luo lasku", + "ShowCreateQuotationOfOrganization": "Luo tarjous", + "BookDiscounts": "Kirja-alennukset", + "Permission:BookDiscount": "Varaa alennus", + "Menu:BookDiscounts": "Kirja-alennukset", + "BookType": "Kirjan tyyppi", + "PurchasePlatform": "Ostoalusta", + "StartTime": "Aloitusaika", + "EndTime": "Loppu aika", + "CreateABookDiscount": "Luo kirja-alennus", + "BookDiscountDeletionConfirmationMessage": "Haluatko varmasti poistaa tämän kirja-alennuksen?", + "CustomPaymentFlexSwitchDescription": "Lisenssillä", + "AllowFeatureUpgradeOnLicenseExpire": "Salli ominaisuuden päivitys lisenssin vanhentuessa", + "Deleted{0}": "[poistettu {0}]", + "Tags": "Tunnisteet", + "SetTagsInfo": "Tunnisteet on erotettava pilkuilla. Esim: CSharp, Entity Framework", + "RejectTrialLicenseWarningMessage": "Haluatko varmasti hylätä tämän kokeilulupapyynnön?", + "ExportToExcel": "Vie Exceliin", + "OverallTotalPrice": "Kokonaishinta", + "OverallDiscountPrice": "Kokonaisalennushinta", + "OverallDiscountText": "Kokonaisalennusteksti", + "SelectReport": "- Valitse Raportti -", + "NoDataAvailable": "Tietoja ei ole saatavilla", + "StatisticsOfCachedContents": "Nuget.abp.io:n välimuistissa olevan NuGet-paketin sisällön tilastot", + "Compact": "Kompakti", + "EditSettings": "Muokkaa asetuksia", + "CurrentEstimatedSize": "Nykyinen arvioitu koko", + "CurrentEntryCount": "Nykyinen kohteiden määrä", + "TotalHits": "Yhteensä osumia", + "TotalMisses": "Yhteensä huteja", + "NoResponseFrom": "Ei vastausta käyttäjältä", + "ContentCacheSlidingExpirationByDay": "Sisällön välimuistin liukuva vanheneminen päivältä", + "MaxDaysForCaching": "Enimmäispäiviä välimuistiin", + "Enabled": "Käytössä", + "Menu:NugetPackagesContentCache": "NuGet-välimuisti", + "NugetPackagesContentCache": "NuGet-sisältövälimuisti", + "SlidingExpritionByDayInfo": "Hakee tai määrittää, kuinka kauan välimuistimerkintä voi olla passiivinen (esim. sitä ei käytetä), ennen kuin se poistetaan. Tämä ei pidennä merkinnän käyttöikää absoluuttisen vanhenemisen jälkeen.", + "MaxDaysForCachingInfo": "Hakee tai asettaa absoluuttisen vanhenemisajan suhteessa nykyhetkeen.", + "CurrentEstimatedSizeInfo": "Ilmaisee arvioidun summan kaikkien NuGet-pakettien tämänhetkisen sisältökoon välimuistissa", + "CurrentEntryCountInfo": "Ilmaisee välimuistissa tällä hetkellä olevien esiintymien määrän.", + "TotalHitsInfo": "Ilmaisee välimuistin hutien kokonaismäärän. Välimuistiosuma tapahtuu, kun välimuistista pyydetään tiedostoa ja välimuisti pystyy täyttämään pyynnön.", + "TotalMissesInfo": "Ilmaisee välimuistin osumien kokonaismäärän. Välimuisti puuttuu, kun välimuisti ei sisällä pyydettyä sisältöä.", + "Permission:VersionHistory": "Versiohistoria", + "Caches": "Välimuistit", + "VersionHistories": "Versiohistoria", + "Version": "Versio", + "PublishDate": "Julkaisupäivämäärä", + "IsStableVersion": "Vakaa versio", + "IsActive": "Aktiivinen", + "NewVersion": "Uusi versio", + "VersionHistoryDeletionConfirmationMessage": "Haluatko varmasti poistaa tämän version?", + "CreateAbpConsultantLogoInfo": "Tiedoston enimmäiskoko: 1Mt
Tuetut tiedostotyypit: jpg, jpeg, png, SVG, WebP", + "UrlCode": "URL-koodi", + "Clear": "Tyhjennä", + "Permission:AbpConsultant": "ABP-konsultti", + "Menu:AbpConsultants": "ABP konsultit", + "CreateAbpConsultant": "Luo ABP-konsultti", + "UrlCodeIsNotAvailable": "Toinen ABP-konsultti käyttää URL-koodia.", + "AbpConsultants": "ABP konsultit", + "AbpConsultant": "ABP-konsultti", + "AbpConsultantEdit": "Muokkaa ABP-konsulttia", + "AbpConsultantCreate": "Luo ABP-konsultti", + "WhoWeAreItem": "Keitä me olemme -tuote", + "FieldIsRequired": "{0} vaaditaan.", + "FieldIsNotValid": " {0} ei kelpaa.", + "InterestedLicenseType": "Kiinnostunut lisenssityyppi", + "MoveWaitList": "Siirrä jonotuslistalle", + "CommunityLinkTitle": "Avaa yhteisön verkkosivuilla", + "CommunityLink": "Yhteisön linkki", + "ReloadFromSource": "Lataa uudelleen lähteestä", + "ReloadFromSourceConfirmationMessage": "Tämä viesti päivitetään kohteesta \"{0}\". Haluatko jatkaa?" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json index 8ac2e0e770..075992d7c7 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json @@ -348,6 +348,11 @@ "Volo.AbpIo.Commercial:030008": "La date d'achat ne peut être définie que lorsque le statut est Acheté !", "Volo.AbpIo.Commercial:030009": "Utilisateur non trouvé!", "Volo.AbpIo.Commercial:030010": "Pour acheter la licence d'essai, vous devez d'abord activer votre licence d'essai !", - "Volo.AbpIo.Commercial:030011": "Vous ne pouvez pas supprimer une licence d'essai lorsqu'elle est achetée !" + "Volo.AbpIo.Commercial:030011": "Vous ne pouvez pas supprimer une licence d'essai lorsqu'elle est achetée !", + "MoveWaitList": "Passer à la liste d'attente", + "CommunityLinkTitle": "Ouvert sur le site communautaire", + "CommunityLink": "Lien communautaire", + "ReloadFromSource": "Recharger à partir de la source", + "ReloadFromSourceConfirmationMessage": "Ce message sera actualisé à partir de \"{0}\". Voulez-vous continuer?" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json index bbe2980c7b..01d9a571e1 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json @@ -348,6 +348,11 @@ "Volo.AbpIo.Commercial:030008": "खरीद की तारीख तभी निर्धारित की जा सकती है जब स्थिति खरीदी गई हो!", "Volo.AbpIo.Commercial:030009": "उपयोगकर्ता नहीं मिला!", "Volo.AbpIo.Commercial:030010": "परीक्षण लाइसेंस खरीदने के लिए, पहले आपको अपना परीक्षण लाइसेंस सक्रिय करना होगा!", - "Volo.AbpIo.Commercial:030011": "जब आप एक परीक्षण लाइसेंस खरीदा जाता है तो आप उसे हटा नहीं सकते हैं!" + "Volo.AbpIo.Commercial:030011": "जब आप एक परीक्षण लाइसेंस खरीदा जाता है तो आप उसे हटा नहीं सकते हैं!", + "MoveWaitList": "प्रतीक्षा सूची में ले जाएँ\n", + "CommunityLinkTitle": "सामुदायिक वेबसाइट पर खोलें", + "CommunityLink": "सामुदायिक लिंक", + "ReloadFromSource": "स्रोत से पुनः लोड करें", + "ReloadFromSourceConfirmationMessage": "यह पोस्ट \"{0}\" से ताज़ा की जाएगी। क्या आप जारी रखना चाहते हैं?" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json index eb6b0e7779..f4e51f77c0 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json @@ -348,6 +348,11 @@ "Volo.AbpIo.Commercial:030008": "La data di acquisto può essere impostata solo quando lo stato è Acquistato!", "Volo.AbpIo.Commercial:030009": "Utente non trovato!", "Volo.AbpIo.Commercial:030010": "Per acquistare la licenza di prova, devi prima attivare la tua licenza di prova!", - "Volo.AbpIo.Commercial:030011": "Non è possibile eliminare una licenza di prova al momento dell'acquisto!" + "Volo.AbpIo.Commercial:030011": "Non è possibile eliminare una licenza di prova al momento dell'acquisto!", + "MoveWaitList": "Passa alla lista d'attesa", + "CommunityLinkTitle": "Apri sul sito web della comunità", + "CommunityLink": "Collegamento comunitario", + "ReloadFromSource": "Ricarica dalla sorgente", + "ReloadFromSourceConfirmationMessage": "Questo post verrà aggiornato da \"{0}\". Volete continuare?" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json index 421ed3f0d4..968c8fa8c0 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sl.json @@ -348,6 +348,11 @@ "Volo.AbpIo.Commercial:030008": "Datum nakupa je mogoče nastaviti samo, če je status Kupljeno!", "Volo.AbpIo.Commercial:030009": "Uporabnik ni najden!", "Volo.AbpIo.Commercial:030010": "Za nakup preizkusne licence morate najprej aktivirati preizkusno licenco!", - "Volo.AbpIo.Commercial:030011": "Preskusne licence ne morete izbrisati, ko je kupljena!" + "Volo.AbpIo.Commercial:030011": "Preskusne licence ne morete izbrisati, ko je kupljena!", + "MoveWaitList": "Presuňte sa na zoznam čakateľov", + "CommunityLinkTitle": "Otvorte na webovej stránke komunity", + "CommunityLink": "Odkaz na komunitu", + "ReloadFromSource": "Znovu načítať zo zdroja", + "ReloadFromSourceConfirmationMessage": "Tento príspevok bude obnovený z „{0}“. Chceš pokračovať?" } } \ 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 8fb4eeb4e1..4559503a6d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json @@ -412,6 +412,11 @@ "CustomPaymentFlexSwitchDescription": "Lisanslı", "AllowFeatureUpgradeOnLicenseExpire": "Lisans süresi dolunca özellik yükseltmesine izin ver", "Deleted{0}": "[{0} silindi]", - "Permission:BookDiscount": "Kitap indirimleri" + "Permission:BookDiscount": "Kitap indirimleri", + "MoveWaitList": "Bekleme listesine geç", + "CommunityLinkTitle": "Topluluk web sitesinde açın", + "CommunityLink": "Topluluk Bağlantısı", + "ReloadFromSource": "Kaynaktan Yeniden Yükle", + "ReloadFromSourceConfirmationMessage": "Bu gönderi \"{0}\" adresinden yenilenecek. Devam etmek istiyor musun?" } } 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 b363aa0c64..0ed00a2c75 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json @@ -220,6 +220,7 @@ "SuccessfullyReIndexAllPosts": "成功索引所有的帖子", "Permission:FullSearch": "全文检索", "Menu:CliAnalytics": "客户端分析", + "Menu:Reports": "报表", "TemplateName": "模板名称", "TemplateVersion": "模板版本", "DatabaseProvider": "数据库提供者", @@ -231,8 +232,10 @@ "UiFramework": "界面框架", "Options": "选项", "CliAnalytics": "客户端分析", + "Reports": "报表", "Permission:CliAnalyticses": "客户端分析", "Permission:CliAnalytics": "客户端分析", + "Permission:Reports": "报表", "Search": "搜索", "ClearFilter": "清除过滤", "LicensePrivateKey": "许可私有密钥", @@ -397,6 +400,63 @@ "BookDiscountDeletionConfirmationMessage": "你确定要删除这个书籍折扣吗?", "CustomPaymentFlexSwitchDescription": "授权", "AllowFeatureUpgradeOnLicenseExpire": "允许在授权过期时进行功能升级", - "Deleted{0}": "[已删除 {0}]" + "Deleted{0}": "[已删除 {0}]", + "Tags": "标签", + "SetTagsInfo": "标签使用逗号分隔,例如:CSharp, Entity Framework", + "RejectTrialLicenseWarningMessage": "您确定要拒绝这个试用许可证申请吗?", + "ExportToExcel": "导出Excel", + "OverallTotalPrice": "总价", + "OverallDiscountPrice": "折扣价", + "OverallDiscountText": "折扣内容", + "SelectReport": "- 选择报表 -", + "NoDataAvailable": "无数据", + "StatisticsOfCachedContents": "nuget.abp.io缓存的NuGet包内容统计", + "Compact": "紧凑型", + "EditSettings": "编辑设置", + "CurrentEstimatedSize": "当前预计规模", + "CurrentEntryCount": "当前数量", + "TotalHits": "点击量", + "TotalMisses": "未命中总数", + "NoResponseFrom": "没有收到任何答复", + "ContentCacheSlidingExpirationByDay": "内容缓存按天滑动过期", + "MaxDaysForCaching": "缓存的最大天数", + "Enabled": "启用", + "Menu:NugetPackagesContentCache": "NuGet包缓存", + "NugetPackagesContentCache": "NuGet内容缓存", + "SlidingExpritionByDayInfo": "获取或设置一个缓存在多长时间内不活动(例如,不被访问)才会被删除。这不会使期超过绝对到期时间。", + "MaxDaysForCachingInfo": "获取或设置相对于当前时间的绝对过期时间。", + "CurrentEstimatedSizeInfo": "当前在内存缓存中的所有NuGet包的内容大小的估计总和", + "CurrentEntryCountInfo": "当前在内存缓存中的实例数量。", + "TotalHitsInfo": "缓存未命中总数,当从缓存请求文件并且缓存能够满足该请求时,就会发生缓存命中。。", + "TotalMissesInfo": "缓存命中的总数,缓存未命中是指缓存中不包含请求的内容。", + "Permission:VersionHistory": "版本历史", + "Caches": "缓存", + "VersionHistories": "版本历史", + "Version": "版本", + "PublishDate": "发布日期", + "IsStableVersion": "稳定版", + "IsActive": "激活", + "NewVersion": "新版本", + "VersionHistoryDeletionConfirmationMessage": "您确定要删除此版本吗?", + "CreateAbpConsultantLogoInfo": "最大文件大小:1MB
支持的文件类型:jpg, jpeg, png, SVG, WebP", + "UrlCode": "许可证代码", + "Clear": "清除", + "Permission:AbpConsultant": "ABP顾问", + "Menu:AbpConsultants": "ABP顾问", + "CreateAbpConsultant": "创建ABP顾问", + "UrlCodeIsNotAvailable": "Url代码已被其他ABP顾问使用。", + "AbpConsultants": "ABP顾问", + "AbpConsultant": "ABP顾问", + "AbpConsultantEdit": "编辑ABP顾问", + "AbpConsultantCreate": "创建ABP顾问", + "WhoWeAreItem": "关于我们", + "FieldIsRequired": "{0}是必须的。", + "FieldIsNotValid": "{0}是无效的。", + "InterestedLicenseType": "感兴趣的许可证类型", + "MoveWaitList": "移至候补名单", + "CommunityLinkTitle": "在社区网站上打开", + "CommunityLink": "在社区网站上打开", + "ReloadFromSource": "从源重新加载", + "ReloadFromSourceConfirmationMessage": "此帖子将从“{0}”刷新。 你想继续吗?" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hant.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hant.json index 209e667c83..b5ec2f3816 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hant.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hant.json @@ -348,6 +348,11 @@ "Volo.AbpIo.Commercial:030008": "購買日期只能在狀態為已購買時設置!", "Volo.AbpIo.Commercial:030009": "未找到用戶!", "Volo.AbpIo.Commercial:030010": "要購買試用許可證,首先您需要激活您的試用許可證!", - "Volo.AbpIo.Commercial:030011": "購買試用許可證後,您無法刪除它!" + "Volo.AbpIo.Commercial:030011": "購買試用許可證後,您無法刪除它!", + "MoveWaitList": "移至候补名单", + "CommunityLinkTitle": "在社区网站上打开", + "CommunityLink": "社区链接", + "ReloadFromSource": "從源重新加載", + "ReloadFromSourceConfirmationMessage": "此帖子將從“{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 89f7648368..783582d7be 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -27,6 +27,8 @@ "Volo.AbpIo.Domain:030010": "To purchase the trial license, you first need to activate your trial license!", "Volo.AbpIo.Domain:030011": "You cannot delete a trial license when it is purchased!", "Volo.AbpIo.Domain:030012": "A user is entitled to have only 1 free trial period. You already used your trial license.", + "Volo.AbpIo.Domain:030013": "A user with an active license cannot start a trial license.", + "Volo.AbpIo.Domain:040000": "Telemetry already exists!.", "Volo.AbpIo.Domain:070000": "The organization name can only contain latin letters, numbers, dots and hyphens!", "Volo.AbpIo.Domain:070001": "The company name can only contain latin letters, numbers, dots, space and hyphens!", "WantToLearn?": "Want to learn?", @@ -177,6 +179,7 @@ "BuyNewLicenseBetweenDatesToGetBenefit": "Buy a new license between {0} and {1} to get benefit for extra 2 months!", "CheckAllCommunityTalks": "Check All Community Posts", "ReadMore": "Read More", + "ContinueReading": "Continue Reading", "Post": "Post", "ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "Explore the contents created by the core ABP team and the ABP community.", "WelcomeFallCampaign": "Welcome Fall Campaign!", @@ -205,6 +208,8 @@ "Icons": "Icons", "Url": "Url", "Icon": "Icon", - "RecentActivities": "Recent Activities" + "RecentActivities": "Recent Activities", + "SpringCampaign": "Welcome
Spring Sale!", + "SpringCampaign2": "Limited
Time Offer!
" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json index 441efbc761..cd2802fef4 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json @@ -14,13 +14,29 @@ "Volo.AbpIo.Domain:020002": "Tätä NPM-pakettia ei voitu poistaa, koska moduulit \"{Modules}\" käyttävät tätä pakettia.", "Volo.AbpIo.Domain:020003": "Tätä NPM-pakettia ei voitu poistaa, koska moduulit \"{Modules}\" käyttävät tätä pakettia ja \"{NugetPackages}\" -nugettipaketit ovat riippuvaisia tästä paketista.", "Volo.AbpIo.Domain:020004": "Tätä Nuget-pakettia ei voitu poistaa, koska moduulit \"{Modules}\" käyttävät tätä pakettia.", + "Volo.AbpIo.Domain:030000": "Olet jo suorittanut kokeilujaksosi.", + "Volo.AbpIo.Domain:030001": "Tämä organisaation nimi on jo olemassa.", + "Volo.AbpIo.Domain:030002": "Kun kokeilukäyttöoikeus on aktivoitu, et voi vaihtaa -pyynnön tilaan!", + "Volo.AbpIo.Domain:030003": "Sellaista statusta ei ole!", + "Volo.AbpIo.Domain:030004": "Tilaa ei voitu muuttaa odottamattoman virheen vuoksi!", + "Volo.AbpIo.Domain:030005": "Alkamis- ja päättymispäivä voidaan päivittää, kun koekäyttöoikeus on -aktivoitu-tilassa!", + "Volo.AbpIo.Domain:030006": "Päättymispäivän on oltava aloituspäivää suurempi!", + "Volo.AbpIo.Domain:030007": "Tämä kokeilukäyttöoikeus on jo aktivoitu!", + "Volo.AbpIo.Domain:030008": "Ostopäivä voidaan asettaa vain, kun tila on -ostettu-!", + "Volo.AbpIo.Domain:030009": "Käyttäjää ei löydy!", + "Volo.AbpIo.Domain:030010": "Kokeilulisenssin ostamiseksi sinun on ensin aktivoitava kokeilulisenssi!", + "Volo.AbpIo.Domain:030011": "Kokeilulisenssiä ei voi poistaa, kun se on ostettu!", + "Volo.AbpIo.Domain:030012": "Käyttäjällä on oikeus saada vain yksi ilmainen kokeilujakso. Olet jo käyttänyt kokeilukäyttölupaasi.", + "Volo.AbpIo.Domain:030013": "Käyttäjä, jolla on aktiivinen käyttöoikeus, ei voi aloittaa kokeilukäyttöoikeutta.", + "Volo.AbpIo.Domain:070000": "Organisaation nimi saa sisältää vain latinalaisia kirjaimia, numeroita, pisteitä ja yhdysmerkkejä!", + "Volo.AbpIo.Domain:070001": "Yrityksen nimi saa sisältää vain latinalaisia kirjaimia, numeroita, pisteitä, välilyöntejä ja yhdysmerkkejä!", "WantToLearn?": "Haluan oppia?", "ReadyToGetStarted?": "Oletko valmis aloittamaan?", "JoinOurCommunity": "Liity yhteisöömme", - "GetStartedUpper": "ALOITTAA", + "GetStartedUpper": "ALOITA", "ForkMeOnGitHub": "Haaraa minut GitHubiin", "Features": "ominaisuudet", - "GetStarted": "Aloittaa", + "GetStarted": "Aloita", "Documents": "Asiakirjat", "Community": "Yhteisö", "ContributionGuide": "Contribution Guide", @@ -39,6 +55,159 @@ "TrialLicensePeriodHasExpired": "Kokeilulisenssijaksosi päättyi {0} päivää sitten.", "TrialLicensePeriodWillExpire": "Kokeilulisenssijaksosi vanhenee {0} päivän kuluttua.", "TrialLicensePeriodExpireToday": "Kokeilulisenssijaksosi päättyy tänään.", - "PurchaseNow": "Osta nyt!" + "PurchaseNow": "Osta nyt!", + "LatestReleaseLogs": "Uusimmat julkaisulokit", + "RoadMap": "Tiekartta", + "FAQ": "FAQ", + "SourceCode": "Lähdekoodi", + "SeeAllPosts": "Katso kaikki viestit", + "Contribute": "Osallistu", + "LiveDemo": "Live-demo", + "GetLicense": "Hanki lisenssi", + "OpenSource": "Avoin lähdekoodi", + "WebApplication": "Verkkosovellus", + "MeetTheABP": "Tapaa ABP", + "CompleteWebDevelopment": "Täydellinen verkkokehitys", + "Platform": "Alusta", + "ABPDescription": "ABP Framework on täydellinen infrastruktuuri nykyaikaisten verkkosovellusten luomiseen noudattamalla ohjelmistokehityksen parhaita käytäntöjä.", + "StrongInfrastructure": "Vahva infrastruktuuri", + "CompleteArchitecture": "Täydellinen arkkitehtuuri", + "DeveloperFocused": "Kehittäjä-fokusoitunut", + "ShareYourExperiences": "Jaa kokemuksesi ABP Frameworkista", + "LatestPosts": "Uusimmat viestit", + "LatestVideos": "Uusimmat videot", + "Views": "Näkymät", + "LearnLatestNewsAboutABPFramework": "Hanki tietoa ABP:n tapahtumista, kuten uusista julkaisuista, ilmaisista lähteistä, julkaisuista ja paljon muuta.", + "DeveloperTools": "Kehittäjän työkalut", + "StartupTemplates": "Käynnistysmallit", + "ApplicationModules": "Sovellusmoduulit", + "UI": "UI", + "Themes": "Teemat", + "Premium": "Premium", + "PrivacyPolicy": "Tietosuojakäytäntö", + "TermsAndConditions": "Käyttöehdot", + "WouldLikeToReceiveMarketingMaterials": "Haluan saada markkinointimateriaaleja, kuten tuotetarjouksia ja erikoistarjouksia.", + "JoinOurMarketingNewsletter": "Liity markkinointiuutiskirjeeseemme", + "CommunityPrivacyPolicyConfirmation": "Hyväksyn käyttöehdot ja tietosuojakäytännön .", + "WouldLikeToReceiveNotification": "Haluan saada viimeisimmät uutiset abp.io-sivustoilta.", + "CommercialNewsletterConfirmationMessage": "Hyväksyn käyttöehdot ja tietosuojakäytännön.", + "FreeDDDEBook": "Ilmainen DDD e-kirja", + "AdditionalServices": "Lisäpalvelut", + "Learn": "Opi", + "AccountOverview": "Tilin yleiskatsaus", + "MyOrganizations": "Omat organisaatiot", + "MySupportQuestions": "Tukikysymykseni", + "MyProfile": "Profiilini", + "Logout": "Kirjaudu ulos", + "Home": "Koti", + "Posts": "Viestit", + "Videos": "Videot", + "JoinTheABPCommunity": "Liity ABP-yhteisöön", + "SubmitYourPost": "Lähetä viestisi", + "Modules": "Moduulit", + "Tools": "Työkalut", + "Pricing": "Hinnoittelu", + "ChangeLogs": "Muutoslokit", + "SubscribeToNewsletter": "Tilaa uutiskirje", + "SubscribeToNewsletterDescription": "Hanki tietoa ABP:n tapahtumista, kuten uusista julkaisuista, ilmaisista lähteistä, julkaisuista ja muusta.", + "EmailAddress": "Sähköpostiosoite", + "Subscribe": "Tilaa", + "WelcomeToABP": "Tervetuloa ABP:hen", + "EULA": "EULA", + "ABPCommercialIntroductionMessage": "Valmiiksi rakennetut sovellusmoduulit, edistyneet käynnistysmallit, nopeat sovelluskehitystyökalut, ammattikäyttöliittymäteemat ja ensiluokkainen tuki.", + "MasteringAbpFrameworkEBook": "ABP-kehyksen hallitseminen", + "MasteringTheABPFrameworkExplanation": "Tämä ABP Frameworkin luojan kirjoittama kirja auttaa sinua saamaan täydellisen käsityksen viitekehyksestä ja nykyaikaisista verkkosovelluskehitystekniikoista.", + "Speakers": "Esiintyjät", + "PreviousEvents": "Aiemmat tapahtumat", + "WatchTheEvent": "Katso Tapahtuma", + "RegisterNow": "Rekisteröidy nyt", + "ThereIsNoEvent": "Tapahtumaa ei ole.", + "Events": "Tapahtumat", + "Volo.AbpIo.Domain:080000": "Ostokohde nimeltä \"{Name}\" on jo olemassa", + "MasteringAbpFrameworkBook": "Kirja: Mastering ABP Framework", + "ABPIO-CommonPreferenceDefinition": "Hanki viimeisimmät uutiset ABP Platformista, kuten uudet viestit, tapahtumat ja paljon muuta.", + "BuiltOn": "Rakennettu", + "AbpFramework": "ABP-kehys", + "Volo.AbpIo.Domain:080001": "Aloitusaika ei voi olla suurempi kuin lopetusaika", + "Enum:BookType:0": "Mastering ABP Framework", + "Enum:PurchasePlatform:0": "Amazon", + "Enum:PurchasePlatform:1": "Packt", + "Copied": "Kopioitu!", + "CouldNotCopy": "Ei voitu kopioida!", + "CopyNotSupportByYourBrowser": "Tämä ominaisuus ei toimi käyttämässäsi selaimessa.", + "City": "Kaupunki", + "ZipCode": "Postinumero", + "Address": "Osoite", + "Homepage": "Kotisivu", + "Year": "vuosi", + "Copyright": "Tekijänoikeus © {1}", + "DomainDrivenDesign": "Domain Driven Design", + "CrossCuttingConcerns": "Cross Cutting Concerns", + "AbpCommunity": "ABP-yhteisö", + "Footer_GithubStarCount": "{0} tähteä GitHubissa", + "Footer_NugetDownloadCount": "{0} Lataukset NuGetissä", + "AbpDescription": "ABP on avoimen lähdekoodin sovelluskehys, joka keskittyy AspNet Core -pohjaiseen verkkosovelluskehitykseen. Älä toista itseäsi, vaan keskity omaan yrityskoodiisi.", + "Layout_AbpFramework_MetaTitle": "ABP Framework - avoimen lähdekoodin verkkosovelluskehys", + "CommunityTalks_CountdownDays": "Pv", + "CommunityTalks_CountdownHours": "T", + "CommunityTalks_CountdownMinutes": "Min", + "CommunityTalks_CountdownSeconds": "Sek", + "SeePreviousEvents": "Katso aiemmat tapahtumat", + "CookieConsent_Accept": "Hyväksy", + "CookieConsent_Explanation_1": "Käytämme evästeitä tarjotaksemme sinulle parhaan kokemuksen verkkosivustollamme.", + "CookieConsent_Explanation_2": "Jos jatkat selaamista, hyväksyt tietosuojakäytäntömme ja evästekäytäntömme.", + "Error_Page_400_Title": "Pyydetyn sivun näyttämisessä oli ongelma.", + "Error_Page_400_Description_1": "Yleensä tämä tarkoittaa, että pyyntöäsi käsiteltäessä tapahtui odottamaton virhe.", + "Error_Page_400_Description_2": "Jos ongelma jatkuu, ota meihin yhteyttä osoitteeseen info@abp.io, niin autamme sinua pääsemään eteenpäin.", + "GoToHomepage": "Mene kotisivulle", + "Error_Page_404_Title": "Sivua ei löytynyt!", + "Error_Page_404_Description_1": "Tämä ei ole etsimäsi verkkosivu.", + "Error_Page_500_Title": "Näyttää siltä, että jotain meni pieleen!", + "Error_Page_500_Description_1": "Seuraamme näitä virheitä automaattisesti, mutta jos ongelma jatkuu,
ota meihin yhteyttä. Kokeile sillä välin sivun päivittämistä.", + "Error_Page_500_Description_2": "Ota meihin yhteyttä osoitteessa info@abp.io.", + "Books": "Kirjat", + "ABPDiscordServer": "ABP Discord-palvelin", + "ABPCommunityTalks": "ABP Community Talks", + "ABPCommunityPosts": "ABP-yhteisön viestit", + "BuyAndGetMonths": "OSTA 12 KUUKAUTA, SAAT 14 KUUKAUTA!", + "GetYourDeal": "Hanki tarjouksesi", + "BuyOrRenewLicense": "Osta tai uusi lisenssi nyt ja saat 2 lisäkuukautta!", + "BuyOrRenewLicenseToGetExtra2Months": "Osta tai uusi lisenssi nyt ja saat 2 lisäkuukautta! KIIREHDI! ⏰ Viimeinen päivä: {0}", + "HurryUp": "KIIREHDI!", + "LastDay": "Viimeinen päivä: {0}", + "BuyNewLicenseBetweenDatesToGetBenefit": "Osta uusi lisenssi välillä {0} - {1}, niin saat 2 lisäkuukautta!", + "CheckAllCommunityTalks": "Tarkista kaikki yhteisön viestit", + "ReadMore": "Lue lisää", + "Post": "Viesti", + "ExploreTheContentsCreatedByTheCoreABPTeamAndTheABPCommunity": "Tutustu ABP-ydintiimin ja ABP-yhteisön luomaan sisältöön.", + "WelcomeFallCampaign": "Tervetuloa syksyn kampanjaan!", + "GiveAwayForNewPurchases": "Sovelluskehityksen luokkahuonekoulutus jaetaan uusien ostosten yhteydessä!", + "BlackFriday": "MUSTA PERJANTAI", + "ValidForExistingCustomers": "Voimassa myös
olemassa oleville asiakkaille!", + "CampaignBetweenDates": "{0}
- {1}", + "SaveUpTo": "SÄÄSTÄ JOPA${0}K", + "ImplementingDDD": "Implementing Domain Driven Design", + "ExploreTheEBook": "Tutustu e-kirjaan", + "ExploreTheBook": "Tutustu Kirjaan", + "ConsultantType": "Konsultointityyppi", + "Expert": "ABP-asiantuntija", + "Partner": "ABP kumppani", + "Industry": "Ala", + "Location": "Sijainti", + "Contact": "Ottaa yhteyttä", + "Partner_Year": "Kumppanuuden vuosi", + "Info": "Tiedot", + "SpokenLanguages": "Puhutut kielet", + "SocialMedia": "Sosiaalinen media", + "Activity": "Toiminta", + "Type": "Tyyppi", + "Contribution": "Osallistuminen", + "WhoWeAre": "Keitä olemme", + "Icons": "Kuvakkeet", + "Url": "URL-osoite", + "Icon": "Kuvake", + "RecentActivities": "Viimeaikaiset toimet", + "SpringCampaign": "Tervetuloa kevätaleen!", + "SpringCampaign2": "Rajoitetun
ajan tarjous!
" } } \ 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 0dcd69bbd8..af0190dcbb 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json @@ -26,6 +26,8 @@ "Volo.AbpIo.Domain:030009": "找不到用户!", "Volo.AbpIo.Domain:030010": "要购买试用许可证,您首先需要激活您的试用许可证!", "Volo.AbpIo.Domain:030011": "购买后不能删除试用许可证!", + "Volo.AbpIo.Domain:030012": "用户只有一个免费试用期。您已经使用了试用许可证。", + "Volo.AbpIo.Domain:030013": "已激活许可证的用户无法启动试用许可证。", "Volo.AbpIo.Domain:070000": "组织名称只能包含拉丁字母、数字、点和连字符!", "Volo.AbpIo.Domain:070001": "公司名称只能包含拉丁字母、数字、点、空格和连字符!", "WantToLearn?": "想学习吗?", @@ -183,6 +185,29 @@ "BlackFriday": "黑色 星期五", "ValidForExistingCustomers": "也适用于
现有用户!", "CampaignBetweenDates": "从 {0}
至 {1}", - "SaveUpTo": "最多节省${0}K" + "SaveUpTo": "最多节省${0}K", + "ImplementingDDD": "实现领域驱动设计", + "ExploreTheEBook": "浏览电子书", + "ExploreTheBook": "浏览书籍", + "ConsultantType": "顾问类型", + "Expert": "ABP专家", + "Partner": "ABP合作伙伴", + "Industry": "行业", + "Location": "地点", + "Contact": "联系", + "Partner_Year": "合作年份", + "Info": "信息", + "SpokenLanguages": "母语", + "SocialMedia": "社交媒体", + "Activity": "活动", + "Type": "类型", + "Contribution": "贡献", + "WhoWeAre": "关于我们", + "Icons": "图标", + "Url": "Url", + "Icon": "图标", + "RecentActivities": "最近的活动", + "SpringCampaign": "欢迎
春季促销!", + "SpringCampaign2": "限时优惠!
" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/ar.json index d441da99a4..6da2313da5 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/ar.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/ar.json @@ -1,7 +1,7 @@ { "culture": "ar", "texts": { - "AbpTitle": "إطار عمل ABP - إطار عمل تطبيق ويب مفتوح المصدر", + "AbpTitle": "قراءة جميع مشاركات المدونة", "AbpDescription": "ABP هو إطار عمل مفتوح المصدر يركز على تطوير تطبيقات الويب القائمة على AspNet Core. لا تكرر نفسك ، ركز على كود عملك الخاص." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/en.json index 6f52556e72..2da5698215 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/en.json @@ -1,7 +1,8 @@ { "culture": "en", "texts": { - "AbpTitle": "ABP Framework - Open Source Web Application Framework", - "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." + "AbpTitle": "Read All Blog Posts", + "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.", + "AbpDefinition": "ABP blog for .NET development, cross-platform, ASP.NET application templates, ABP-related news and more..." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/fi.json new file mode 100644 index 0000000000..26f9152365 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/fi.json @@ -0,0 +1,8 @@ +{ + "culture": "fi", + "texts": { + "AbpTitle": "ABP Framework - avoimen lähdekoodin verkkosovelluskehys", + "AbpDescription": "ABP on avoimen lähdekoodin sovelluskehys, joka keskittyy AspNet Core -pohjaiseen verkkosovelluskehitykseen. Älä toista itseäsi, vaan keskity omaan yrityskoodiisi.", + "AbpDefinition": "ABP-blogi .NET-kehityksestä, useista alustoista, ASP.NET-sovellusmalleista, ABP:hen liittyvistä uutisista ja muusta..." + } +} \ 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 c5f72d9fbe..6c75de02f9 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/tr.json @@ -1,7 +1,7 @@ { "culture": "tr", "texts": { - "AbpTitle": "ABP Framework - Açık Kaynak Web Uygulama Çerçevesi", + "AbpTitle": "Tüm Blog Yazılarını Oku", "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/Blog/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/zh-Hans.json index 524c3345e3..7ede6b35c2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/zh-Hans.json @@ -1,7 +1,8 @@ { "culture": "zh-Hans", "texts": { - "AbpTitle": "ABP 框架 - 开源 Web 应用程序框架", - "AbpDescription": "ABP 是一个开源应用程序框架,专注于基于 AspNet Core 的 Web 应用程序开发。 Don't repeat yourself,专注于自己的业务代码。" + "AbpTitle": "阅读所有博客文章", + "AbpDescription": "ABP 是一个开源应用程序框架,专注于基于 AspNet Core 的 Web 应用程序开发。 Don't repeat yourself,专注于自己的业务代码。", + "AbpDefinition": ".NET开发的ABP博客,跨平台,ASP.NET应用程序模板,ABP相关新闻等..." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json index b202302d8e..fff3b72a1d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ar.json @@ -48,7 +48,7 @@ "LiveDemo": "عرض حي", "GetLicence": "احصل على ترخيص", "Application": "تطبيق", - "StartupTemplates": "قوالب بدء التشغيل", + "StartupTemplates": "قوالب بدء تشغيل ABP", "Startup": "بدء", "Templates": "القوالب", "Developer": "مطور", @@ -190,7 +190,7 @@ "ChangingDevelopers": "هل يمكنني تغيير المطورين المسجلين لمنظمتي في المستقبل؟", "ChangingDevelopersExplanation": "بالإضافة إلى إضافة مطورين جدد إلى الترخيص الخاص بك ، يمكنك أيضًا تغيير المطورين الحاليين (يمكنك إزالة مطور وإضافة مطور جديد إلى نفس المقعد) دون أي تكلفة إضافية.", "WhenShouldIRenewMyLicense": "متى يجب أن أجدد رخصتي؟", - "WhenShouldIRenewMyLicenseExplanation": "إذا قمت بتجديد ترخيصك في غضون شهر واحد بعد انتهاء صلاحية ترخيصك ، فسيتم تطبيق الخصومات التالية: ترخيص الفريق {0}؛ رخصة تجارية {1} ؛ ترخيص المؤسسة {2}. ومع ذلك ، إذا جددت ترخيصك بعد شهر واحد من تاريخ انتهاء صلاحية الترخيص ، فسيكون سعر التجديد هو نفسه سعر شراء الترخيص ولن يكون هناك خصم على التجديد.", + "WhenShouldIRenewMyLicenseExplanation": "إذا قمت بتجديد ترخيصك في غضون {3} يوما بعد انتهاء صلاحية رخصتك ، فسيتم تطبيق الخصومات التالية: رخصة الفريق {0}؛ الرخصة التجارية {1} ؛ رخصة المؤسسة {2}. ومع ذلك ، إذا جددت رخصتك بعد {3} يوما من تاريخ انتهاء صلاحية الرخصة ، فسيكون سعر التجديد هو نفسه سعر شراء الرخصة ولن يكون هناك خصم على التجديد.", "TrialPlan": "هل لديك خطة تجريبية؟", "DoYouAcceptBankWireTransfer": "هل تقبل التحويل البنكي؟", "DoYouAcceptBankWireTransferExplanation": "نعم ، نحن نقبل التحويل البنكي.
بعد إرسال رسوم الترخيص عبر التحويل المصرفي ، أرسل لنا بريدًا إلكترونيًا على accounting@abp.io إيصالك ونوع الترخيص المطلوب. معلومات حسابنا المصرفي الدولي:", @@ -489,7 +489,7 @@ "AddBasket": "إضافة إلى السلة", "SendTrainingRequest": "إرسال طلب تدريب", "OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* النسخة الإنجليزية من هذه الوثيقة هي الأحدث وستتم العةدة اليها خلال أي نزاع.", - "Pricing_Page_Title": "الخطط والتسعير", + "Pricing_Page_Title": "التسعير والخطط", "Pricing_Page_Description": "اختر الميزات والوظائف التي يحتاجها عملك اليوم. شراء رخصة تجارية ABP وإنشاء مشاريع غير محدودة.", "Pricing_Page_HurryUp": "أسرع!", "Pricing_Page_BuyLicense": "اشترِ رخصة من أسعار 2021 حتى 16 يناير!", @@ -599,12 +599,11 @@ "Faq_Page_Currency": "العملة", "Faq_Page_VatNumber": "رقم ضريبة القيمة المضافة", "Faq_Page_OtherCurrenciesInfo": "للعملات الأخرى ، انظر الى جميع الحسابات", - "ModuleDetail_Page_Title": "تفاصيل الوحدة - {0}", "ProjectCreatedSuccess_Page_Title": "تم إنشاء مشروعك", "ProjectCreatedSuccess_Page_Description": "تم إنشاء مشروع ABP الخاص بك بنجاح!", - "Suite_Page_Title": "ABP Suite - إنشاء صفحات CRUD", + "Suite_Page_Title": "جناح ABP", "Suite_Page_Description": "يوفر ABP التجاري أدوات تطوير سريعة للتطبيقات لزيادة إنتاجية المطورين. يتيح لك ABP Suite إنشاء صفحات CRUD بسهولة.", - "Themes_Page_Title": "سمات واجهة المستخدم الحديثة والوظيفية", + "Themes_Page_Title": "ثيمات ABP", "Themes_Page_Description": "يوفر ABP التجاري العديد من سمات واجهة المستخدم الاحترافية والحديثة. أنشئ عرضًا تجريبيًا مجانيًا للحصول على عرض سريع لشكل واجهة المستخدم.", "Tools_Page_Title": "أدوات تطوير التطبيقات السريعة", "Tools_Page_Description": "يوفر ABP التجاري أدوات تطوير سريعة للتطبيقات لزيادة إنتاجية المطورين. يتيح لك ABP Suite إنشاء صفحات CRUD بسهولة.", @@ -779,6 +778,13 @@ "UpgradePaymentInfoSection_LicenseRenewalPrice": "تجديد الرخصة", "Total": "المجموع", "SupportPolicyFaqTitle": "ما هي سياسة الدعم الخاصة بك؟", - "SupportPolicyFaqExplanation": "نحن ندعم فقط الإصدار الرئيسي النشط والسابق. لا نضمن إصدار تصحيح للإصدارات الرئيسية الثالثة والأقدم. على سبيل المثال ، إذا كان الإصدار النشط هو 7.0.0 ، فسنصدر إصدارات تصحيح لكل من 6.x.x و 7.x.x. إلى جانب ذلك ، نحن نقدم الدعم فقط لإطار عمل ABP والقضايا التجارية المتعلقة بـ ABP. هذا يعني أنه لا يتم تقديم أي دعم لتطبيقات الطرف الثالث والخدمات السحابية والمكتبات الطرفية الأخرى التي تستخدمها منتجات ABP. سنبذل جهودًا معقولة تجاريًا لتزويد عملائنا بالدعم الفني خلال ساعات العمل الرسمية لـ \"Volosoft Bilisim A.S\". من ناحية أخرى ، نحن لا نلتزم بوقت استجابة اتفاقية مستوى الخدمة (SLA) ، لكننا سنحاول الرد على المشكلات الفنية في أسرع وقت ممكن خلال ساعات العمل الرسمية لدينا. ما لم يتم إبرام اتفاقية خاصة مع العميل ، فإننا نقدم الدعم فقط على https://support.abp.io. لدينا أيضًا دعم خاص بالبريد الإلكتروني ، وهو متاح فقط لحاملي تراخيص المؤسسة." + "SupportPolicyFaqExplanation": "نحن ندعم فقط الإصدار الرئيسي النشط والسابق. لا نضمن إصدار تصحيح للإصدارات الرئيسية الثالثة والأقدم. على سبيل المثال ، إذا كان الإصدار النشط هو 7.0.0 ، فسنصدر إصدارات تصحيح لكل من 6.x.x و 7.x.x. إلى جانب ذلك ، نحن نقدم الدعم فقط لإطار عمل ABP والقضايا التجارية المتعلقة بـ ABP. هذا يعني أنه لا يتم تقديم أي دعم لتطبيقات الطرف الثالث والخدمات السحابية والمكتبات الطرفية الأخرى التي تستخدمها منتجات ABP. سنبذل جهودًا معقولة تجاريًا لتزويد عملائنا بالدعم الفني خلال ساعات العمل الرسمية لـ \"Volosoft Bilisim A.S\". من ناحية أخرى ، نحن لا نلتزم بوقت استجابة اتفاقية مستوى الخدمة (SLA) ، لكننا سنحاول الرد على المشكلات الفنية في أسرع وقت ممكن خلال ساعات العمل الرسمية لدينا. ما لم يتم إبرام اتفاقية خاصة مع العميل ، فإننا نقدم الدعم فقط على https://support.abp.io. لدينا أيضًا دعم خاص بالبريد الإلكتروني ، وهو متاح فقط لحاملي تراخيص المؤسسة.", + "WhyUseAbpIoPlatform": "لماذا يجب علي استخدام منصة ABP.IO بدلاً من إنشاء حل جديد من البداية؟", + "WhyUseAbpIoPlatformFaqExplanation": "انظر الى هذا المستند للحصول على شرح مفصل لسبب استخدام ABP.IO Platform لفائدته الكبيرة بدلًا من القيام بكل شيء بنفسك.", + "EulaPageTitle": "اتفاقية ترخيص المستخدم النهائي (EULA)", + "PrivacyPolicyPageTitle": "سياسة الخصوصية - سياسة ملفات تعريف الارتباط", + "TermsConditionsPageTitle": "الأحكام والشروط", + "TrainingsPageTitle": "حزم تدريب ABP", + "ModulesPageTitle": "وحدات تطبيق ABP سابقة البناء" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/cs.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/cs.json index f50b176063..a1205f1809 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/cs.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/cs.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Mohu v budoucnu změnit registrované vývojáře své organizace?", "ChangingDevelopersExplanation": "Kromě přidání nových vývojářů do vaší licence můžete také změnit stávající vývojáře (můžete odebrat vývojáře a přidat nového na stejné místo) bez jakýchkoli dalších nákladů.", "WhenShouldIRenewMyLicense": "Kdy bych měl obnovit licenci?", - "WhenShouldIRenewMyLicenseExplanation": "Pokud si licenci obnovíte do 1 měsíce po vypršení platnosti licence, budou uplatněny následující slevy: Týmová licence {0}; Obchodní licence {1}; Enterprise Licence {2}. Pokud však licenci obnovíte po 1 měsíci od data vypršení platnosti vaší licence, cena za obnovení bude stejná jako cena za zakoupení licence a na obnovení nebude poskytnuta žádná sleva.", + "WhenShouldIRenewMyLicenseExplanation": "Pokud si licenci obnovíte do {3} dní po vypršení platnosti licence, budou uplatněny následující slevy: Týmová licence {0}; Obchodní licence {1}; Enterprise Licence {2}. Pokud však licenci obnovíte po {3} měsíci od data vypršení platnosti vaší licence, cena za obnovení bude stejná jako cena za zakoupení licence a na obnovení nebude poskytnuta žádná sleva.", "TrialPlan": "Máte zkušební plán?", "DoYouAcceptBankWireTransfer": "Přijímáte bankovní převod?", "DoYouAcceptBankWireTransferExplanation": "Ano, přijímáme bankovní převod.
Po zaslání licenčního poplatku bankovním převodem nám zašlete e-mail na adresu accounting@abp.io potvrzení a požadovaný typ licence. Informace o našem mezinárodním bankovním účtu:", @@ -383,6 +383,24 @@ "RenewLicenseEarly": "Pokud si předčasně obnovím licenci, dostanu celý rok?", "RenewLicenseEarylExplanation": "Když obnovíte licenci před datem vypršení platnosti licence, bude k datu vypršení platnosti licence přidán 1 rok. Pokud například platnost vaší licence vyprší dne {0}-06-06 a obnovíte ji dne {0}-01-01, bude vaše nové datum vypršení platnosti licence {1}-06-06.", "discountForYears": "{0}% sleva po dobu {1} let", - "BlackFridayDiscount": "Black Friday sleva" + "BlackFridayDiscount": "Black Friday sleva", + "OnboardingTrainingFaqTitle": "Máte školení ABP onboarding?", + "OnboardingTrainingFaqExplanation": "Ano, máme školicí služby ABP, které vám pomohou rychle zahájit váš projekt ABP. Dozvíte se o ABP od hlavního člena týmu ABP a získáte dovednosti pro zahájení vašeho projektu ABP. Na onboarding školení si vysvětlíme, jak nastavit vaše vývojové prostředí, nainstalovat požadované nástroje, vytvořit plně funkční stránku CRUD. Školení bude probíhat živě a bude se používat aplikace Zoom a jsme otevřeni využití dalších online platforem pro setkávání. Jazykem školení bude angličtina. Během sezení můžete také klást otázky týkající se ABP. Pro obě strany bude naplánován vhodný čas a datum. Chcete-li získat další informace, kontaktujte nás na adrese info@abp.io.", + "SupportPolicyFaqTitle": "Jaká je vaše politika podpory?", + "SupportPolicyFaqExplanation": "Podporujeme pouze aktivní a předchozí hlavní verzi. Nezaručujeme vydání opravy pro třetí a starší hlavní verzi. Například pokud je aktivní verze 7.0.0, vydáme opravné verze pro verzi 6.x.x i 7.x.x. Kromě toho poskytujeme podporu pouze pro problémy související s ABP Framework a ABP Commercial. To znamená, že neposkytujeme žádnou podporu aplikacím třetích stran, cloudovým službám a dalším periferním knihovnám používaným produkty ABP. Vynaložíme komerčně přiměřené úsilí, abychom našim zákazníkům poskytli technickou podporu během oficiální pracovní doby společnosti \"Volosoft Bilisim A.S\". Na druhou stranu se nezavazujeme k době odezvy podle dohody o úrovni služeb (SLA), ale budeme se snažit reagovat na technické problémy co nejrychleji v rámci naší oficiální pracovní doby. Pokud není se zákazníkem uzavřena zvláštní dohoda, poskytujeme podporu pouze na adrese https://support.abp.io. Máme také soukromou e-mailovou podporu, která je k dispozici pouze držitelům licence Enterprise.", + "DowngradeLicensePlan": "Mohu v budoucnu přejít na nižší licenční plán?", + "DowngradeLicensePlanExplanation": "Stávající licenční plán nelze snížit. Můžete si však zakoupit nový nižší licenční plán a pokračovat ve vývoji s novou licencí. Po zakoupení nižší licence se stačí přihlásit k novému licenčnímu plánu pomocí příkazu ABP CLI: abp login -o `.", + "LicenseTransfer": "Lze licenci převést z jednoho vývojáře na druhého?", + "LicenseTransferExplanation": "Ano! Zakoupením licence se stáváte jejím držitelem, a proto budete mít přístup na stránku pro správu organizace. Organizace má role vlastníka a vývojáře. Vlastníci mohou spravovat místa pro vývojáře a přiřazovat vývojáře. Každý přidělený vývojář se do systému přihlásí pomocí příkazu ABP CLI a bude mít oprávnění k vývoji a podpoře.", + "WhatHappensWhenLicenseEnds": "Co se stane po skončení licenčního období?", + "WhatHappensWhenLicenseEndsExplanation1": "Komerční licence ABP je trvalá licence. Po vypršení platnosti licence můžete pokračovat ve vývoji svého projektu. A nejste povinni licenci obnovovat. Vaše licence je dodávána s ročním plánem aktualizací a podpory již v balení. Abyste mohli i nadále získávat nové funkce, vylepšení výkonu, opravy chyb, podporu a pokračovat v používání sady ABP Suite, musíte svou licenci obnovit. Po vypršení platnosti licence;", + "WhatHappensWhenLicenseEndsExplanation2": "Pomocí ABP Commercial nemůžete vytvářet nová řešení, ale můžete navždy pokračovat ve vývoji stávajících aplikací.", + "WhatHappensWhenLicenseEndsExplanation3": "Aktualizace modulů a témat budete moci získat v rámci své verze MINOR (kromě verzí RC nebo Preview). Například: pokud používáte modul ve verzi 3.2.0, můžete stále získávat aktualizace pro verzi 3.2.x (v3.2.1, v3.2.5... atd.) tohoto modulu. Nemůžete však získat aktualizace pro další hlavní nebo vedlejší verzi (např. v3.3.0, v3.3.3, 4.x.x... atd.). Například když vám vypršela licence, poslední verze byla v4.4.3 a později byla zveřejněna jak verze 4.4.4, tak verze 4.5.0, měli byste přístup k verzi v4.4.X, ale k verzi v4.5.X byste přístup neměli.", + "WhatHappensWhenLicenseEndsExplanation4": "Po skončení platnosti licence nelze instalovat nové moduly a motivy přidané do platformy ABP Commercial.", + "WhatHappensWhenLicenseEndsExplanation5": "Sadu ABP Suite nelze použít.", + "WhatHappensWhenLicenseEndsExplanation6": "Již nelze získat prémiovou podporu.", + "WhatHappensWhenLicenseEndsExplanation7": "Pokud chcete i nadále využívat tyto výhody, můžete svou licenci prodloužit (obnovit). Pokud prodloužíte licenci do {3} dní po vypršení platnosti licence, budou uplatněny následující slevy: Týmová licence {0}; Obchodní licence {1}; Enterprise Licence {2}.", + "BlazoriseLicense": "Musíme si koupit licenci Blazorise?", + "BlazoriseLicenseExplanation": "Máme dohodu mezi společnostmi Volosoft a Megabit, na základě této dohody je licence Blazorise přibalena k produktům ABP Commercial, proto si naši zákazníci nemusí kupovat další licenci Blazorise." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/de.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/de.json index 5b4eb201aa..ebc78f8b51 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/de.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/de.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Kann ich die registrierten Entwickler meiner Organisation in Zukunft ändern?", "ChangingDevelopersExplanation": "Sie können nicht nur neue Entwickler zu Ihrer Lizenz hinzufügen, sondern auch die vorhandenen Entwickler ändern (Sie können einen Entwickler entfernen und einen neuen zum selben Arbeitsplatz hinzufügen) ohne zusätzliche Kosten.", "WhenShouldIRenewMyLicense": "Wann sollte ich meine Lizenz erneuern?", - "WhenShouldIRenewMyLicenseExplanation": "Wenn Sie Ihre Lizenz innerhalb von 1 Monat nach Ablauf Ihrer Lizenz erneuern, werden die folgenden Rabatte gewährt: Team-Lizenz {0}% Rabatt, Business-Lizenz {1}% Rabatt, Enterprise-Lizenz {2}% Rabatt . Wenn Sie Ihre Lizenz 1 Monat nach dem Ablaufdatum Ihrer Lizenz verlängern, entspricht der Verlängerungspreis dem Lizenzkaufpreis und es wird kein Rabatt auf Ihre Verlängerung gewährt.", + "WhenShouldIRenewMyLicenseExplanation": "Wenn Sie Ihre Lizenz innerhalb von {3} Tage nach Ablauf Ihrer Lizenz erneuern, werden die folgenden Rabatte gewährt: Team-Lizenz {0}% Rabatt, Business-Lizenz {1}% Rabatt, Enterprise-Lizenz {2}% Rabatt . Wenn Sie Ihre Lizenz {3} Tage nach dem Ablaufdatum Ihrer Lizenz verlängern, entspricht der Verlängerungspreis dem Lizenzkaufpreis und es wird kein Rabatt auf Ihre Verlängerung gewährt.", "TrialPlan": "Hast du einen Probeplan?", "DoYouAcceptBankWireTransfer": "Akzeptieren Sie Banküberweisungen?", "DoYouAcceptBankWireTransferExplanation": "Ja, wir akzeptieren Banküberweisungen.
Nachdem Sie die Lizenzgebühr per Banküberweisung gesendet haben, senden Sie uns Ihre Quittung und den gewünschten Lizenztyp per E-Mail an accounting@abp.io. Unsere internationale Bankverbindung:", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 6e5e69f079..d1d30af37c 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -160,7 +160,7 @@ "SearchQuestionPlaceholder": "Search in frequently asked questions", "WhatIsTheABPCommercial": "What is ABP Commercial?", "WhatAreDifferencesThanAbpFramework": "What are the differences between the open source ABP Framework and ABP Commercial?", - "AbpCommercialMetaTitle": "ABP Commercial - Complete Web Development Platform : {0} | ABP Commercial", + "AbpCommercialMetaTitle": " {0} | ABP Commercial", "AbpCommercialMetaDescription": "ABP Commercial is a set of pre-built application modules, rapid development tooling, UI themes and services built on top of the open-source ABP framework.", "ABPCommercialExplanation": "ABP Commercial is a set of premium modules, tools, themes and services that are built on top of the open source ABP framework. ABP Commercial is being developed and supported by the same team behind the ABP framework.", "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP framework is a modular, themeable, microservice compatible application development framework for ASP.NET Core. It provides a complete architecture and a strong infrastructure to let you focus on your own business code rather than repeating yourself for every new project. It is based on the best practices of software development and popular tools you already know.

ABP framework is completely free, open source and community-driven. It also provides a free theme and some pre-built modules (e.g. identity management and tenant management).

", @@ -196,19 +196,19 @@ "ChangingDevelopers": "Can I change the registered developers of my organization in the future?", "ChangingDevelopersExplanation": "In addition to adding new developers to your license, you can also change the existing developers (you can remove a developer and add a new one to the same seat) without any additional cost.", "WhatHappensWhenLicenseEnds": "What happens when my license period ends?", - "WhatHappensWhenLicenseEndsExplanation1": "The ABP Commercial license is a perpetual license. After your license expires, you can continue developing your project. And you are not obliged to renew your license. Your license comes with a one-year update and support plan out of the box. In order to continue to get new features, performance enhancements, bug fixes, support and continue using ABP Suite, you need to renew your license. When your license expires, you will not get the following benefits:", + "WhatHappensWhenLicenseEndsExplanation1": "The ABP Commercial license is a perpetual license. After your license expires, you can continue developing your project. And you are not obliged to renew your license. Your license comes with a one-year update and support plan out of the box. In order to continue to get new features, performance enhancements, bug fixes, support and continue using ABP Suite, you need to renew your license. When your license expires;", "WhatHappensWhenLicenseEndsExplanation2": "You can not create new solutions using the ABP Commercial, but you can continue developing your existing applications forever.", "WhatHappensWhenLicenseEndsExplanation3": "You will be able to get updates for the modules and themes within your MINOR version (except RC or Preview versions). For example: if you are using v3.2.0 of a module, you can still get updates for v3.2.x (v3.2.1, v3.2.5... etc.) of that module. But you cannot get updates for the next major or minor version (like v3.3.0, v3.3.3, 4.x.x.. etc.). For example, when your license expired, the latest release was v4.4.3, and later, it published both 4.4.4 version and 4.5.0 version, you would be able to access the v4.4.X but you wouldn't be access the v4.5.X.", "WhatHappensWhenLicenseEndsExplanation4": "You can not install new modules and themes added to the ABP Commercial platform after your license ends.", "WhatHappensWhenLicenseEndsExplanation5": "You can not use the ABP Suite.", "WhatHappensWhenLicenseEndsExplanation6": "You can not get the premium support anymore.", - "WhatHappensWhenLicenseEndsExplanation7": "You can extend (renew) your license if you want to continue getting these benefits. If you extend your license within 1 month after your license expires, the following discounts will be applied: Team License {0}; Business License {1}; Enterprise License {2}.", + "WhatHappensWhenLicenseEndsExplanation7": "You can extend (renew) your license if you want to continue getting these benefits. If you extend your license within {3} days after your license expires, the following discounts will be applied: Team License {0}; Business License {1}; Enterprise License {2}.", "discountForYears": "{0}% discount for {1} year(s)", "WhatHappensWhenLicenseEndsExplanation8": "The ABP projects you generated are not stored on our servers. Therefore, it is your responsibility to keep the source code you download. When your license expires, there's no way to get your generated ABP project source code.", "WhenShouldIRenewMyLicense": "When should I renew my license?", - "WhenShouldIRenewMyLicenseExplanation": "If you renew your license within 1 month after your license expires, the following discounts will be applied: Team License {0}; Business License {1}; Enterprise License {2}. However, if you renew your license after 1 month since the expiry date of your license, the renewal price will be the same as the license purchase price and there will be no discount on your renewal.", + "WhenShouldIRenewMyLicenseExplanation": "If you renew your license within {3} days after your license expires, the following discounts will be applied: Team License {0}; Business License {1}; Enterprise License {2}. However, if you renew your license after {3} days since the expiry date of your license, the renewal price will be the same as the license purchase price and there will be no discount on your renewal.", "TrialPlan": "Do you have a trial plan?", - "TrialPlanExplanation": "It has a 14 days trial period for the ABP Commercial team license. For more information visit here. Furthermore, for the Team licenses we provide a 30 days money-back guarantee. You can just request a refund in the first 30 days. For the Business and Enterprise licenses, we provide 60% refund in 30 days. This is because Business and Enterprise licenses include the full source code of all the modules and the themes.", + "TrialPlanExplanation": "No, there is no trial version for ABP Commercial. You can check the community edition to understand the code quality and approaches. We also offer a 30-day money-back guarantee for the Team license, no questions asked! You can request a refund within the first 30 days. We provide a 60% refund within 30 days for Business and Enterprise licenses. This is because the Business and Enterprise licenses contain the full source-code of all the modules and themes.", "DoYouAcceptBankWireTransfer": "Do you accept bank wire transfers?", "DoYouAcceptBankWireTransferExplanation": "Yes, we accept bank wire transfers.
After sending the license fee via bank transfer, send your receipt and requested license type to accounting@volosoft.com.
Our international bank account information:", "HowToUpgrade": "How to upgrade existing applications when a new version is available?", @@ -511,7 +511,7 @@ "AddBasket": "Add to Basket", "SendTrainingRequest": "Send Training Request", "OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* The English version of this document is the most up-to-date and the English version will prevail in any dispute.", - "Pricing_Page_Title": "Plans & Pricing", + "Pricing_Page_Title": "Pricing & Plans", "Pricing_Page_Description": "Choose the features and functionality your business needs today. Buy an ABP Commercial license and create unlimited projects.", "Pricing_Page_HurryUp": "Hurry Up!", "Pricing_Page_BuyLicense": "Buy a license at 2021 prices until January 16!", @@ -597,7 +597,7 @@ "Purchase_PricePerDeveloper": "{0} {1} per developer", "Purchase_IncludedDeveloperInfo": "{0} {1} included.", "Purchase_LicenseExtraDeveloperPurchaseMessage": "The {0} license contains {1} developer(s). You can add additional developers now or later.", - "StartupTemplates_Page_Title": "The Startup Templates", + "StartupTemplates_Page_Title": "ABP Startup Templates", "StartupTemplates_Page_Description": "ABP Commercial allows you to build solutions with any level of complexity. It provides two main pre-built startup solutions. You can select the one close to your requirements and build your own custom solution on top of it.", "MicroserviceStartupSolutionForDotnet": "Microservice Startup Solution for .NET", "MonolithSolutionForDotnet": "Monolith (modular) Solution for .NET", @@ -622,12 +622,11 @@ "Faq_Page_Currency": "Currency", "Faq_Page_VatNumber": "VAT number", "Faq_Page_OtherCurrenciesInfo": "For other currencies, see all accounts", - "ModuleDetail_Page_Title": "Module Detail - {0}", "ProjectCreatedSuccess_Page_Title": "Your project created", "ProjectCreatedSuccess_Page_Description": "Your ABP project created successfully!", - "Suite_Page_Title": "ABP Suite - Create CRUD Pages", + "Suite_Page_Title": "ABP Suite", "Suite_Page_Description": "ABP Commercial provides rapid application development tooling to increase developer productivity. ABP Suite allows you to create CRUD pages easily.", - "Themes_Page_Title": "Modern and Functional UI Themes", + "Themes_Page_Title": "ABP Themes", "Themes_Page_Description": "ABP Commercial provides multiple professional, modern UI themes. Create a free demo to have a quick view of what the UI looks like.", "Tools_Page_Title": "Rapid Application Development Tools", "Tools_Page_Description": "ABP Commercial provides rapid application development tooling to increase developer productivity. ABP Suite allows you to create CRUD pages easily.", @@ -818,6 +817,15 @@ "DeletingMemberWarningMessage": "\"{0}\" will be removed from the developer list. If you want, you can assign this empty seat to another developer later.", "AdditionalInfo": "If the developer seats are above your requirements, you can reduce them. You can email at info@abp.io to remove some of your developer seats. Clearing unused developer seats will reduce the license renewal cost. If you want, you can re-purchase additional developer seats within your active license period. Note that, since there are {0} developers in this license package, you cannot reduce this number.", "LinkExpiredErrorMessage": "The link you are trying to access is expired.", - "ExpirationDate": "Expiration Date" + "ExpirationDate": "Expiration Date", + "SpringCampaignDiscount": "Spring Campaign Discount", + "WhyUseAbpIoPlatform": "Why should I use the ABP.IO Platform instead of creating a new solution from scratch?", + "WhyUseAbpIoPlatformFaqExplanation": "See that document for a detailed explanation of why using ABP.IO Platform has a significant advantage over doing everything yourself.", + "EulaPageTitle": "End User License Agreement (EULA)", + "PrivacyPolicyPageTitle": "Privacy Policy - Cookie Policy", + "TermsConditionsPageTitle": "Terms and Conditions", + "TrainingsPageTitle": "ABP Training Packages", + "ModulesPageTitle": "ABP Pre-Built Application Modules", + "Volo.AbpIo.Commercial:040001": "API Access Key is incorrect." } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json index 7625f777eb..88ce642703 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/es.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "¿Puedo cambiar los desarrolladores registrados de mi organización en el futuro?", "ChangingDevelopersExplanation": "Además de agregar nuevos desarrolladores a su licencia, también puede cambiar los desarrolladores existentes (puede eliminar un desarrollador y agregar uno nuevo al mismo puesto) sin ningún costo adicional.", "WhenShouldIRenewMyLicense": "¿Cuándo debo renovar mi licencia?", - "WhenShouldIRenewMyLicenseExplanation": "Si renueva su licencia dentro de 1 mes después de su vencimiento, se aplicarán los siguientes descuentos: Licencia de equipo {0}% de descuento, Licencia comercial {1}% de descuento, Licencia empresarial {2}% de descuento . Si renueva su licencia 1 mes después de la fecha de vencimiento de su licencia, el precio de renovación será el mismo que el precio de compra de la licencia y no habrá descuento en su renovación.", + "WhenShouldIRenewMyLicenseExplanation": "Si renueva su licencia dentro de {3} dias después de su vencimiento, se aplicarán los siguientes descuentos: Licencia de equipo {0}% de descuento, Licencia comercial {1}% de descuento, Licencia empresarial {2}% de descuento . Si renueva su licencia {3} dias después de la fecha de vencimiento de su licencia, el precio de renovación será el mismo que el precio de compra de la licencia y no habrá descuento en su renovación.", "TrialPlan": "¿Tiene un plan de prueba?", "DoYouAcceptBankWireTransfer": "¿Aceptan transferencia bancaria?", "DoYouAcceptBankWireTransferExplanation": "Sí, aceptamos transferencia bancaria.
Después de enviar la tarifa de la licencia mediante transferencia bancaria, envíenos un correo electrónico a accounting@abp.io con su recibo y el tipo de licencia solicitada. Nuestra información de cuenta bancaria internacional:", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json index c2b09e4c5b..dc3c374d78 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json @@ -9,15 +9,18 @@ "QuestionCount": "Jäljellä olevat/yhteensä kysymykset", "Unlimited": "Rajoittamaton", "Owners": "Omistajat", + "Owner": "Omistaja", "AddMember": "Lisää jäsen", - "AddOwner": "Lisää omistaja", - "AddDeveloper": "Lisää kehittäjä", + "AddNewOwner": "Lisää uusi omistaja", + "AddNewDeveloper": "Lisää uusi kehittäjä", "UserName": "Käyttäjätunnus", "Name": "Nimi", "EmailAddress": "Sähköpostiosoite", "Developers": "Kehittäjät", "LicenseType": "Lisenssi-tyyppi", "Manage": "Hallitse", + "SetDefault": "Aseta oletukseksi", + "DefaultOrganization": "Oletus", "StartDate": "Aloituspäivämäärä", "EndDate": "Päättymispäivä", "Modules": "Moduulit", @@ -60,8 +63,6 @@ "Themes": "Teemat", "JoinOurNewsletter": "Liity uutiskirjeemme", "Send": "Lähettää", - "Learn": "Oppia", - "AdditionalServices": "Lisäpalvelut", "WhatIsABPFramework": "MIKÄ ON ABP-KEHYS?", "OpenSourceBaseFramework": "Avoimen lähdekoodin peruskehys", "ABPFrameworkExplanation": "

ABP Commercial perustuu ABP Frameworkiin, avoimen lähdekoodin ja yhteisövetoiseen verkkosovelluskehykseen ASP.NET Core -sovellukselle.

ABP Framework tarjoaa erinomaisen infrastruktuurin ylläpitettävien, laajennettavien tiedostojen kirjoittamiseen. ja testattava koodi parhailla käytänteillä.

Rakennettu ja integroitu jo tunnettujen suosittujen työkalujen kanssa. Matala oppimiskäyrä, helppo sopeutuminen, mukava kehitys.

", @@ -127,6 +128,8 @@ "TellUsWhatYouNeed": "Kerro meille mitä tarvitset.", "YourMessage": "Viestisi", "YourFullName": "Koko nimesi", + "FirstNameField": "Etunimi", + "LastNameField": "Sukunimi", "EmailField": "Sähköpostiosoite", "YourEmailAddress": "Sähköpostiosoitteesi", "HowMayWeHelpYou": "Kuinka voimme auttaa sinua?", @@ -141,7 +144,7 @@ "ApplicationTemplateExplanation": "Sovelluksen käynnistysmallia käytetään uuden verkkosovelluksen luomiseen.", "EfCoreProvider": "Entity Framework (tukee SQL Serveriä, MySQL: ää, PostgreSQL: ää, Oracle ja muita )", "AlreadyIncludedInTemplateModules": "Seuraavat moduulit ovat jo mukana ja määritetty tähän malliin:", - "ApplicationTemplateArchitecture": "Tämä sovellusmalli tukee myös porrastettua arkkitehtuuria, jossa käyttöliittymäkerros, API-kerros ja todennuspalvelu on fyysisesti erotettu.", + "ApplicationTemplateArchitecture": "Tämä sovellusmalli tukee myös monikerros arkkitehtuuria, jossa käyttöliittymäkerros, API-kerros ja todennuspalvelu on fyysisesti erotettu.", "SeeTheGuideOrGoToTheLiveDemo": "Katso teknistä tietoa tästä mallista kehittäjän oppaasta tai siirry live-esittelyyn.", "DeveloperGuide": "Kehittäjän opas", "ModuleTemplate": "Moduulimalli", @@ -157,6 +160,8 @@ "SearchQuestionPlaceholder": "Hae usein kysyttyjä kysymyksiä", "WhatIsTheABPCommercial": "Mikä on ABP-kauppa?", "WhatAreDifferencesThanAbpFramework": "Mitä eroja on avoimen lähdekoodin ABP Frameworkilla ja ABP Commercialilla?", + "AbpCommercialMetaTitle": "ABP Commercial - täydellinen verkkokehitysalusta: {0} | ABP Commercial", + "AbpCommercialMetaDescription": "ABP Commercial on joukko valmiita sovellusmoduuleja, nopean kehityksen työkaluja, käyttöliittymäteemoja ja palveluita, jotka on rakennettu avoimen lähdekoodin ABP-kehyksen päälle.", "ABPCommercialExplanation": "ABP Commercial on joukko ensiluokkaisia moduuleja, työkaluja, teemoja ja palveluja, jotka on rakennettu avoimen lähdekoodin ABP-kehyksen päälle. ABP Commercial kehittää ja tukee samaa tiimiä ABP-kehyksen takana.", "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP-kehys on modulaarinen, teemoitettava, mikropalvelujen kanssa yhteensopiva sovelluskehys ASP.NET Core -sovellukselle. Se tarjoaa täydellisen arkkitehtuurin ja vahvan infrastruktuurin, joka saa sinut keskittymään omaan yrityskoodiin sen sijaan, että toistat itsesi jokaisessa uudessa projektissa. Se perustuu ohjelmistokehityksen parhaisiin käytäntöihin ja jo tiedettyihin suosittuihin työkaluihin.

ABP-kehykset ovat täysin ilmaisia, avoimen lähdekoodin ja yhteisölähtöisiä. Se tarjoaa myös ilmaisen teeman ja joitain valmiita moduuleja (esim. Henkilöllisyyden hallinta ja vuokralaisten hallinta).

", "VisitTheFrameworkVSCommercialDocument": "Vieraile seuraavalla linkillä saadaksesi lisätietoja {1} ", @@ -178,6 +183,7 @@ "ChangingLicenseType": "Voinko muuttaa lisenssityyppiäni tulevaisuudessa?", "ChangingLicenseTypeExplanation": "Voit aina lisätä uusia kehittäjiä samaan lisenssityyppiin. Katso myös \"Kuinka monta kehittäjää voi työskennellä ABP Commercialilla?\". Voit myös päivittää korkeampaan lisenssiin maksamalla lasketun hintaeron. Kun päivität korkeampaan lisenssisuunnitelmaan, saat uuden suunnitelman edut, mutta lisenssin päivitys ei muuta lisenssin voimassaolon päättymispäivää.", "LicenseExtendUpgradeDiff": "Mitä eroa on lisenssin laajennuksella ja päivityksellä?", + "LicenseExtendUpgradeDiffExplanation": "Pidentäminen: pidentämällä/uusimalla lisenssiä saat jatkossakin premium-tuen ja saat isoja tai pieniä päivityksiä moduuleille ja teemoille. Lisäksi voit jatkaa uusien projektien luomista. Ja voit silti käyttää ABP Suitea, joka nopeuttaa kehitystäsi. Kun jatkat lisenssiäsi, lisenssin voimassaolopäivään lisätään 1 vuosi.
Päivitys: Päivittämällä lisenssisi siirryt korkeampaan lisenssisuunnitelmaan, jonka avulla saat lisäetuja. Tutustu lisenssien vertailutaulukkoon nähdäksesi erot lisenssisuunnitelmien välillä. Toisaalta, kun päivität, lisenssisi viimeinen voimassaolopäivä ei muutu! Jos haluat pidentää lisenssin päättymispäivää, sinun on jatkettava lisenssiäsi.", "LicenseRenewalCost": "Mitkä ovat lisenssin uusimiskustannukset vuoden kuluttua?", "LicenseRenewalCostExplanation": "Standardin tiimilisenssin uusimishinta (pidennys) on ${0}, normaalin yrityslisenssin hinta on {1} $ ja normaalin yrityslisenssin hinta on {2} $. Jos olet jo asiakas, kirjaudu sisään tiliisi ja tarkista käytettävissä olevat uusimishinnat.", "HowDoIRenewMyLicense": "Kuinka uusin lisenssin?", @@ -189,9 +195,20 @@ "IsSourceCodeIncludedExplanation4": "

Moduulin lähdekoodin sisällyttäminen ratkaisuun antaa sinulle maksimaalisen vapauden mukauttaa moduulia. Tällöin moduulia ei voida päivittää automaattisesti, kun uusi versio julkaistaan.

Mikään lisensseistä ei sisällä ABP Suiten lähdekoodia, joka on ulkoinen työkalu, joka tuottaa koodia sinulle ja auttaa kehitykseen.

Katso muita lisenssityyppien eroja hinnoittelusivulta .

", "ChangingDevelopers": "Voinko muuttaa organisaationi rekisteröityneitä kehittäjiä tulevaisuudessa?", "ChangingDevelopersExplanation": "Uusien kehittäjien lisäämisen lisenssiin lisäksi voit myös muuttaa olemassa olevia kehittäjiä (voit poistaa kehittäjän ja lisätä uuden samalle paikalle) ilman lisäkustannuksia.", + "WhatHappensWhenLicenseEnds": "Mitä tapahtuu, kun lisenssikauteni päättyy?", + "WhatHappensWhenLicenseEndsExplanation1": "ABP Commercial -lisenssi on ikuinen lisenssi. Kun lisenssi päättyy, voit jatkaa projektisi kehittämistä. Etkä ole velvollinen uusimaan lisenssiäsi. Lisenssisi mukana tulee yhden vuoden päivitys- ja tukisuunnitelma suoraan pakkauksesta. Jotta voit jatkossakin saada uusia ominaisuuksia, suorituskykyparannuksia, vikakorjauksia, tukea ja jatkaa ABP Suiten käyttöä, sinun on uusittava lisenssi. Kun lisenssi päättyy;", + "WhatHappensWhenLicenseEndsExplanation2": "Et voi luoda uusia ratkaisuja ABP Commercialin avulla, mutta voit jatkaa olemassa olevien sovellusten kehittämistä ikuisesti.", + "WhatHappensWhenLicenseEndsExplanation3": "Voit saada päivityksiä moduuleihin ja teemoihin MINOR-versiossasi (lukuun ottamatta RC- tai Preview-versioita). Esimerkiksi: jos käytät moduulin v3.2.0 versiota, voit silti saada päivityksiä kyseisen moduulin v3.2.x versiolle (v3.2.1, v3.2.5... jne.). Mutta et voi saada päivityksiä seuraavaan pää- tai pienempään versioon (kuten v3.3.0, v3.3.3.3, 4.x.x.. jne.). Esimerkiksi, kun lisenssisi päättyi, uusin julkaisu oli v4.4.3, ja myöhemmin julkaistiin sekä 4.4.4.4 että 4.5.0-versio, voit käyttää v4.4.X-versiota, mutta et v4.5.X-versiota.", + "WhatHappensWhenLicenseEndsExplanation4": "Et voi asentaa uusia moduuleja ja teemoja, jotka on lisätty ABP Commercial -alustaan lisenssisi päätyttyä.", + "WhatHappensWhenLicenseEndsExplanation5": "Et voi käyttää ABP Suitea.", + "WhatHappensWhenLicenseEndsExplanation6": "Et voi enää saada premium-tukea.", + "WhatHappensWhenLicenseEndsExplanation7": "Voit jatkaa (uusia) lisenssiäsi, jos haluat jatkaa näiden etujen saamista. Jos jatkat lisenssiäsi {3} päivää kuluessa lisenssin vanhenemisesta, seuraavat alennukset sovelletaan: Tiimilisenssi {0}; Toimilupa {1}; Yrityslisenssi {2}.", + "discountForYears": "{0}% de remise pendant {1} an(s)", + "WhatHappensWhenLicenseEndsExplanation8": "Luomiasi ABP-projekteja ei tallenneta palvelimillemme. Siksi on sinun vastuullasi säilyttää lataamasi lähdekoodi. Kun lisenssisi vanhenee, luotua ABP-projektin lähdekoodia ei ole mahdollista saada.", "WhenShouldIRenewMyLicense": "Milloin minun pitäisi uusia lisenssini?", "WhenShouldIRenewMyLicenseExplanation": "Jos uusit lisenssisi 1 kuukauden kuluessa lisenssin vanhenemisesta, seuraavat alennukset sovelletaan: Team License {0} ; Business License {1} ; Enterprise License {2} ; . Jos uusit lisenssisi 1 kuukauden lisenssin päättymispäivän jälkeen, uusimishinta on sama kuin lisenssin ostohinta, eikä uusimisesta saa alennusta.", "TrialPlan": "Onko sinulla kokeilusuunnitelma?", + "TrialPlanExplanation": "Ei, ABP Commercialille ei ole kokeiluversiota. Voit tarkistaa yhteisön versiosta ymmärtääksesi koodin laadun ja lähestymistavat. Tarjoamme myös 30 päivän rahat takaisin -takuun Team-lisenssille ilman kysymyksiä! Voit pyytää hyvitystä ensimmäisten 30 päivän sisällä. Tarjoamme 60 %:n hyvityksen 30 päivän kuluessa Business- ja Enterprise-lisensseistä. Tämä johtuu siitä, että Business- ja Enterprise-lisenssit sisältävät kaikkien moduulien ja teemojen täyden lähdekoodin.", "DoYouAcceptBankWireTransfer": "Hyväksytkö pankkisiirron?", "DoYouAcceptBankWireTransferExplanation": "Kyllä, hyväksymme pankkisiirron.
Kun olet lähettänyt lisenssimaksun pankkisiirrolla, lähetä meille sähköposti osoitteeseen accounting@abp.io kuittisi ja pyydetty lisenssityyppi. Kansainväliset pankkitilitietomme:", "HowToUpgrade": "Kuinka päivittää olemassa olevia sovelluksia, kun uusi versio on saatavilla?", @@ -348,41 +365,462 @@ "WeWillSendYouADownloadLink": "Linkki e-kirjan latausta varten on lähetetty osoitteeseen {0}.
Tarkista postilaatikkosi/roskapostisi/roskapostilaatikot!", "InvalidFormInputs": "Ole hyvä ja kirjoita lomakkeessa ilmoitetut voimassa olevat tiedot.", "DDDBookEmailBody": "Kiitos.
Lataa kirjasi napsauttamalla tätä.", - "FreeDDDEBook": "Ilmainen DDD e-kirja", "StartFree": "Aloita ilmaiseksi", "FreeTrial": "Ilmainen kokeilu", "AcceptsMarketingCommunications": " Kyllä, haluaisin saada ABP Commercial -markkinointiviestintää.", "PurposeOfUsage": "Käytön tarkoitus", - "Industry": "Ala", "Choose": "- Valitse -", "CompanyOrganizationName": "Yrityksen/organisaation nimi", "CompanySize": "Yhtiön koko", "Next": "Seuraava", "StartTrial": "Aloita ilmainen kokeilujaksoni", - "ContactUsIssues": "Ota yhteyttä, jos sinulla on ongelmia", + "ContactUsQuestions": "Ota yhteyttä, jos sinulla on kysyttävää", "TrialActivatedWarning": "Käyttäjällä on oikeus vain yhteen ilmaiseen kokeilujaksoon. Olet jo käyttänyt kokeilujaksosi.", + "ActivationRequirement": "Olet viimeisen askeleen päässä kokeilujakson aloittamisesta.
Tarkistettuasi tietosi aktivoimme lisenssisi. Kun käyttölupasi on aktivoitu, lähetämme sähköpostin osoitteeseen {0}. Älä huoli, tämä prosessi ei vie kauan!", "SaveAndDownload": "Tallenna ja lataa", "CompanyNameValidationMessage": "Yrityksen nimi on liian pitkä!", "AddressValidationMessage": "Osoite on liian pitkä!", "TaxNoValidationMessage": "TAX/ALV-numero on liian pitkä!", "NotesValidationMessage": "Huomautuskenttä on liian pitkä!", "CheckYourBillingInfo": "Voit luoda laskun vain kerran! Tarkista laskutustietosi ennen laskun luomista.", - "Volo.AbpIo.Commercial:030000": "Olet jo käyttänyt kokeilujaksosi.", - "Volo.AbpIo.Commercial:030001": "Tämä organisaation nimi on jo olemassa.", "StartYourFreeTrial": "Aloita ilmainen kokeilujaksosi", "TrialLicenseModelInvalidErrorMessage": "Yksi seuraavista kentistä on virheellinen: maan nimi, yrityksen koko, toimiala tai käyttötarkoitus.", "Trial": "Oikeudenkäynti", "Purchased": "Osti", - "PurchaseLicense": "Osta {0} lisenssi", + "PurchaseNow": "Osta nyt", "PurchaseTrialLicenseMessage": "Lisenssisi viimeinen voimassaolopäivä on {0}.
Jos haluat jatkaa ilmaisen kokeilujakson aikana luomiesi projektien käyttöä, sinun on vaihdettava lisenssiavaimet appsettings.secrets.json-tiedostoissasi. Tässä on lisenssiavaimesi:", "TrialLicenseExpireMessage": "Käytät kokeiluversiota, ja kokeilukäyttölupasi vanhenee {0}.", "TryForFree": "Kokeile ilmaiseksi", "TrialLicenseExpiredInfo": "Kokeilulisenssijaksosi on umpeutunut!", - "CommercialNewsletterConfirmationMessage": "Hyväksyn käyttöehdot ja tietosuojakäytännön .", - "ContinueWithNewOrganization": "Jatka uudessa organisaatiossa", + "DowngradeLicensePlan": "Voinko tulevaisuudessa vaihtaa alempaan lisenssisuunnitelmaan?", + "DowngradeLicensePlanExplanation": "Et voi alentaa olemassa olevaa lisenssisuunnitelmaa. Voit kuitenkin ostaa uuden alemman lisenssisuunnitelman ja jatkaa kehitystyötäsi uudella lisenssillä. Kun olet ostanut alemman lisenssin, sinun tarvitsee vain kirjautua uuteen lisenssisuunnitelmaan ABP CLI -komennolla: ` abp login -o `.", + "LicenseTransfer": "Voiko lisenssin siirtää kehittäjältä toiselle?", + "LicenseTransferExplanation": "Kyllä! Kun ostat lisenssin, sinusta tulee lisenssin haltija, joten sinulla on pääsy organisaation hallintasivulle. Organisaatiolla on omistaja- ja kehittäjäroolit. Omistajat voivat hallita kehittäjäpaikkoja ja määrittää kehittäjiä. Kukin nimetty kehittäjä kirjautuu ABP CLI -komennolla järjestelmään, ja sillä on kehitys- ja tukioikeudet.", + "UserOwnerDescription": "Organisaation 'omistaja' on tämän tilin järjestelmänvalvoja. Hän johtaa organisaatiota ostamalla lisenssejä ja allokoimalla kehittäjiä. 'Omistaja' ei voi kirjoittaa koodia ABP Commercial -projekteihin, ladata ABP-näyteprojekteja eikä esittää kysymyksiä tukisivustolla. Jos haluat tehdä kaikki nämä, sinun on lisättävä itsesi myös kehittäjäksi.", + "UserDeveloperDescription": "'Kehittäjät' voivat kirjoittaa koodia ABP Commercial -projekteihin, ladata ABP-näyteprojekteja ja esittää kysymyksiä tukisivustolla. Toisaalta 'kehittäjät' eivät voi hallita tätä organisaatiota.", + "RemoveCurrentUserFromOrganizationWarningMessage": "Olet poistamassa itsesi omasta organisaatiostasi. Et voi enää hallinnoida tätä organisaatiota, vahvistatko?", + "RenewExistingOrganizationOrCreateNewOneMessage": "Voit uusia organisaatiosi lisenssin napsauttamalla alla olevaa \"Pidennä nyt\" -painiketta, jolloin voit pidentää lisenssin voimassaolopäivää yhdellä vuodella. Jos jatkat maksamista, sinulla on uusi organisaatio. Haluatko jatkaa uudessa organisaatiossa?", + "PurchaseTrialOrganizationOrCreateNewOneMessage": "Sinulla on koekäyttölupa. Voit ostaa kokeiluversion lisenssin napsauttamalla Osta nyt -painiketta. Jos jatkat maksamista, sinulla on uusi organisaatio. Haluatko jatkaa uudessa organisaatiossa?", + "ExtendNow": "Laajenna nyt", + "CreateNewOrganization": "Luo uusi organisaatio", "RenewLicenseEarly": "Jos uusin ajokorttini etuajassa, saanko koko vuoden?", "RenewLicenseEarylExplanation": "Kun uusit lisenssisi ennen lisenssin vanhenemispäivää, lisenssin voimassaolopäivään lisätään 1 vuosi. Jos lisenssisi vanhenee esimerkiksi {0}-06-06 ja uusit sen {0}-01-01, uusi lisenssisi päättymispäivä on {1}-06-06.", - "discountForYears": "{0}% de remise pendant {1} an(s)", - "BlackFridayDiscount": "Black Friday -alennus" + "OpenSourceWebApplication": "Avoimen lähdekoodin verkkosovellus", + "CompleteWebDevelopment": "Täydellinen verkkokehitys", + "ABPFrameworkDescription": "ABP Framework on täydellinen infrastruktuuri nykyaikaisten verkkosovellusten luomiseen noudattamalla ohjelmistokehityksen parhaita käytäntöjä ja käytäntöjä.", + "CommunityDescription": "Jaa kokemuksesi ABP Frameworkista!", + "GetStarted": "Aloita", + "Views": "näkymät", + "LatestPosts": "Uusimmat viestit", + "PreBuiltApplication": "Valmiiksi rakennettu sovellus", + "DatabaseProviders": "Tietokannan tarjoajat", + "UIFrameworks": "UI-kehykset", + "UsefulLinks": "Hyödyllisiä linkkejä", + "Platform": "Alusta", + "CoolestCompaniesUseABPCommercial": "Tyylikkäimmät yritykset käyttävät jo ABP Commercialia.", + "UserInterface": "Käyttöliittymä", + "APIGateway": "API-yhdyskäytävä", + "Microservice": "Mikropalvelu", + "Database": "Tietokanta", + "Architecture": "Arkkitehtuuri", + "MicroserviceArchitectureExplanation": "Tämä on täydellinen ratkaisuarkkitehtuuri, joka koostuu useista sovelluksista, API-yhdyskäytävistä, mikropalveluista ja tietokannoista, jotta voidaan rakentaa skaalautuva mikropalveluratkaisu uusimmalla tekniikalla.", + "BusinessLogic": "Liiketoimintalogiikka", + "DataAccessLayer": "Tietojen käyttökerros", + "Monolith": "Monoliitti", + "ModularArchitectureExplanation": "Tämä käynnistysmalli tarjoaa kerroksellisen, modulaarisen ja DDD-pohjaisen ratkaisuarkkitehtuurin puhtaan ja ylläpidettävän koodikannan luomiseksi.", + "SeeDetails": "Katso yksityiskohdat", + "SeeDocumentation": "Tutustu dokumentaatioon", + "Bs5Compatible": "Bootstrap 5 -yhteensopiva ammattiteema, täydellinen järjestelmänvalvojan verkkosivustollesi.", + "LeptonXTheme": "LeptonX teema", + "LeptonXDark": "LeptonX tumma", + "LeptonXLight": "LeptonX vaalea", + "LeptonXSemiDark": "LeptonX puolitumma", + "BuiltOnBs5Library": "Pohjautuu Bootstrap 5 -kirjastoon", + "FullyCompatibleWithBs5": "100 % yhteensopiva Bootstrap 5:n HTML-rakenteen ja CSS-luokkien kanssa", + "ResponsiveAndMobileCompatible": "Responsiivinen, mobiiliyhteensopiva, RTL-tuki", + "ProvidesStylesForDatatables": "Tarjoaa tyylejä tietotaulukoille", + "MultipleLayoutOptions": "Useita asetteluvaihtoehtoja", + "EasilyInstallAndUpgrade": "Asenna ja päivitä helposti", + "SupportForum": "Tukifoorumi", + "TrustedBy": "Luotettu", + "OurPricing": "Hinnoittelumme", + "Plans": "Suunnitelmat", + "NameSurname": "Nimi sukunimi", + "Unspecified": "Määrittelemätön", + "LicenceType": "Lisenssityyppi", + "LicenseDiscountWarning": "TÄMÄ ALENNUSSIVU KÄYTTÄÄ OLETUSALENNUSKOODIA JA VOLOSOFT-KEHITTÄJILLE. ALLA OLEVAT OSTOLINKIT EIVÄT TOIMI.", + "DiscountedLicenseExplanation": "Nämä lisenssihinnat ovat pienille startup-yrityksille, yksittäisille kehittäjille, opiskelijoille, voittoa tavoittelemattomille järjestöille ja projekteille!", + "General": "Yleiset", + "License": "Lisenssi", + "Development": "Kehitys", + "Payment": "Maksu", + "WatchExplainerVideo": "Tavataan! Katso selittävä video", + "LightDarkAndSemiDarkThemes": "Vaalea, tumma ja puolitumma", + "LeptonXThemeExplanation": "Lepton Theme voi muuttaa teemaasi järjestelmäasetustesi mukaan.", + "PRO": "PRO", + "WelcomeToABPCommercial": "Tervetuloa ABP Commercialiin!", + "YourAccountDetails": "Tilisi tiedot", + "OrganizationName": "Organisaation nimi", + "AddDevelopers": "Lisää kehittäjiä", + "StartDevelopment": "Aloita kehittäminen", + "CreateAndRunApplicationUsingStartupTemplate": "Opi luomaan ja suorittamaan uusi verkkosovellus ABP Commercial -käynnistysmallin avulla.", + "CommunityDescription2": "community.abp.io on paikka, jossa ihmiset voivat jakaa ABP-aiheisia artikkeleita. Etsi artikkeleita, opetusohjelmia, koodinäytteitä, tutkimuksia ja tapaa ihmisiä samalla taajuudella kuin sinä.", + "UseABPSuiteExplanation": "Lataa moduulien ja teemojen lähdekoodi ABP Suiten avulla.", + "ManageModulesWithSuite": "Voit myös hallita ABP-moduulejasi Suiten avulla.", + "LearnHowToInstallSuite": "Opi asentamaan ja käyttämään ABP Suitea.", + "SeeMore": "Katso lisää", + "SeeLess": "Katso Vähemmän", + "LayeredSolutionStructure": "Kerrostettu ratkaisurakenne", + "LayeredSolutionStructureExplanation": "Ratkaisu on kerrostettu Domain Driven Design -periaatteiden ja -mallien perusteella, jotta liiketoimintalogiikkasi voidaan eristää infrastruktuurista ja integraatioista ja maksimoida koodin ylläpidettävyys ja uudelleenkäytettävyys. ABP Framework tarjoaa jo abstraktioita, perusluokkia ja oppaita DDD:n toteuttamiseen sovelluksessasi.", + "MultipleUIOptions": "Useita käyttöliittymävaihtoehtoja", + "MultipleUIOptionsExplanation": "Rakastamme erilaisia tapoja luoda käyttöliittymä. Tämä käynnistysratkaisu tarjoaa kolme erilaista käyttöliittymäkehysvaihtoehtoa yrityssovelluksellesi.", + "MultipleDatabaseOptions": "Useita tietokantavaihtoehtoja", + "MultipleDatabaseOptionsExplanation": "Sinulla on kaksi tietokannan tarjoajavaihtoehtoa (sen lisäksi, että voit käyttää molempia yhdessä sovelluksessa). Käytä Entity Framework Corea työskennelläksesi minkä tahansa relaatiotietokannan kanssa ja käytä valinnaisesti Dapperia, kun sinun on kirjoitettava matalan tason kyselyitä parantaaksesi suorituskykyä. MongoDB on toinen vaihtoehto, jos haluat käyttää dokumenttipohjaista NoSQL-tietokantaa. Vaikka nämä palveluntarjoajat ovat hyvin integroituja, abstrakteja ja esikonfiguroituja, voit itse asiassa olla vuorovaikutuksessa minkä tahansa tietokantajärjestelmän kanssa, jota voit käyttää .NET:n kanssa.", + "ModularArchitectureExplanation2": "Modulaarisuus on ensiluokkainen kansalainen ABP.IO-alustalla. Kaikki sovelluksen toiminnot on jaettu hyvin eristettyihin valinnaisiin moduuleihin. Käynnistysratkaisussa on valmiiksi asennettuna perus ABP Commercial -moduulit. Voit myös luoda omia moduuleita rakentaaksesi modulaarisen järjestelmän omalle sovelluksellesi.", + "MultiTenancyForSaasBusiness": "Monivuokraus SaaS-yrityksellesi", + "MultiTenancyForSaasBusinessExplanation": "ABP Commercial tarjoaa täydellisen, päästä-päähän usean vuokrausjärjestelmän SaaS-järjestelmien (Software-as-a-Service) luomiseen. Sen avulla vuokralaiset voivat jakaa tai käyttää omia tietokantojaan tietokantojen luonti- ja siirtojärjestelmässä.", + "MicroserviceStartupSolution": "Mikropalvelun käynnistysratkaisu", + "MicroserviceArchitectureExplanation2": "Voit hankkia sen seuraavaan mikropalvelujärjestelmääsi hyödyntääksesi valmiiksi rakennetun perusratkaisun ja laadukkaan kokemuksen.", + "PreIntegratedTools": "Esiintegroitu suosittuihin työkaluihin", + "PreIntegratedToolsExplanation": "Ratkaisu on jo integroitu alan standardityökaluihin ja teknologioihin, mutta voit aina muuttaa niitä ja integroida suosikkityökaluihisi.", + "SingleSignOnAuthenticationServer": "Kertakirjautumisen todennuspalvelin", + "SingleSignOnAuthenticationServerExplanation": "Ratkaisussa on todennuspalvelinsovellus, jota muut sovellukset käyttävät kertakirjautumispalvelimena API-käyttöoikeuksien hallintaominaisuuksilla. Se perustuu IdentityServeriin.", + "WebAppsWithGateways": "2 verkkosovellusta kahdella API-yhdyskäytävällä", + "WebAppsWithGatewaysExplanation": "Ratkaisu sisältää kaksi verkkosovellusta, joista jokaisessa on oma API-yhdyskäytävä (BFF - Backend For Frontend -malli).", + "BackOfficeApplication": "Back Office -sovellus", + "BackOfficeApplicationExplanation": "Järjestelmäsi todellinen verkkosovellus, jossa on useita käyttöliittymäkehysvaihtoehtoja. Voit luoda minkä tahansa yrityssovelluksen.", + "LandingWebsite": "Julkinen sivusto", + "LandingWebsiteExplanation": "Yleinen etusivu/julkinen verkkosivusto, jota voidaan käyttää useisiin tarkoituksiin, kuten yrityksesi esittelyyn, tuotteiden myymiseen jne.", + "ABPFrameworkEBook": "Mastering ABP Framework e-kirja", + "MasteringAbpFrameworkEBookDescription": "Sisältyy ABP Commercial -lisenssiisi", + "FullName": "Koko nimi", + "LicenseTypeNotCorrect": "Lisenssityyppi ei ole oikea!", + "Trainings": "Koulutukset", + "ChooseTrainingPlaceholder": "Valitse koulutus...", + "DoYouNeedTrainings": "Tarvitsetko jotain näistä koulutuksista?", + "DoYouNeedTraining": "Tarvitsetko {0} koulutusta?", + "GetInTouchUs": "Ota yhteyttä meihin", + "ForMoreInformationClickHere": "Saat lisätietoja napsauttamalla tätä.", + "IsGetOnboardingTraining": "Haluaisitko perehdytys- ja verkkosovelluskehityskoulutukseen?", + "OnboardingWebApplicationDevelopmentTrainingMessage": "Voit ajoittaa koulutuskalenterisi ottamalla yhteyttä osoitteeseen {0} organisaation luomisen jälkeen", + "CustomPurchaseMessage": "Ota meihin yhteyttä napsauttamalla {0} seuraavaa vaihetta varten.", + "Note": "Huomautus", + "AdditionalNote": "Lisähuomautus", + "OnboardingTrainingFaqTitle": "Onko teillä ABP onboarding -koulutusta?", + "OnboardingTrainingFaqExplanation": "Kyllä, meillä on ABP-koulutuspalvelut, jotka auttavat sinua saamaan ABP-projektisi käyntiin nopeasti. Opit ABP:stä ABP:n ydintiimin jäseneltä ja saat valmiudet aloittaa ABP-projektisi. Perehdytyskoulutuksessa kerromme kuinka perustat kehitysympäristösi, asennat tarvittavat työkalut ja luot täysin toimivan CRUD-sivun. Koulutus toteutetaan livenä ja Zoom-sovellusta käytetään ja olemme avoimia muiden online-kokousalustojen käyttöön. Koulutuksen kieli on englanti. Voit myös esittää kysymyksiäsi ABP:stä istuntojen aikana. Molemmille osapuolille suunnitellaan sopiva aika ja päivämäärä. Saat lisätietoja ottamalla yhteyttä meihin osoitteessa info@abp.io.", + "AddBasket": "Lisää ostoskoriin", + "SendTrainingRequest": "Lähetä koulutuspyyntö", + "OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* Tämän asiakirjan englanninkielinen versio on ajan tasalla ja englanninkielinen versio toimii ensisijaisena kaikissa riita-asioissa.", + "Pricing_Page_Title": "Suunnitelmat ja hinnoittelu", + "Pricing_Page_Description": "Valitse yrityksesi tarvitsemat ominaisuudet ja toiminnot tänään. Osta ABP Commercial -lisenssi ja luo rajattomasti projekteja.", + "Pricing_Page_HurryUp": "Kiirehdi!", + "Pricing_Page_BuyLicense": "Osta lisenssi 2021 hinnoilla 16. tammikuuta asti!", + "Pricing_Page_ValidForExistingCustomers": "Koskee myös nykyisiä asiakkaita ja lisenssien uusimista.", + "Pricing_Page_Hint1": "Lisenssihinta sisältää tietyn määrän kehittäjäpaikkoja. Jos sinulla on enemmän kehittäjiä, voit aina ostaa lisää paikkoja.", + "Pricing_Page_Hint2": "Voit ostaa lisää kehittäjälisenssejä nyt tai tulevaisuudessa. Lisenssit ovat paikkaperusteisia, joten voit siirtää paikan kehittäjältä toiselle.", + "Pricing_Page_Hint3": "Voit kehittää rajoittamattoman määrän erilaisia tuotteita lisenssilläsi.", + "Pricing_Page_Hint4": "ABP Suite on työkalu, joka auttaa kehitystäsi parantamaan tuottavuuttasi. Se tukee CRUD-sivujen luomista ja uusien projektien luomista.", + "Pricing_Page_Hint5": "Voit käyttää kaikkia valmiita moduuleja sovelluksissasi.", + "Pricing_Page_Hint6": "Voit käyttää kaikkia valmiita teemoja sovelluksissasi.", + "Pricing_Page_Hint7": "Käynnistysmalli on Visual Studio -ratkaisu, jonka avulla pääset alkuun projektissasi. Kaikki perusmoduulit on lisätty ja valmiiksi määritetty sinua varten.", + "Pricing_Page_Hint8": "Mastering ABP Framework -e-kirja selittää, kuinka .NET-ratkaisuja toteutetaan parhaiden käytäntöjen avulla. Sitä myydään Amazon.com-sivustolla ja voit ladata kirjan ilmaiseksi lisenssisi puitteissa.", + "Pricing_Page_Hint9": "Voit ladata minkä tahansa moduulin lähdekoodin. Voit halutessasi lisätä lähdekoodin ratkaisuusi tehdäksesi radikaaleja muutoksia tai säilyttää sen itsellesi turvallisuussyistä.", + "Pricing_Page_Hint10": "Lisenssit ovat elinikäisiä. Tämä tarkoittaa, että voit jatkaa sovelluksesi kehittämistä ikuisesti. Uusimman version käyttö ja tuen saaminen myönnetään lisenssijakson sisällä (1 vuosi, ellet uusi sitä).", + "Pricing_Page_Hint11": "Ei rajoituksia käyttöönotolle! Voit ottaa käyttöön niin monelle palvelimelle kuin haluat, mukaan lukien pilvipalvelut tai paikan päällä.", + "Pricing_Page_Hint12": "Voit päivittää moduulit, teemat ja työkalut uusimpaan versioon aktiivisen lisenssijaksosi aikana. Kun lisenssisi vanhenee, sinun on uusittava se, jotta voit jatkaa päivitysten saamista virheenkorjauksista, uusista ominaisuuksista ja parannuksista.", + "Pricing_Page_Hint13": "Voit saada premium-tuen vuodeksi (voit uusia lisenssin jatkaaksesi sitä).", + "Pricing_Page_Hint14": "Team- ja Business-lisensseillä on tapausten/kysymysten määräraja. Jos ostat lisää kehittäjälisenssejä, tapahtumarajasi kasvaa {0} (Tiimilisenssi) tai {1} (Business License) kehittäjää kohden.", + "Pricing_Page_Hint15": "Vain Enterprise License sisältää yksityisen tuen. Voit lähettää sähköpostia suoraan ABP-tiimille tai esittää kysymyksiä osoitteessa support.abp.io yksityisellä lippuvaihtoehdolla. Yksityiset liput eivät ole yleisön nähtävissä.", + "Pricing_Page_Hint16": "Voit ladata kaikkien ABP-teemojen lähdekoodin. Voit halutessasi lisätä lähdekoodin ratkaisuusi tehdäksesi radikaaleja muutoksia tai säilyttää sen itsellesi turvallisuussyistä.", + "Pricing_Page_Testimonial_1": "ABP Commercial antoi SC Venturesille mahdollisuuden toimittaa pankkitason usean vuokralaisen siilotietokanta SaaS-alustan yhdeksässä kuukaudessa tukemaan myyntisaamisten / ostovelkojen toimitusketjun rahoitusta merkittävien arvolaskujen kautta useilta integroiduilta ankkureilta. ABP:n modulaarisuus mahdollisti sen, että tiimi toimitti ennätysajassa, läpäisi kaiken VAPT:n ja ottaa käyttöön konttimuotoisen mikropalvelupinon täyden CI/CD:n ja putkien kautta tuotantoon.", + "Pricing_Page_Testimonial_2": "Näemme ABP Commercialin käytön arvon mukautettujen kehitysprojektien yleiskustannusten vähentämiseen. Ja tiimi pystyy yhdistämään koodimallin eri projektivirroissa. Näemme viitekehyksessä enemmän mahdollisuuksia rakentaa uusia ominaisuuksia nopeammin kuin ennen. Luotamme, että näemme jatkuvasti ABP Commercialin hyödyntämisen arvon.", + "Pricing_Page_Testimonial_3": "Rakastamme ABP:tä. Meidän ei tarvitse kirjoittaa kaikkea tyhjästä. Aloitamme valmiista ominaisuuksista ja keskitymme vain siihen, mitä todella tarvitsemme kirjoittaa. Lisäksi ABP on hyvin suunniteltu ja koodi on korkealaatuista ja siinä on vähemmän bugeja. Jos joutuisimme kirjoittamaan kaiken tarvitsemamme itse, joudumme ehkä viettämään vuosia. Jälleen kerran pidämme siitä, että uusi versio, ongelmankorjaus tai parannus ilmestyy hyvin pian joka toinen viikko. Emme odota liian kauan.", + "Pricing_Page_Testimonial_4": "ABP Commercial on loistava tuote, jota suosittelen. Kaupalliset tuotteet markkinoille asiakkaillemme yhdellä konfiguroitavalla alustalla. Kehyksen ja työkalujen tarjoama aloitus jokaiselle joukkueelle on jokaisen sentin arvoinen. ABP Commercial sopi parhaiten tarpeisiimme.", + "Pricing_Page_Testimonial_5": "ABP Framework ei ole vain viitekehys, vaan se on myös opas projektin kehittämiseen/hallintaan, koska se tarjoaa DDD-, GenericRepository-, DI-, Microservice- ja Modularity-koulutusta. Vaikka et itse käyttäisikään kehystä, voit kehittää itseäsi docs.abp.io:lla, joka on hyvin ja ammattimaisesti valmisteltu (OpenIddict, Redis, Quartz jne.). Koska monet asiat on rakennettu valmiiksi, se lyhentää projektin kehitysaikaa merkittävästi (kuten kirjautumissivu, poikkeusten käsittely, tietojen suodatus, kylvö, tarkastusloki, lokalisointi, automaattinen API-ohjain jne.). Esimerkkinä sovelluksestamme olen käyttänyt paikallista tapahtumaväylää varastonhallintaan. Pystyn siis hallitsemaan tilausliikkeitä kirjoittamalla varastokäsittelijäksi. On hienoa olla hukkaamatta aikaa CreationTimelle, CreatorId:lle. Ne täytetään automaattisesti.", + "AbpBookDownloadArea_ClaimYourEBook": "Lunasta Mastering ABP Framework -e-kirja", + "AddMemberModal_Warning_1": "Jos käyttäjätunnusta, jota yrität lisätä, ei ole järjestelmässä, pyydä tiimisi jäsentä rekisteröitymään osoitteessa {0} ja jakaa hänen tilinsä käyttäjätunnus kanssasi.", + "MyOrganizations_Detail_WelcomeMessage": "Tervetuloa organisaatioosi, {0}", + "MyOrganizations_Detail_OrganizationManagement": "Organisaation hallinta", + "OrganizationDisplayName": "Organisaation näyttönimi", + "MyOrganizations_Detail_EditDisplayName": "Muokkaa näyttönimeä", + "MyOrganizations_Detail_UpgradeYourLicense": "Päivitä lisenssisi", + "MyOrganizations_Detail_LicenseStartAndExpiryDate": "Lisenssin alkamispäivä - viimeinen voimassaolopäivä", + "MyOrganizations_Detail_OwnerRightInfo": "Käytät {0} {1} omistajan oikeuksistasi.", + "MyOrganizations_Detail_CopyApiKey": "Kopioi avain", + "MyOrganizations_Detail_ApiKeyDescription": "API-avain on {1}-palvelussa isännöivien PRO-pakettien tunnus.", + "MyOrganizations_Detail_YourPrivateNugetSource": "Yksityinen NuGet-lähteesi on {0}", + "MyOrganizations_Detail_PrivateNugetSourceWarning": "Tämä lisätään automaattisesti syötteeksi NuGet.Configiin ABP-ratkaisussasi. Älä jaa yksityistä avaintasi luvattomien käyttäjien kanssa!", + "MyOrganizations_Detail_DeveloperSeatInfo": "Käytät {0} {1} kehittäjäpaikastasi.", + "NeedMoreSeatsForYourTeam": "Tarvitsetko lisää paikkoja tiimillesi?", + "MyOrganizations_Detail_PricePerYear": "{0} / vuosi", + "MyOrganizations_Detail_PurchaseDeveloperSeats": "Osta kehittäjäistuimet", + "Invoices": "Laskut", + "RequestInvoice": "Pyydä lasku", + "OrderNumber": "Tilausnumero", + "Date": "Päivämäärä", + "Products": "Tuotteet", + "TotalPrice": "Kokonaishinta", + "ThereIsNoInvoice": "Ei ole laskua", + "MyOrganizations_Detail_PaymentProviderInfo": "Jos olet ostanut käyttöluvan {0}-yhdyskäytävän kautta, se lähettää PDF-laskun sähköpostiosoitteeseesi, katso {0}-laskutus. ", + "MyOrganizations_Detail_PayUInfo": "Jos olet ostanut PayU-yhdyskäytävän kautta, napsauta \"Pyydä lasku\" -painiketta ja täytä laskutustiedot.", + "MyOrganizations_Detail_ConclusionInfo": "Laskupyyntösi saatetaan päätökseen {0} arkipäivän kuluessa.", + "ExtendYourLicense": "Laajenna {0}-lisenssiäsi", + "Continue": "Jatka", + "PurchaseLicense": "Osta {0} lisenssi", + "DownloadInvoiceModal_DownloadInvoice": "Lataa lasku", + "DownloadInvoiceModal_SaveInformationOnlyOnce": "Voit tallentaa laskutustietosi vain kerran.", + "InvoiceModal_EnterCompanyName": "Anna yrityksesi virallinen nimi...", + "InvoiceModal_EnterCompanyAddress": "Anna laillinen yrityksesi osoite...", + "InvoiceModal_EnterTaxNumber": "Anna vero-/alv-numerosi, jos se on saatavilla...", + "RequestInvoiceModal_EnterNotes": "Kirjoita lisäviestisi laskustasi...", + "PrePayment_PayWithIyzico": "Maksat Iyzicolla", + "ContinueToCheckout": "Jatka Checkoutiin", + "PrePayment_IyzicoRedirectionInfo": "Sinut ohjataan Iyzico Payment Gatewaylle viimeistelemään ostoksesi turvallisesti.", + "PrePayment_IyzicoAcceptVisaAndMasterCard": "Iyzico hyväksyy Visa- ja MasterCard-kortit.", + "Purchase": "Osta", + "AcceptTermsAndConditions": "Olen lukenut, ymmärtänyt ja hyväksyn tietosuojakäytännön, käyttöehdot ja EULA:n.", + "AcceptTermsAndConditionsWarningMessage": "Hyväksy tietosuojakäytäntö ja ehdot", + "SelectGatewayToContinue": "Valitse yhdyskäytävä jatkaaksesi!", + "GatewaySelection_SelectGateway": "Valitse maksuyhdyskäytävä", + "GatewaySelection_RedirectionMessage": "Seuraavaksi sinut ohjataan tapahtumaa varten valitun maksuyhdyskäytävän verkkosivustolle.", + "PaymentSucceed_PaymentSuccessMessage": "Maksu suoritettu", + "PaymentSucceed_ThanksForPurchase": "Kiitos ostoksestasi!", + "PaymentSucceed_CreateYourOrganization": "Luo organisaatiosi", + "PaymentSucceed_AddMeAsDeveloper": "Olen myös kehittäjä, lisää minut kehittäjäksi organisaatiooni.", + "PaymentSucceed_CreateOrganization": "Luo organisaatio", + "PaymentSucceed_OrganizationDescription": "Organisaatio koostuu kehittäjistä ja omistajista. Kehittäjät ovat käyttäjiä, jotka kirjoittavat koodia ABP-projektiin ja hyötyvät {1}-verkkosivustosta. Omistajat ovat käyttäjiä, jotka jakavat kehittäjäpaikkoja ja hallinnoivat lisensointia.", + "PaymentSucceed_ViewOrganization": "Napsauta tästä nähdäksesi organisaation", + "Purchase_TotalAnnualPrice": "YHTEENSÄ (vuosimaksu)", + "Purchase_TrainingPrice": "Koulutuksen hinta", + "Purchase_OnboardingTraining": "ABP:n perehdytys ja verkkosovelluskehitys live-koulutus", + "TotalDeveloperPrice": "Kehittäjän kokonaishinta", + "Purchase_PricePerDeveloper": "{0} {1} kehittäjää kohden", + "Purchase_IncludedDeveloperInfo": "{0} {1} mukana.", + "Purchase_LicenseExtraDeveloperPurchaseMessage": "{0} lisenssi sisältää {1} kehittäjää. Voit lisätä uusia kehittäjiä nyt tai myöhemmin.", + "StartupTemplates_Page_Title": "Käynnistysmallit", + "StartupTemplates_Page_Description": "ABP Commercialin avulla voit rakentaa minkä tahansa monimutkaisia ratkaisuja. Se tarjoaa kaksi pääasiallista valmiiksi rakennettua käynnistysratkaisua. Voit valita tarpeitasi vastaavan ja rakentaa oman mukautetun ratkaisun sen päälle.", + "MicroserviceStartupSolutionForDotnet": "Mikropalvelu käynnistysmalli .NET:ille", + "MonolithSolutionForDotnet": "Monoliitti (modulaarinen) Ratkaisu .NET:ille", + "TrainingDetailsHeaderInfo_TrainingHour": "{0} tunti(a)", + "Trainings_Content": "Koulutuksen sisältö", + "Trial_Page_StartYourFreeTrial": "Aloita ilmainen kokeilujaksosi", + "TrialLicenseFeatures": "Voit hyötyä kaikista ABP:n kaupallisista ominaisuuksista", + "TrialPeriodDays": "Sinulla on {0} päivän tiimilisenssi", + "TrialForumSupportIncident": "Sinulla on {0} keskustelupalstan tukitapausta", + "Contact_Page_Title": "Ota yhteyttä ABP:n kehitystiimiin", + "Contact_Page_Description": "Ota yhteyttä ABP:n kehitystiimiin, jos tarvitset apua tai kerro ajatuksesi ja mielipiteesi! ABP-tukitiimi on valmis auttamaan.", + "Demo_Page_Title": "Luo demo", + "Demo_Page_Description": "Luo ilmainen demo nähdäksesi esimerkkisovelluksen, joka on luotu käyttämällä ABP Commercial -käynnistysmallia. Älä toista itseäsi yleisten hakemusvaatimusten suhteen.", + "Discounted_Page_Title": "Alennettu hinnoittelu", + "Discounted_Page_Description": "Valitse yrityksesi tänään tarvitsemat ominaisuudet ja toiminnot. Osta ABP Commercial -lisenssi ja luo rajattomasti projekteja", + "Faq_Page_Title": "Usein kysytyt kysymykset (FAQ)", + "Faq_Page_Description": "Onko sinulla kysymyksiä? Hae usein kysyttyjä kysymyksiä tai kysy meiltä yhteydenottolomakkeella.", + "Faq_Page_SwiftCode": "Swift-koodi", + "Faq_Page_BankName": "Pankin nimi", + "Faq_Page_AccountName": "Tilin nimi", + "Faq_Page_AccountNumber": "Tilinumero", + "Faq_Page_Currency": "Valuutta", + "Faq_Page_VatNumber": "ALV-numero", + "Faq_Page_OtherCurrenciesInfo": "Jos haluat lisätietoja muista valuutoista, katso kaikki tilit", + "ModuleDetail_Page_Title": "Moduulin tiedot - {0}", + "ProjectCreatedSuccess_Page_Title": "Projektisi on luotu", + "ProjectCreatedSuccess_Page_Description": "ABP-projektisi luotu onnistuneesti!", + "Suite_Page_Title": "ABP Suite - Luo CRUD-sivuja", + "Suite_Page_Description": "ABP Commercial tarjoaa nopeat sovelluskehitystyökalut kehittäjien tuottavuuden lisäämiseksi. ABP Suiten avulla voit luoda CRUD-sivuja helposti.", + "Themes_Page_Title": "Modernit ja toimivat käyttöliittymäteemat", + "Themes_Page_Description": "ABP Commercial tarjoaa useita ammattimaisia, moderneja käyttöliittymäteemoja. Luo ilmainen demo nähdäksesi nopeasti, miltä käyttöliittymä näyttää.", + "Tools_Page_Title": "Nopeat sovelluskehitystyökalut", + "Tools_Page_Description": "ABP Commercial tarjoaa nopeat sovelluskehitystyökalut kehittäjien tuottavuuden lisäämiseksi. ABP Suiten avulla voit luoda CRUD-sivuja helposti.", + "DeveloperPrice": "Kehittäjän hinta", + "AdditionalDeveloperPaymentInfoSection_AdditionalDevelopers": "{0} kehittäjät", + "LicenseRemainingDays": " {0} päivän ajan", + "ExtendPaymentInfoSection_Description": "Pidentämällä/uusimalla käyttölupaasi saat edelleen premium-tuen. Voit myös saada suurempia tai pieniä päivityksiä moduuleille ja teemoille. Voit jatkaa uusien projektien luomista. Voit silti käyttää ABP Suitea, joka nopeuttaa kehitystäsi.", + "LicenseRenewalPrice": "Lisenssin uusimisen hinta", + "LicensePrice": "Lisenssin hinta", + "TrialLicensePaymentInfoSection_Description": "Osta käyttölupa: Ostamalla lisenssin saat edelleen premium-tuen. Voit myös saada suurempia tai pieniä päivityksiä moduuleille ja teemoille. Voit jatkaa uusien projektien luomista. Voit edelleen käyttää ABP Suitea, joka nopeuttaa kehitystäsi.
Katso käyttölupien vertailutaulukko, jolla voit tarkistaa lisenssityyppien väliset erot.", + "SelectTargetLicense": "Valitse kohdelisenssi", + "UpgradePaymentInfoSection_ExtendMyLicenseForOneYear": "Kyllä, jatka lisenssini voimassaolopäivää 1 vuodella.", + "UpgradePaymentInfoSection_WantToExtendLicense": "Haluatko jatkaa lisenssiäsi vielä {0} vuodella?", + "UpgradePaymentInfoSection_UpgradingWillNotExtendLicense": "Päivitys ei pidennä lisenssisi vanhenemispäivää!", + "UpgradePaymentInfoSection_LicenseUpgradeDescription": "Päivittämällä lisenssisi ylennät korkeampaan lisenssityyppiin, mikä antaa sinulle lisäetuja. Katso lisenssityyppien väliset erot lisenssivertailutaulukosta.", + "Landing_Page_CustomerStories": "Asiakkaiden tarinoita", + "Landing_Page_OurGreatCustomers": "Hienot asiakkaamme", + "Landing_Page_WebApplicationFramework": "Web Application Framework", + "Landing_Page_WebDevelopmentPlatform": "Web-kehitysalusta", + "Landing_Page_CompleteWebDevelopmentPlatform": "Täydellinen Web-kehitysalusta", + "Landing_Page_TryFreeDemo": "Kokeile ilmaista demoa", + "Landing_Page_StartingPointForWebApplications": "Lähtökohta ASP.NET Core -pohjaisille verkkosovelluksille! Se perustuu parhaan verkkokehityksen ABP-kehykseen.", + "Landing_Page_AbpProvidesSoftwareInfrastructure": "ABP Framework tarjoaa ohjelmistoinfrastruktuurin erinomaisten verkkosovellusten kehittämiseen parhaiden käytäntöjen kanssa.", + "Landing_Page_MicroserviceCompatibleArchitecture": "Microservice-yhteensopiva arkkitehtuuri", + "Landing_Page_PreBuiltApplicationModulesAndThemes": "Valmiiksi rakennetut sovellusmoduulit ja teemat", + "Landing_Page_MultiTenantArchitecture": "Monivuokraus arkkitehtuuri", + "Landing_Page_MultiTenancyDescription": "SaaS-sovellukset on tehty helpoksi! Integroitu monivuokraus tietokannasta käyttöliittymään.", + "Landing_Page_DDDIntroduction": "Suunniteltu ja kehitetty DDD-mallien ja -periaatteiden perusteella. Tarjoaa kerrostetun mallin sovelluksellesi.", + "Landing_Page_CrossCuttingConcernsInfo": "Täydellinen infrastruktuuri valtuutukseen, validointiin, poikkeusten käsittelyyn, välimuistiin, tarkastuslokiin, tapahtumien hallintaan ja muuhun.", + "Landing_Page_PreBuiltApplicationModules": "Valmiiksi rakennetut sovellusmoduulit, jotka sisältävät yleisimmät verkkosovellusvaatimukset.", + "Landing_Page_ChatModule": "Keskustelut", + "Landing_Page_DocsModule": "Asiakirjat", + "Landing_Page_FileManagementModule": "Tiedostonhallinta", + "Landing_Page_CustomerStory_1": "ABP Commercial antoi SC Venturesille mahdollisuuden toimittaa pankkitason usean vuokralaisen siilotietokanta SaaS-alustan yhdeksässä kuukaudessa tukemaan myyntisaamisten / ostovelkojen toimitusketjun rahoitusta merkittävien arvolaskujen kautta useilta integroiduilta ankkureilta. ABP:n modulaarisuus mahdollisti sen, että tiimi toimitti ennätysajassa, läpäisi kaiken VAPT:n ja siirsi konttipohjaisen mikropalvelupinon täyden CI/CD:n ja putkien kautta tuotantoon.", + "Landing_Page_CustomerStory_2": "Näemme ABP Commercialin käytön arvon mukautettujen kehitysprojektien yleiskustannusten vähentämiseen. Ja tiimi pystyy yhdistämään koodimallin eri projektivirroissa. Näemme viitekehyksessä enemmän mahdollisuuksia rakentaa uusia ominaisuuksia nopeammin kuin ennen. Luotamme, että näemme jatkuvasti ABP Commercialin hyödyntämisen arvon.", + "Landing_Page_CustomerStory_3": "Rakastamme ABP:tä. Meidän ei tarvitse kirjoittaa kaikkea tyhjästä. Aloitamme valmiista ominaisuuksista ja keskitymme vain siihen, mitä todella tarvitsemme kirjoittaa. Lisäksi ABP on hyvin suunniteltu ja koodi on korkealaatuista ja siinä on vähemmän bugeja. Jos joutuisimme kirjoittamaan kaiken tarvitsemamme itse, joudumme ehkä viettämään vuosia. Jälleen kerran pidämme siitä, että uusi versio, ongelmankorjaus tai parannus julkaistaan hyvin pian\n joka toinen viikko. Emme odota liian kauan.", + "Landing_Page_CustomerStory_4": "ABP Commercial on loistava tuote, jota suosittelen. Kaupalliset tuotteet markkinoille asiakkaillemme yhdellä konfiguroitavalla alustalla. Kehyksen ja työkalujen tarjoama aloitus jokaiselle joukkueelle on jokaisen sentin arvoinen. ABP Commercial sopi parhaiten tarpeisiimme.", + "Landing_Page_AdditionalServices": "Mukautettu tai volyymilisenssi, perehdytys, suora koulutus ja tuki, mukautettu projektikehitys, olemassa olevien projektien siirtäminen ja paljon muuta...", + "Landing_Page_IncludedDeveloperLicenses": "Mukana {0} kehittäjälisenssi", + "Landing_Page_SeeOnDemo": "Katso Demosta", + "Landing_Page_LeptonThemes": "Lepton-teemat", + "Landing_Page_AccountModuleDescription_1": "Tämä moduuli toteuttaa sovelluksen todennusjärjestelmän;", + "Landing_Page_AccountModuleDescription_2": "Tarjoaa kirjautumissivun, jossa on käyttäjätunnus ja salasana", + "Landing_Page_AccountModuleDescription_3": "Tarjoaa rekisteröintisivun uuden tilin luomista varten.", + "Landing_Page_AccountModuleDescription_4": "Tarjoaa unohdin salasanan -sivun salasanan palautus -linkin lähettämistä varten sähköpostitse.", + "Landing_Page_AccountModuleDescription_5": "Tarjoaa sähköpostivahvistustoiminnon käyttöliittymän kanssa.", + "Landing_Page_AccountModuleDescription_6": "Toteuttaa kaksivaiheisen todennuksen (tekstiviesti ja sähköposti).", + "Landing_Page_AccountModuleDescription_7": "Toteuttaa käyttäjän lukituksen (lukitsee tilin määritetyksi ajaksi, kun tietty määrä epäonnistuneita kirjautumisia tapahtuu virheellisten tunnistetietojen vuoksi tietyn ajanjakson sisällä).", + "Landing_Page_AccountModuleDescription_8": "Toteuttaa Identity Server -todennuspalvelimen käyttöliittymän ja toiminnot.", + "Landing_Page_AccountModuleDescription_9": "Mahdollistaa vaihtamisen vuokralaisten välillä usean vuokralaisen ympäristössä.", + "Landing_Page_AccountModuleDescription_10": "Mahdollistaa sovelluksen käyttöliittymän kielen muuttamisen.", + "Landing_Page_AuditLoggingModuleDescription_1": "Tämä moduuli tarjoaa tarkastuslokin raportoinnin käyttöliittymän valvontainfrastruktuurille. Mahdollistaa tarkastuslokimerkintöjen ja entiteettimuutoslokien etsimisen, suodattamisen ja näyttämisen.", + "Landing_Page_AuditLoggingModuleDescription_2": "Tarkastuslokikirjaus sisältää tärkeitä tietoja jokaisesta asiakaspyynnöstä:", + "Landing_Page_AuditLoggingModuleDescription_3": "URL, selain, IP-osoite, asiakkaan nimi", + "Landing_Page_AuditLoggingModuleDescription_4": "Käyttäjä", + "Landing_Page_AuditLoggingModuleDescription_5": "HTTP-metodi, HTTP-vastauksen tilakoodi", + "Landing_Page_AuditLoggingModuleDescription_6": "Onnistuminen/epäonnistuminen, poikkeustiedot, jos saatavilla", + "Landing_Page_AuditLoggingModuleDescription_7": "Pyynnön suoritusaika", + "Landing_Page_AuditLoggingModuleDescription_8": "Entiteetit on luotu, poistettu tai päivitetty tässä pyynnössä (muuttunein ominaisuuksin).", + "Landing_Page_BloggingModuleDescription_1": "Tämä moduuli lisää yksinkertaisen blogin ABP-sovellukseesi;", + "Landing_Page_BloggingModuleDescription_2": "Mahdollistaa useiden blogien luomisen yhdessä sovelluksessa.", + "Landing_Page_BloggingModuleDescription_3": "Tukee Markdown-formaattia.", + "Landing_Page_BloggingModuleDescription_4": "Mahdollistaa kommentin kirjoittamisen viestiin.", + "Landing_Page_BloggingModuleDescription_5": "Mahdollistaa tunnisteiden liittämisen blogikirjoituksiin.", + "Landing_Page_BloggingModuleDescription_6": "Katso blog.abp.io-verkkosivusto elävänä esimerkkinä blogimoduulista.", + "Landing_Page_ChatModuleDescription_1": "Tätä moduulia käytetään reaaliaikaiseen viestintään sovelluksen käyttäjien välillä.", + "Landing_Page_ChatModuleDescription_2": "Reaaliaikainen viestintä chat-sivulla.", + "Landing_Page_ChatModuleDescription_3": "Hae käyttäjiltä uusia keskusteluja sovelluksessa.", + "Landing_Page_ChatModuleDescription_4": "Yhteystiedot viimeaikaisista keskusteluista.", + "Landing_Page_ChatModuleDescription_5": "Uusi viesti-ilmoitukset, kun käyttäjä katselee toista sivua.", + "Landing_Page_ChatModuleDescription_6": "Lukemattomien viestien kokonaismäärä -merkki valikkokuvakkeessa.", + "Landing_Page_ChatModuleDescription_7": "Lukemattomien viestien määrä jokaisessa keskustelussa.", + "Landing_Page_ChatModuleDescription_8": "Lazy Load -keskustelut.", + "Landing_Page_DocsModuleDescription_1": "Tätä moduulia käytetään teknisen dokumentaation web-sivustojen luomiseen;", + "Landing_Page_DocsModuleDescription_2": "Sisäänrakennettu GitHub-integraatio: Kirjoita ja hallitse asiakirjoja suoraan GitHubissa.", + "Landing_Page_DocsModuleDescription_3": "Versiointi-tuki integroitu suoraan GitHub-julkaisuihin.", + "Landing_Page_DocsModuleDescription_4": "Tukee monia kieltä (varatuki oletuskielelle).", + "Landing_Page_DocsModuleDescription_5": "Tukee Markdown- ja HTML-formaatteja.", + "Landing_Page_DocsModuleDescription_6": "Tarjoaa navigointi- ja ääriviivat-osion.", + "Landing_Page_DocsModuleDescription_7": "Mahdollistaa useiden projektien dokumentaation yhdessä sovelluksessa.", + "Landing_Page_DocsModuleDescription_8": "Linkit tiedostoon GitHubissa, jotta kuka tahansa voi helposti osallistua klikkaamalla Muokkaa-linkkiä.", + "Landing_Page_DocsModuleDescription_9": "GitHub-lähteen lisäksi mahdollistaa tavallisen kansion käytön dokumentaatiolähteenä.", + "Landing_Page_FileManagementModuleDescription_1": "Lataa, lataa ja järjestä tiedostoja hierarkkisessa kansiorakenteessa.", + "Landing_Page_FileManagementModuleDescription_2": "Tätä moduulia käytetään tiedostojen lataamiseen, lataamiseen ja järjestämiseen hierarkkisessa kansiorakenteessa. Se on myös yhteensopiva usean vuokrauksen kanssa, ja voit määrittää vuokralaisten kokonaiskokorajan.", + "Landing_Page_FileManagementModuleDescription_3": "Tämä moduuli perustuu BLOB-tallennusjärjestelmään, joten se voi käyttää eri tallennuspalveluita tiedoston sisällön tallentamiseen.", + "Landing_Page_IdentityModuleDescription_1": "Tämä moduuli toteuttaa sovelluksen käyttäjä- ja roolijärjestelmän;", + "Landing_Page_IdentityModuleDescription_2": "Luotu käyttäen Microsoftin ASP.NET Core Identity -kirjastoa.", + "Landing_Page_IdentityModuleDescription_3": "Hallinnoi rooleja ja käyttäjiä järjestelmässä. Käyttäjällä voi olla useita rooleja.", + "Landing_Page_IdentityModuleDescription_4": "Aseta käyttöoikeudet rooli- ja käyttäjätasoilla.", + "Landing_Page_IdentityModuleDescription_5": "Ota käyttöön tai poista käytöstä kaksivaihetodennus ja käyttäjän lukitus käyttäjää kohti.", + "Landing_Page_IdentityModuleDescription_6": "Hallinnoi perus käyttäjäprofiilia ja salasanaa.", + "Landing_Page_IdentityModuleDescription_7": "Hallitse vaatimustyyppejä järjestelmässä, aseta vaatimuksia rooleille ja käyttäjille.", + "Landing_Page_IdentityModuleDescription_8": "Asetussivu salasanan monimutkaisuuden, käyttäjän kirjautumisen, tilin ja lukituksen hallintaa varten.", + "Landing_Page_IdentityModuleDescription_9": "Tukee LDAP-todennusta.", + "Landing_Page_IdentityModuleDescription_10": "Tarjoaa sähköpostin ja puhelinnumeron vahvistuksen.", + "Landing_Page_IdentityModuleDescription_11": "Tukee sosiaalisen kirjautumisen integraatioita (Twitter, Facebook, GitHub jne.).", + "Landing_Page_IdentityModuleDescription_12": "Hallinnoi järjestelmän organisaatioyksiköitä.", + "Landing_Page_PaymentModuleDescription_1": "Tarjoaa integroinnin eri maksuyhdyskäytäville.", + "Landing_Page_PaymentModuleDescription_2": "Tämä moduuli integroi maksuyhdyskäytäviä, joten saat helposti maksuja asiakkailtasi.", + "Landing_Page_PaymentModuleDescription_3": "Tämä moduuli tukee seuraavia maksuyhdyskäytäviä", + "Welcome_Page_UseSameCredentialForCommercialWebsites": "Käytä samoja kirjautumistietoja sekä commercial.abp.io- että support.abp.io.", + "WatchCrudPagesVideo": "Katso \"CRUD-sivujen luominen ABP Suiten avulla\" -video!", + "WatchGeneratingFromDatabaseVideo": "Katso \"ABP Suite: CRUD-sivujen luominen olemassa olevista tietokantataulukoista\" -video!", + "WatchTakeCloserLookVideo": "Katso \"Katso tarkemmin koodin sukupolvea: ABP Suite\" -video!", + "ConfirmedEmailAddressRequiredToStartTrial": "Sinulla tulee olla vahvistettu sähköpostiosoite, jotta voit aloittaa kokeilukäyttöoikeuden.", + "EmailVerificationMailNotSent": "Sähköpostivahvistusviestiä ei voitu lähettää.", + "GetConfirmationEmail": "Klikkaa tätä saadaksesi vahvistussähköpostin, jos et ole saanut sitä aiemmin.", + "WhichLicenseTypeYouAreInterestedIn": "Mistä lisenssityypistä olet kiinnostunut?", + "DontTakeOurWordForIt": "Älä luota sanaamme...", + "ReadAbpCommercialUsersWantYouToKnow": "Lue, mitä ABP Commercialin käyttäjät haluavat sinun tietävän", + "Testimonial_ShortDescription_1": "ABP:n modulaarisuus mahdollisti tiimin toimituksen ajoissa.", + "Testimonial_ShortDescription_2": "Rakenna uusia ominaisuuksia nopeammin kuin ennen.", + "Testimonial_ShortDescription_3": "Aloitamme valmiista ominaisuuksista ja keskitymme vain siihen, mitä todella tarvitsee kirjoittaa.", + "Testimonial_ShortDescription_4": "ABP Commercial sopi parhaiten tarpeisiimme.", + "OnlineReviewersOnAbpCommercial": "Online-arvostelut ABP Commercialista", + "SeeWhatToldAboutAbpCommercial": "Katso, mitä ABP Commercialista on kerrottu, ja kirjoita ajatuksesi, jos haluat.", + "BlazoriseLicense": "Pitääkö meidän ostaa Blazorise-lisenssi?", + "BlazoriseLicenseExplanation": "Meillä on Volosoftin ja Megabitin välinen sopimus, jonka mukaan Blazorise-lisenssi on niputettu ABP Commercial -tuotteisiin, joten asiakkaidemme ei tarvitse ostaa ylimääräistä Blazorise-lisenssiä.", + "ExtendPaymentInfoSection_DeveloperPrice": "{0}x lisäkehittäjä(ä)", + "ExtendPaymentInfoSection_DiscountRate": "Alennus {0} %", + "TotalNetPrice": "Nettohinta yhteensä", + "EFCore": "Entity Framework Core", + "All": "Kaikki", + "Mvc": "MVC", + "DataBaseProvider": "Tietojen tarjoaja", + "UIFramework": "UI Framework", + "LeptonXThemeForDashboard": "LeptonX-teema järjestelmänvalvojan hallintapaneelillesi", + "AbpPlatform": "ABP-alusta", + "YouDeserveGoodUXUI": "Ansaitset hyvän käyttöliittymän ja paremman UX:n. ABP:n LeptonX Theme on täällä palvelemassa sitä.", + "ViewLiveDemo": "Katso live-teeman esittely", + "GetLeptonX": "Hanki LeptonX nyt", + "SeeLeptonXDocumentation": "Katso LeptonX-dokumentaatio", + "SimplifiedMenu": "Yksinkertaistettu valikko", + "SimplifiedMenuDescription": "Löydät etsimäsi sivun helposti suodattamalla valikkoa", + "YourFavoritePages": "Suosikkisivusi ulottuvillasi", + "YourFavoritePagesDescription": "Lisää tai poista sivu helposti suosikeista napsauttamalla tähtikuvaketta sivun oikeassa yläkulmassa.", + "BreadCrumbs": "Leivänmurut (Breadcrumbs) saumattomaan sivun vaihtamiseen", + "BreadCrumbsDescription": "Breadcrumbin avulla voit siirtyä samalla tasolla oleville sivuille yhdellä napsautuksella, vaikka vasen valikko olisi suljettu, ja se toimii sekä tabletilla että mobiililaitteella responsiivisesti!", + "YourMenu": "Valikko kuten haluat", + "YourMenuDescription": "Mukauta käyttäjävalikon suoraan napsautettavat kuvakkeet ja avattavat ruudut haluamallasi tavalla. Käyttäjävalikko on täysin muokattavissa tarpeidesi mukaan", + "RtlSupport": "RTL-tuki kielellesi", + "RtlSupportDescription": "LeptonX Theme tukee RTL:ää kielelläsi. Kielivaihtoehdot ovat asetusvalikossa, jotta voit vaihtaa kieltä.", + "YourColors": "Värit hallintapaneelin käyttöliittymässä", + "YourColorsDescription": "LeptonX Theme toimii järjestelmäasetustesi mukaan, ja siinä on kojelaudan vaalea teema, kojelaudan tumma teema ja kojelaudan puolitumma teema.", + "ArrangeContentWidth": "Järjestä sisällön leveys helposti", + "ArrangeContentWidthDescription": "Muuta helposti sisältöalueen leveyttä.", + "LeptonXCompatibleWith": "LeptonX-teema on yhteensopiva", + "MobileResponsiveTemplate": "Mobiiliresponsiivinen malli", + "MobileResponsiveTemplateDescription1": "Käytä LeptonX-hallintapaneelia miltä tahansa haluamaltasi laitteelta.", + "MobileResponsiveTemplateDescription2": "Se on suunniteltu käytettäväksi helposti kaikissa laitteissasi. Se on responsiivinen mobiililaitteissa ja tablet-koossa.", + "TopMenuLayoutOption": "Ylävalikon asetteluvaihtoehto", + "TopMenuLayoutOptionDescription1": "Jos haluat määrittää verkkosivustollesi saman järjestelmänvalvojan hallintapaneelin, voit tehdä sen LeptonX-teemalla!", + "TopMenuLayoutOptionDescription2": "Kokeile vain LeptonX:n ylävalikkoasettelua, jotta se tapahtuu!", + "EasilyCustomizable": "Helposti muokattavissa brändisi väreihin", + "EasilyCustomizableDescription1": "Voit mukauttaa LeptonX-teemaa vain muutamalla SCSS-muuttujalla. Ei ohittamista, ei ylimääräistä CSS-kuormaa!", + "EasilyCustomizableDescription2": "LeptonX:n avulla voit järjestää järjestelmänvalvojan kojelautasi haluamallasi tavalla.", + "IndependentLayout": "Itsenäinen ulkoasu ja sisältöalue", + "IndependentLayoutDescription1": "LeptonX:n asetteluinfrastruktuuri suunniteltiin täysin erillään sisällöstä.", + "IndependentLayoutDescription2": "Tämä tarkoittaa, että voit vapaasti suunnitella projektisi muulla sisältörakenteella kuin Bootstrapilla, jos haluat.", + "MostUsedLibraries": "Useimmat käytetyt LeptonX:ään integroidut kirjastot", + "MostUsedLibrariesDescription1": "LeptonX sisältää eniten käytetyt kirjastosi. Sen avulla voit käyttää vaivattomasti kirjastoja, kuten ApexCharts, DataTables, DropZone, FullCalender, JSTree, Select2, Toast.", + "MostUsedLibrariesDescription2": "LeptonX tukee myös MVC Angular- ja Blazor-spesifisiä kirjastoja.", + "CreateAndCustomize": "Luo ja mukauta tarvitsemasi sivut sekunneissa LeptonX mukautetuilla sivuilla", + "CreateAndCustomizeDescription": "Käyttämällä LeptonX-teemaa sinulla on myös pääsy monille valmiiksi tehdyille html-sivuille. Näitä ovat monet sivut, kuten kirjautumissivu, blogi, UKK, tilausluettelo, lasku, hinnoittelu, tiedostojen hallinta.", + "LeptonThemeForAdmin": "Lepton-teema järjestelmänvalvojan hallintapaneelillesi", + "LeptonThemeForAdminDescription": "Lepton-teema on edelleen saatavilla ja sitä ylläpidetään. Jos haluat siirtyä LeptonX-teemaan Lepton-teeman käyttäjänä, voit katsoa ohjeista ohjeita.", + "LeptonCompatibleWith": "Lepton-teema on yhteensopiva", + "BlackFridayDiscount": "Black Friday -alennus", + "UpgradePaymentInfoSection_DeveloperPrice": "{0} {1} lisäkehittäjälle", + "Upgrade": "Päivitä", + "Renewal": "Uusiminen", + "UpgradePaymentInfoSection_LicensePrice": "{0} lisenssi", + "UpgradePaymentInfoSection_LicenseRenewalPrice": "Lisenssin uusiminen", + "Total": "Kaikki yhteensä", + "SupportPolicyFaqTitle": "Mikä on tukipolitiikkanne?", + "SupportPolicyFaqExplanation": "Tuemme vain aktiivista ja edellistä pääversiota. Emme takaa korjausjulkaisua 3. ja vanhemmille pääversioille. Jos aktiivinen versio on esimerkiksi 7.0.0, julkaisemme korjaustiedostoja sekä versioista 6.x.x että 7.x.x. Lisäksi tarjoamme tukea vain ABP Frameworkiin ja ABP Commercialiin liittyville ongelmille. Tämä tarkoittaa, että tukea ei anneta kolmannen osapuolen sovelluksille, pilvipalveluille ja muille ABP-tuotteiden käyttämille oheiskirjastoille. Käytämme kaupallisesti kohtuullisia keinoja tarjotaksemme asiakkaillemme teknistä tukea \"Volosoft Bilisim A.S\"-yhtiön virallisina työaikoina. Toisaalta emme sitoudu palvelutasosopimuksen (SLA) mukaiseen vastausaikaan, mutta pyrimme vastaamaan teknisiin ongelmiin mahdollisimman nopeasti virallisten työaikojemme puitteissa. Ellei asiakkaan kanssa ole tehty erityistä sopimusta, tarjoamme tukea vain osoitteessa https://support.abp.io. Meillä on myös yksityinen sähköpostituki, joka on vain Enterprise-lisenssin haltijoiden käytettävissä.", + "TotalDevelopers": "Yhteensä {0} kehittäjää", + "CustomPurchaseExplanation": "Räätälöity sinun tarpeidesi mukaan", + "WhereDidYouHearAboutUs": "Mistä kuulit meistä?", + "Twitter": "Twitter", + "Facebook": "Facebook", + "Youtube": "YouTube", + "Google": "Google", + "Github": "GitHub", + "Friend": " Ystävältä", + "Other": "Muu", + "WhereDidYouHearAboutUs_explain": "Täsmennä ...", + "DeletingMemberWarningMessage": "\"{0}\" poistetaan kehittäjäluettelosta. Jos haluat, voit määrittää tämän tyhjän paikan myöhemmin toiselle kehittäjälle.", + "AdditionalInfo": "Jos kehittäjäistuimet ylittävät vaatimukset, voit vähentää niitä. Voit poistaa joitain kehittäjäpaikkojasi lähettämällä sähköpostia osoitteeseen info@abp.io. Käyttämättömien kehittäjäpaikkojen tyhjentäminen vähentää lisenssin uusimiskustannuksia. Voit halutessasi ostaa uudelleen lisää kehittäjäpaikkoja aktiivisen lisenssijaksosi aikana. Huomaa, että koska tässä lisenssipaketissa on {0} kehittäjää, et voi vähentää tätä määrää.", + "LinkExpiredErrorMessage": "Linkki, jota yrität käyttää, on vanhentunut.", + "ExpirationDate": "Viimeinen käyttöpäivä", + "SpringCampaignDiscount": "Kevään kampanja-alennus", + "WhyUseAbpIoPlatform": "Miksi minun pitäisi käyttää ABP.IO-alustaa sen sijaan, että luon uuden ratkaisun tyhjästä?", + "WhyUseAbpIoPlatformFaqExplanation": "Katso kyseisestä asiakirjasta yksityiskohtainen selitys siitä, miksi ABP.IO Platformin käyttäminen on huomattavasti edullisempaa kuin kaiken tekeminen itse." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json index 786485ae48..0588062ad8 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Puis-je changer les développeurs enregistrés de mon organisation à l'avenir?", "ChangingDevelopersExplanation": "En plus d'ajouter de nouveaux développeurs à votre licence, vous pouvez également modifier les développeurs existants (vous pouvez supprimer un développeur et en ajouter un nouveau sur le même siège) sans aucun coût supplémentaire.", "WhenShouldIRenewMyLicense": "Quand dois-je renouveler ma licence?", - "WhenShouldIRenewMyLicenseExplanation": "Si vous renouvelez votre licence dans 1 mois après l'expiration de votre licence, les remises suivantes seront appliquées : licence d'équipe {0} ; licence commerciale {1} ; licence d'entreprise {2}. Toutefois, si vous renouveler votre licence après 1 mois depuis la date d'expiration de votre licence, le prix de renouvellement sera le même que le prix d'achat de la licence et il n'y aura pas de remise sur votre renouvellement.", + "WhenShouldIRenewMyLicenseExplanation": "Si vous renouvelez votre licence dans {3} jours après l'expiration de votre licence, les remises suivantes seront appliquées : licence d'équipe {0} ; licence commerciale {1} ; licence d'entreprise {2}. Toutefois, si vous renouveler votre licence après {3} jours depuis la date d'expiration de votre licence, le prix de renouvellement sera le même que le prix d'achat de la licence et il n'y aura pas de remise sur votre renouvellement.", "TrialPlan": "Avez-vous un plan d'essai?", "DoYouAcceptBankWireTransfer": "Acceptez-vous les virements bancaires?", "DoYouAcceptBankWireTransferExplanation": "Oui, nous acceptons les virements bancaires.
Après avoir envoyé les frais de licence par virement bancaire, envoyez-nous par e-mail à accounting@abp.io votre reçu et le type de licence demandé. Nos coordonnées bancaires internationales :", @@ -383,6 +383,24 @@ "RenewLicenseEarly": "Si je renouvelle ma licence plus tôt, obtiendrai-je l'année complète ?", "RenewLicenseEarylExplanation": "Lorsque vous renouvelez votre licence avant la date d'expiration de votre licence, 1 an sera ajouté à la date d'expiration de votre licence. Par exemple, si votre licence expire le {0}-06-06 et que vous la renouvelez le {0}-01-01, la nouvelle date d'expiration de votre licence sera le {1}-06-06.", "discountForYears": "{0} % de remise pendentif {1} an(s)", - "BlackFridayDiscount": "Remise Black Friday" + "BlackFridayDiscount": "Remise Black Friday", + "OnboardingTrainingFaqTitle": "Avez-vous une formation d'accueil ABP ?", + "OnboardingTrainingFaqExplanation": "Oui, nous avons des services de formation ABP pour vous aider à démarrer rapidement votre projet ABP. Vous en apprendrez plus sur l'ABP auprès d'un membre de l'équipe principale d'ABP et vous acquerrez les compétences nécessaires pour commencer votre projet ABP. Dans la formation d'intégration, nous vous expliquerons comment configurer votre environnement de développement, installer les outils requis, créer une page CRUD entièrement fonctionnelle. La formation sera en direct et l'application Zoom sera utilisée, et nous sommes ouverts à l'utilisation d'autres plateformes de réunion en ligne. La langue de la formation sera l'anglais. Vous pouvez également poser vos questions sur l'ABP pendant les séances. Une heure et une date convenables seront prévues pour les deux parties. Pour obtenir plus d'informations, contactez-nous à info@abp.io.", + "SupportPolicyFaqTitle": "Quelle est votre politique de soutien ?", + "SupportPolicyFaqExplanation": "Nous ne prenons en charge que la version active et la version majeure précédente. Nous ne garantissons pas la publication d'un correctif pour la troisième version majeure et la version majeure antérieure. Par exemple, si la version active est 7.0.0, nous publierons des correctifs pour les versions 6.x.x et 7.x.x. En outre, nous n'assurons le support que pour les problèmes liés à ABP Framework et ABP Commercial. Cela signifie qu'aucun support n'est fourni pour les applications tierces, les services cloud et les autres bibliothèques périphériques utilisées par les produits ABP. Nous ferons des efforts commercialement raisonnables pour fournir à nos clients une assistance technique pendant les heures de bureau officielles de \"Volosoft Bilisim A.S\". D'autre part, nous ne nous engageons pas à respecter un accord de niveau de service (SLA) sur le temps de réponse, mais nous essaierons de répondre aux problèmes techniques aussi rapidement que possible pendant nos heures de travail officielles. Sauf accord spécial avec le client, nous ne fournissons une assistance qu'à l'adresse https://support.abp.io. Nous disposons également d'une assistance privée par courrier électronique, qui n'est accessible qu'aux détenteurs d'une licence d'entreprise.", + "DowngradeLicensePlan": "Puis-je passer à un plan de licence inférieur à l'avenir ?", + "DowngradeLicensePlanExplanation": "Vous ne pouvez pas rétrograder votre plan de licence existant. Mais vous pouvez acheter un nouveau plan de licence inférieur et continuer votre développement sur la nouvelle licence. Après avoir acheté une licence inférieure, il vous suffit de vous connecter à votre nouveau plan de licence via la commande CLI d'ABP : ` abp login -o `.", + "LicenseTransfer": "Une licence peut-elle être transférée d'un développeur à un autre ?", + "LicenseTransferExplanation": "Oui ! Lorsque vous achetez une licence, vous en devenez le détenteur et vous avez donc accès à la page de gestion de l'organisation. Une organisation a des rôles de propriétaire et de développeur. Les propriétaires peuvent gérer les sièges des développeurs et leur assigner des développeurs. Chaque développeur assigné se connectera au système via la commande ABP CLI et disposera d'autorisations de développement et de support.", + "WhatHappensWhenLicenseEnds": "Que se passe-t-il à la fin de la période de validité de ma licence ?", + "WhatHappensWhenLicenseEndsExplanation1": "La licence commerciale ABP est une licence perpétuelle. Après l'expiration de votre licence, vous pouvez continuer à développer votre projet. Vous n'êtes pas obligé de renouveler votre licence. Votre licence est livrée avec un plan de mise à jour et d'assistance d'un an. Pour continuer à bénéficier des nouvelles fonctionnalités, des améliorations de performance, des corrections de bogues, de l'assistance et continuer à utiliser ABP Suite, vous devez renouveler votre licence. Lorsque votre licence expire ;", + "WhatHappensWhenLicenseEndsExplanation2": "Vous ne pouvez pas créer de nouvelles solutions à l'aide d'ABP Commercial, mais vous pouvez continuer à développer vos applications existantes pour toujours.", + "WhatHappensWhenLicenseEndsExplanation3": "Vous pourrez obtenir des mises à jour pour les modules et thèmes de votre version MINOR (à l'exception des versions RC ou Preview). Par exemple : si vous utilisez la version 3.2.0 d'un module, vous pouvez toujours obtenir des mises à jour pour la version 3.2.x (v3.2.1, v3.2.5... etc.) de ce module. Mais vous ne pouvez pas obtenir de mises à jour pour la prochaine version majeure ou mineure (comme v3.3.0, v3.3.3, 4.x.x.. etc.). Par exemple, lorsque votre licence a expiré, la dernière version était la v4.4.3, et plus tard, la version 4.4.4 et la version 4.5.0 ont été publiées, vous pourrez accéder à la v4.4.X mais vous ne pourrez pas accéder à la v4.5.X.", + "WhatHappensWhenLicenseEndsExplanation4": "Vous ne pouvez pas installer les nouveaux modules et thèmes ajoutés à la plateforme ABP Commercial après l'expiration de votre licence.", + "WhatHappensWhenLicenseEndsExplanation5": "Vous ne pouvez pas utiliser la suite ABP.", + "WhatHappensWhenLicenseEndsExplanation6": "Vous ne pouvez plus bénéficier du support premium.", + "WhatHappensWhenLicenseEndsExplanation7": "Vous pouvez prolonger (renouveler) votre licence si vous souhaitez continuer à bénéficier de ces avantages. Si vous prolongez votre licence dans {3} jours après l'expiration de votre licence, les remises suivantes seront appliquées : Licence d'équipe {0} ; Licence commerciale {1} ; Licence Entreprise {2}.", + "BlazoriseLicense": "Faut-il acheter une licence Blazorise ?", + "BlazoriseLicenseExplanation": "Nous avons conclu un accord entre Volosoft et Megabit, dans le cadre duquel la licence Blazorise est intégrée aux produits ABP Commercial, de sorte que nos clients n'ont pas besoin d'acheter une licence Blazorise supplémentaire." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json index 1d420c4202..2d16ac121d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "क्या मैं भविष्य में अपने संगठन के पंजीकृत डेवलपर्स को बदल सकता हूं?", "ChangingDevelopersExplanation": "अपने लाइसेंस में नए डेवलपर्स को जोड़ने के अलावा, आप मौजूदा डेवलपर्स को भी बदल सकते हैं (आप एक डेवलपर को हटा सकते हैं और एक ही सीट पर एक नया जोड़ सकते हैं) बिना किसी अतिरिक्त लागत के।", "WhenShouldIRenewMyLicense": "मुझे अपना लाइसेंस कब नवीनीकृत करना चाहिए?", - "WhenShouldIRenewMyLicenseExplanation": "यदि आप अपने लाइसेंस की समय सीमा समाप्त होने के बाद 1 महीने के भीतर अपना लाइसेंस नवीनीकृत करते हैं, तो निम्नलिखित छूटें लागू होंगी: टीम लाइसेंस {0} ; व्यवसाय लाइसेंस {1} ; एंटरप्राइज़ लाइसेंस {2} . यदि आप अपने लाइसेंस की समाप्ति तिथि के बाद 1 महीने अपने लाइसेंस का नवीनीकरण करते हैं, तो नवीनीकरण मूल्य लाइसेंस खरीद मूल्य के समान होगा और आपके नवीनीकरण पर कोई छूट नहीं होगी।", + "WhenShouldIRenewMyLicenseExplanation": "यदि आप अपने लाइसेंस की समय सीमा समाप्त होने के बाद {3} दिन के भीतर अपना लाइसेंस नवीनीकृत करते हैं, तो निम्नलिखित छूटें लागू होंगी: टीम लाइसेंस {0} ; व्यवसाय लाइसेंस {1} ; एंटरप्राइज़ लाइसेंस {2} . यदि आप अपने लाइसेंस की समाप्ति तिथि के बाद {3} दिन अपने लाइसेंस का नवीनीकरण करते हैं, तो नवीनीकरण मूल्य लाइसेंस खरीद मूल्य के समान होगा और आपके नवीनीकरण पर कोई छूट नहीं होगी।", "TrialPlan": "क्या आपके पास एक परीक्षण योजना है?", "DoYouAcceptBankWireTransfer": "क्या आप बैंक वायर ट्रांसफर स्वीकार करते हैं?", "DoYouAcceptBankWireTransferExplanation": "हां, हम बैंक वायर ट्रांसफर स्वीकार करते हैं।
बैंक हस्तांतरण के माध्यम से लाइसेंस शुल्क भेजने के बाद, हमें अपनी रसीद और अनुरोधित लाइसेंस के प्रकार accounting@abp.io पर ईमेल करें। हमारे अंतरराष्ट्रीय बैंक खाते की जानकारी:", @@ -381,5 +381,25 @@ "CommercialNewsletterConfirmationMessage": "मैं नियम और शर्तों और गोपनीयता नीति से सहमत हूं .", "discountForYears": "{1}वर्ष(वर्षों) के लिए {0}% छूट", "BlackFridayDiscount": "ब्लैक फ्राइडे छूट", + "OnboardingTrainingFaqTitle": "क्या आपके पास एबीपी ऑनबोर्डिंग प्रशिक्षण है?", + "OnboardingTrainingFaqExplanation": "हां, आपके एबीपी प्रोजेक्ट को तेजी से शुरू करने में आपकी मदद करने के लिए हमारे पास एबीपी प्रशिक्षण सेवाएं हैं। आप ABP कोर टीम के सदस्य से ABP के बारे में जानेंगे और आपको अपना ABP प्रोजेक्ट शुरू करने का कौशल प्राप्त होगा। ऑनबोर्डिंग प्रशिक्षण में, हम समझाएंगे कि अपने विकास के माहौल को कैसे स्थापित करें, आवश्यक उपकरण कैसे स्थापित करें, पूरी तरह कार्यात्मक CRUD पेज कैसे बनाएं। प्रशिक्षण लाइव होगा और जूम एप्लिकेशन का उपयोग किया जाएगा, और हम अन्य ऑनलाइन मीटिंग प्लेटफॉर्म का उपयोग करने के लिए तैयार हैं। प्रशिक्षण की भाषा अंग्रेजी होगी। आप सत्रों के दौरान ABP के बारे में अपने प्रश्न भी पूछ सकते हैं। दोनों पक्षों के लिए एक सुविधाजनक समय और तारीख की योजना बनाई जाएगी। अधिक जानकारी प्राप्त करने के लिए, info@abp.io पर हमसे संपर्क करें।", + "SupportPolicyFaqTitle": "आपकी समर्थन नीति क्या है?", + "SupportPolicyFaqExplanation": "हम केवल सक्रिय और पिछले प्रमुख संस्करण का समर्थन करते हैं। हम तीसरे और पुराने प्रमुख संस्करणों के लिए पैच रिलीज़ की गारंटी नहीं देते हैं। उदाहरण के लिए, यदि सक्रिय संस्करण 7.0.0 है, तो हम 6.x.x और 7.x.x दोनों के लिए पैच रिलीज़ जारी करेंगे। इसके अलावा, हम केवल ABP फ्रेमवर्क और ABP कमर्शियल संबंधित मुद्दों के लिए समर्थन प्रदान करते हैं। इसका मतलब है कि तीसरे पक्ष के अनुप्रयोगों, क्लाउड सेवाओं और एबीपी उत्पादों द्वारा उपयोग किए जाने वाले अन्य परिधीय पुस्तकालयों के लिए कोई समर्थन नहीं दिया गया है। हम अपने ग्राहकों को \"Volosoft Bilisim A.S\" के आधिकारिक व्यावसायिक घंटों के दौरान तकनीकी सहायता प्रदान करने के लिए व्यावसायिक रूप से उचित प्रयासों का उपयोग करेंगे। दूसरी ओर, हम सर्विस-लेवल एग्रीमेंट (SLA) प्रतिक्रिया समय के लिए प्रतिबद्ध नहीं हैं, लेकिन हम अपने आधिकारिक कामकाजी घंटों के भीतर जितनी जल्दी हो सके तकनीकी मुद्दों पर प्रतिक्रिया देने का प्रयास करेंगे। जब तक ग्राहक के साथ कोई विशेष समझौता नहीं किया जाता है, हम केवल https://support.abp.io पर सहायता प्रदान करते हैं। हमारे पास निजी ईमेल समर्थन भी है, जो केवल एंटरप्राइज़ लाइसेंस धारकों के लिए उपलब्ध है।", + "DowngradeLicensePlan": "क्या मैं भविष्य में कम लाइसेंस योजना में डाउनग्रेड कर सकता हूँ?", + "DowngradeLicensePlanExplanation": "आप अपनी मौजूदा लाइसेंस योजना को डाउनग्रेड नहीं कर सकते। लेकिन आप नया लोअर लाइसेंस प्लान खरीद सकते हैं और नए लाइसेंस पर अपना विकास जारी रख सकते हैं। आपके द्वारा कम लाइसेंस खरीदने के बाद, आपको एबीपी सीएलआई कमांड के माध्यम से अपनी नई लाइसेंस योजना में प्रवेश करने की आवश्यकता है: `एबीपी लॉगिन <उपयोगकर्ता नाम> -ओ <संगठन>`।", + "LicenseTransfer": "क्या लाइसेंस एक डेवलपर से दूसरे डेवलपर को ट्रांसफर किया जा सकता है?", + "LicenseTransferExplanation": "हाँ! जब आप लाइसेंस खरीदते हैं, तो आप लाइसेंस धारक बन जाते हैं, इसलिए आपके पास संगठन प्रबंधन पृष्ठ तक पहुंच होगी। एक संगठन में मालिक और डेवलपर की भूमिकाएँ होती हैं। मालिक डेवलपर सीटों का प्रबंधन कर सकते हैं और डेवलपर्स को असाइन कर सकते हैं। प्रत्येक असाइन किया गया डेवलपर सिस्टम में ABP CLI कमांड के माध्यम से लॉगिन करेगा और उसके पास विकास और समर्थन अनुमतियाँ होंगी।", + "RenewLicenseEarly": "अगर मैं अपना लाइसेंस जल्दी रिन्यू करता हूं, तो क्या मुझे पूरा साल मिलेगा?", + "RenewLicenseEarylExplanation": "जब आप अपनी लाइसेंस समाप्ति तिथि से पहले अपने लाइसेंस का नवीनीकरण करते हैं, तो आपकी लाइसेंस समाप्ति तिथि में 1 वर्ष जोड़ दिया जाएगा। उदाहरण के लिए, यदि आपका लाइसेंस {0}-06-06 को समाप्त हो रहा है और आप इसे {0}-01-01 को नवीनीकृत करते हैं, तो आपकी नई लाइसेंस समाप्ति तिथि {1}-06-06 होगी।", + "WhatHappensWhenLicenseEnds": "मेरी लाइसेंस अवधि समाप्त होने पर क्या होता है?", + "WhatHappensWhenLicenseEndsExplanation1": "ABP वाणिज्यिक लाइसेंस एक स्थायी लाइसेंस है। आपका लाइसेंस समाप्त होने के बाद, आप अपना प्रोजेक्ट विकसित करना जारी रख सकते हैं। और आप अपने लाइसेंस को नवीनीकृत करने के लिए बाध्य नहीं हैं। आपका लाइसेंस एक साल के अपडेट और आउट ऑफ बॉक्स सपोर्ट प्लान के साथ आता है। नई सुविधाएँ, प्रदर्शन संवर्द्धन, बग फिक्स, समर्थन और ABP सूट का उपयोग जारी रखने के लिए, आपको अपने लाइसेंस को नवीनीकृत करने की आवश्यकता है। जब आपका लाइसेंस समाप्त हो जाता है;", + "WhatHappensWhenLicenseEndsExplanation2": "आप ABP कमर्शियल का उपयोग करके नए समाधान नहीं बना सकते, लेकिन आप अपने मौजूदा एप्लिकेशन को हमेशा के लिए विकसित करना जारी रख सकते हैं।", + "WhatHappensWhenLicenseEndsExplanation3": "आप अपने लघु संस्करण (आरसी या पूर्वावलोकन संस्करणों को छोड़कर) के भीतर मॉड्यूल और थीम के लिए अपडेट प्राप्त करने में सक्षम होंगे। उदाहरण के लिए: यदि आप किसी मॉड्यूल के 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": "आप एबीपी सूट का उपयोग नहीं कर सकते।", + "WhatHappensWhenLicenseEndsExplanation6": "अब आप प्रीमियम समर्थन प्राप्त नहीं कर सकते।", + "WhatHappensWhenLicenseEndsExplanation7": "यदि आप इन लाभों को प्राप्त करना जारी रखना चाहते हैं तो आप अपने लाइसेंस का विस्तार (नवीनीकरण) कर सकते हैं। यदि आप अपना लाइसेंस समाप्त होने के बाद {3} दिन के भीतर अपना लाइसेंस बढ़ाते हैं, तो निम्नलिखित छूट लागू होंगी: टीम लाइसेंस {0}; व्यवसाय लाइसेंस {1}; उद्यम लाइसेंस {2}।", + "BlazoriseLicense": "क्या हमें ब्लेज़ोराइज़ लाइसेंस खरीदने की ज़रूरत है?", + "BlazoriseLicenseExplanation": "हमारे पास Volosoft और Megabit के बीच एक समझौता है, इस समझौते के साथ Blazorise लाइसेंस को ABP वाणिज्यिक उत्पादों के साथ बंडल किया गया है, इसलिए हमारे ग्राहकों को एक अतिरिक्त Blazorise लाइसेंस खरीदने की आवश्यकता नहीं है।" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json index b144988670..b481c362e6 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hu.json @@ -160,7 +160,7 @@ "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", + "AbpCommercialMetaTitle": "{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).

", @@ -206,9 +206,9 @@ "discountForYears": "{0}% kedvezmény {1} évre", "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}; Üzleti engedély {1}; Vállalati licenc {2}. Ha azonban a licenc lejárati dátuma óta 1 hónap után megújítja a licencet, 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.", + "WhenShouldIRenewMyLicenseExplanation": "Ha a licenc lejártát követő {3} nap megújítja a licencet, a következő kedvezmények érvényesek: Csapatlicenc {0}; Üzleti engedély {1}; Vállalati licenc {2}. Ha azonban a licenc lejárati dátuma óta 1 hónap után megújítja a licencet, 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.", + "TrialPlanExplanation": "Nem, az ABP Commercialhoz nincs próbaverzió. Tekintse meg a közösségi kiadást, hogy megértse a kód minőségét és megközelítéseit. A Team licencre 30 napos pénz-visszafizetési garanciát is vállalunk, kérdés nélkül! Az első 30 napon belül kérheti a visszatérítést. Üzleti és vállalati licencek esetén 60% visszatérítést biztosítunk 30 napon belül. Ennek az az oka, hogy a Business és Enterprise licencek 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?", @@ -623,7 +623,6 @@ "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", @@ -750,4 +749,4 @@ "TotalNetPrice": "Total Net Price", "BlackFridayDiscount": "Black Friday Kedvezmény" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/is.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/is.json index 7e5c856f4e..ff0c6cc417 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/is.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/is.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Get ég breytt skráðum forriturum fyrirtækisins míns í framtíðinni?", "ChangingDevelopersExplanation": "Til viðbótar við að bæta nýjum verktaka við leyfi þitt geturðu einnig breytt núverandi forriturum (þú getur fjarlægt verktaka og bætt nýjum við) án aukakostnaðar.", "WhenShouldIRenewMyLicense": "Hvenær ætti ég að endurnýja leyfið mitt?", - "WhenShouldIRenewMyLicenseExplanation": "Ef þú endurnýjar leyfið þitt innan eins mánaðar eftir að leyfið þitt rennur út verða eftirfarandi afslættir notaðir: Team Leyfi {0}% afsláttur, Business License {1}% afsláttur, Enterprise License {2}% afsláttur . Ef þú endurnýjar leyfið þitt 1 mánuði eftir að leyfið rennur út, verður endurnýjunarverðið það sama og kaupverð leyfisins og enginn afsláttur af endurnýjun þinni.", + "WhenShouldIRenewMyLicenseExplanation": "Ef þú endurnýjar leyfið þitt innan {3} dagar eftir að leyfið þitt rennur út verða eftirfarandi afslættir notaðir: Team Leyfi {0}% afsláttur, Business License {1}% afsláttur, Enterprise License {2}% afsláttur . Ef þú endurnýjar leyfið þitt {3} dagar eftir að leyfið rennur út, verður endurnýjunarverðið það sama og kaupverð leyfisins og enginn afsláttur af endurnýjun þinni.", "TrialPlan": "Ertu með prufuáætlun?", "DoYouAcceptBankWireTransfer": "Samþykki þið bankamillifærslu?", "DoYouAcceptBankWireTransferExplanation": "Já, við tökum við bankamillifærslu.
Eftir að hafa sent leyfisgjaldið með millifærslu skaltu senda okkur tölvupóst á accounting@abp.io kvittun þína og tegund leyfis sem óskað er eftir. Upplýsingar um alþjóðlega bankareikninginn okkar:", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json index 76637fee4b..ccd74fd005 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Posso cambiare gli sviluppatori registrati della mia organizzazione in futuro?", "ChangingDevelopersExplanation": "Oltre ad aggiungere nuovi sviluppatori alla tua licenza, puoi anche Modifica gli sviluppatori esistenti (puoi rimuovere uno sviluppatore e aggiungerne uno nuovo alla stessa postazione) senza alcun costo aggiuntivo.", "WhenShouldIRenewMyLicense": "Quando devo rinnovare la mia licenza?", - "WhenShouldIRenewMyLicenseExplanation": "Se rinnovi la licenza entro 1 mese dopo la scadenza della licenza, verranno applicati i seguenti sconti: Licenza Team {0} ; Licenza Business {1} ; Licenza Enterprise {2} . Se rinnovi la licenza 1 mese dopo la data di scadenza della licenza, il prezzo di rinnovo sarà lo stesso del prezzo di acquisto della licenza e non ci saranno sconti sul rinnovo.", + "WhenShouldIRenewMyLicenseExplanation": "Se rinnovi la licenza entro {3} giorni dopo la scadenza della licenza, verranno applicati i seguenti sconti: Licenza Team {0} ; Licenza Business {1} ; Licenza Enterprise {2} . Se rinnovi la licenza {3} giorni dopo la data di scadenza della licenza, il prezzo di rinnovo sarà lo stesso del prezzo di acquisto della licenza e non ci saranno sconti sul rinnovo.", "TrialPlan": "Hai un piano di prova?", "DoYouAcceptBankWireTransfer": "Accettate bonifici bancari?", "DoYouAcceptBankWireTransferExplanation": "Sì, accettiamo bonifico bancario.
Dopo aver inviato il canone tramite bonifico bancario, inviaci un'e-mail a accounting@abp.io con la ricevuta e il tipo di licenza richiesta. Le nostre informazioni sul conto bancario internazionale:", @@ -380,6 +380,26 @@ "TrialLicenseExpiredInfo": "Il periodo della tua licenza di prova è scaduto!", "CommercialNewsletterConfirmationMessage": "Accetto i Termini e condizioni e la Informativa sulla privacy .", "discountForYears": "{0}% di sconto per {1} anno/i", - "BlackFridayDiscount": "Sconto Black Friday" + "BlackFridayDiscount": "Sconto Black Friday", + "OnboardingTrainingFaqTitle": "Avete un corso di formazione ABP per l'onboarding?", + "OnboardingTrainingFaqExplanation": "Sì, abbiamo i servizi di formazione ABP per aiutarti a far partire rapidamente il tuo progetto ABP. Imparerai a conoscere ABP da un membro del core team ABP e otterrai le competenze per iniziare il tuo progetto ABP. Nella formazione onboarding, spiegheremo come configurare il tuo ambiente di sviluppo, installare gli strumenti richiesti, creare una pagina CRUD completamente funzionante. La formazione sarà in diretta e verrà utilizzata l'applicazione Zoom e siamo aperti all'utilizzo di altre piattaforme di riunioni online. La lingua della formazione sarà l'inglese. Puoi anche porre le tue domande sull'ABP durante le sessioni. Verrà pianificata una data e un orario convenienti per entrambe le parti. Per ulteriori informazioni, contattaci all'indirizzo info@abp.io.", + "SupportPolicyFaqTitle": "Qual è la vostra politica di assistenza?", + "SupportPolicyFaqExplanation": "Supportiamo solo la versione principale attiva e quella precedente. Non garantiamo il rilascio di patch per la terza e la precedente versione principale. Ad esempio, se la versione attiva è la 7.0.0, rilasceremo patch sia per la 6.x.x che per la 7.x.x. Inoltre, forniamo supporto solo per i problemi relativi ad ABP Framework e ABP Commercial. Ciò significa che non viene fornito alcun supporto per le applicazioni di terze parti, i servizi cloud e altre librerie periferiche utilizzate dai prodotti ABP. Faremo tutto il possibile per fornire ai nostri clienti assistenza tecnica durante gli orari di lavoro ufficiali di Volosoft Bilisim A.S.. D'altra parte, non ci impegniamo a rispettare i tempi di risposta di un accordo sul livello di servizio (SLA), ma cercheremo di rispondere ai problemi tecnici il più rapidamente possibile entro i nostri orari di lavoro ufficiali. A meno che non venga stipulato un accordo speciale con il cliente, forniamo assistenza solo all'indirizzo https://support.abp.io. Disponiamo anche di un supporto privato via e-mail, disponibile solo per i titolari di licenza Enterprise.", + "DowngradeLicensePlan": "Posso passare a un piano di licenza inferiore in futuro?", + "DowngradeLicensePlanExplanation": "Non è possibile effettuare il downgrade del piano di licenza esistente. È però possibile acquistare un nuovo piano di licenza inferiore e continuare lo sviluppo con la nuova licenza. Dopo aver acquistato una licenza inferiore, è sufficiente effettuare il login al nuovo piano di licenza tramite il comando ABP CLI: abp login -o `.", + "LicenseTransfer": "È possibile trasferire una licenza da uno sviluppatore a un altro?", + "LicenseTransferExplanation": "Sì! Quando si acquista una licenza, si diventa il titolare della licenza e quindi si ha accesso alla pagina di gestione dell'organizzazione. Un'organizzazione ha ruoli di proprietario e di sviluppatore. I proprietari possono gestire i posti di sviluppatore e assegnare gli sviluppatori. Ogni sviluppatore assegnato accederà al sistema tramite il comando ABP CLI e avrà i permessi di sviluppo e di assistenza.", + "RenewLicenseEarly": "Se rinnovo la mia licenza in anticipo, avrò l'intero anno?", + "RenewLicenseEarylExplanation": "Quando si rinnova la licenza prima della data di scadenza, viene aggiunto 1 anno alla data di scadenza della licenza. Ad esempio, se la licenza scade il {0}-06-06 e viene rinnovata il {0}-01-01, la nuova data di scadenza della licenza sarà {1}-06-06.", + "WhatHappensWhenLicenseEnds": "Cosa succede quando termina il periodo di licenza?", + "WhatHappensWhenLicenseEndsExplanation1": "La licenza commerciale ABP è una licenza perpetua. Dopo la scadenza della licenza, potete continuare a sviluppare il vostro progetto. Non siete obbligati a rinnovare la licenza. La licenza viene fornita con un piano di aggiornamento e supporto di un anno. Per continuare a ottenere nuove funzionalità, miglioramenti delle prestazioni, correzioni di bug, supporto e continuare a utilizzare ABP Suite, è necessario rinnovare la licenza. Quando la licenza scade;", + "WhatHappensWhenLicenseEndsExplanation2": "Non è possibile creare nuove soluzioni utilizzando ABP Commercial, ma è possibile continuare a sviluppare le applicazioni esistenti per sempre.", + "WhatHappensWhenLicenseEndsExplanation3": "Sarà possibile ottenere gli aggiornamenti per i moduli e i temi della propria versione MINOR (ad eccezione delle versioni RC o Preview). Ad esempio, se si utilizza la versione 3.2.0 di un modulo, è ancora possibile ottenere gli aggiornamenti per la versione 3.2.x (v3.2.1, v3.2.5... ecc.) di quel modulo. Ma non è possibile ottenere aggiornamenti per la versione maggiore o minore successiva (come v3.3.0, v3.3.3, 4.x.x... ecc.). Ad esempio, quando la vostra licenza è scaduta, l'ultima release era la v4.4.3 e in seguito sono state pubblicate sia la versione 4.4.4 che la versione 4.5.0, sarete in grado di accedere alla v4.4.X ma non alla v4.5.X.", + "WhatHappensWhenLicenseEndsExplanation4": "Non è possibile installare nuovi moduli e temi aggiunti alla piattaforma ABP Commercial dopo la scadenza della licenza.", + "WhatHappensWhenLicenseEndsExplanation5": "Non è possibile utilizzare la suite ABP.", + "WhatHappensWhenLicenseEndsExplanation6": "Non è più possibile ottenere il supporto premium.", + "WhatHappensWhenLicenseEndsExplanation7": "Puoi estendere (rinnovare) la tua licenza se desideri continuare a ottenere questi vantaggi. Se estendi la licenza entro {3} giorni dalla scadenza della licenza, verranno applicati i seguenti sconti: Licenza Team {0}; Licenza commerciale {1}; Licenza aziendale {2}.", + "BlazoriseLicense": "Dobbiamo acquistare la licenza di Blazorise?", + "BlazoriseLicenseExplanation": "Abbiamo un accordo tra Volosoft e Megabit, con il quale la licenza di Blazorise viene fornita in bundle con i prodotti commerciali ABP, pertanto i nostri clienti non hanno bisogno di acquistare una licenza Blazorise aggiuntiva." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/nl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/nl.json index b2c2caec7f..cc7a076848 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/nl.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/nl.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Kan ik de geregistreerde ontwikkelaars van mijn organisatie in de toekomst wijzigen?", "ChangingDevelopersExplanation": "Naast het toevoegen van nieuwe ontwikkelaars aan uw licentie, kunt u ook de bestaande ontwikkelaars wijzigen (u kunt een ontwikkelaar verwijderen en een nieuwe toevoegen aan dezelfde stoel) zonder extra kosten.", "WhenShouldIRenewMyLicense": "Wanneer moet ik mijn licentie verlengen?", - "WhenShouldIRenewMyLicenseExplanation": "Als u uw licentie verlengt binnen 1 maand nadat uw licentie is verlopen, worden de volgende kortingen toegepast: Teamlicentie {0}% korting, Zakelijke licentie {1}% korting, Enterprise-licentie {2}% korting . Als u uw licentie 1 maand na de vervaldatum van uw licentie verlengt, is de verlengingsprijs gelijk aan de aankoopprijs van de licentie en wordt er geen korting op uw verlenging gegeven.", + "WhenShouldIRenewMyLicenseExplanation": "Als u uw licentie verlengt binnen {3} dagen nadat uw licentie is verlopen, worden de volgende kortingen toegepast: Teamlicentie {0}% korting, Zakelijke licentie {1}% korting, Enterprise-licentie {2}% korting . Als u uw licentie {3} dagen na de vervaldatum van uw licentie verlengt, is de verlengingsprijs gelijk aan de aankoopprijs van de licentie en wordt er geen korting op uw verlenging gegeven.", "TrialPlan": "Heb je een proefplan?", "DoYouAcceptBankWireTransfer": "Accepteert u bankoverschrijvingen?", "DoYouAcceptBankWireTransferExplanation": "Ja, we accepteren bankoverschrijvingen.
Nadat u de licentievergoeding via bankoverschrijving heeft verzonden, stuurt u een e-mail naar accounting@abp.io met uw kwitantie en het type licentie dat u heeft aangevraagd. Onze internationale bankrekeninggegevens:", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pl-PL.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pl-PL.json index a640fe8f4f..4aa4f6616f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pl-PL.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pl-PL.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Czy mogę w przyszłości zmienić zarejestrowanych programistów mojej organizacji?", "ChangingDevelopersExplanation": "Oprócz dodawania nowych programistów do swojej licencji możesz również zmieniać istniejących programistów (możesz usunąć programistę i dodać nowego do tego samego stanowiska) bez żadnych dodatkowych kosztów.", "WhenShouldIRenewMyLicense": "Kiedy powinienem odnowić licencję?", - "WhenShouldIRenewMyLicenseExplanation": "Jeśli odnowisz licencję w ciągu 1 miesiąca po wygaśnięciu licencji, zostaną zastosowane następujące rabaty: licencja zespołowa {0}% rabatu, licencja biznesowa {1}% rabatu, licencja Enterprise {2}% rabatu . Jeśli odnowisz licencję 1 miesiąc po dacie wygaśnięcia licencji, cena odnowienia będzie taka sama jak cena zakupu licencji i nie będzie rabatu na odnowienie.", + "WhenShouldIRenewMyLicenseExplanation": "Jeśli odnowisz licencję w ciągu {3} dni po wygaśnięciu licencji, zostaną zastosowane następujące rabaty: licencja zespołowa {0}% rabatu, licencja biznesowa {1}% rabatu, licencja Enterprise {2}% rabatu . Jeśli odnowisz licencję 1 miesiąc po dacie wygaśnięcia licencji, cena odnowienia będzie taka sama jak cena zakupu licencji i nie będzie rabatu na odnowienie.", "TrialPlan": "Czy masz plan próbny?", "DoYouAcceptBankWireTransfer": "Czy akceptujesz przelew bankowy?", "DoYouAcceptBankWireTransferExplanation": "Tak, akceptujemy przelew bankowy.
Po wysłaniu opłaty licencyjnej przelewem bankowym wyślij nam e-mail na adres accounting@abp.io z potwierdzeniem odbioru i rodzajem żądanej licencji. Informacje o naszych międzynarodowych kontach bankowych:", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pt-BR.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pt-BR.json index 4dd117e52a..4e06b40112 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pt-BR.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/pt-BR.json @@ -178,6 +178,7 @@ "ChangingLicenseType": "Posso atualizar meu tipo de licença mais tarde?", "ChangingLicenseTypeExplanation": "Você pode atualizar para uma licença superior pagando a diferença dentro do período de sua licença ativa. Ao atualizar para um plano de licença superior, você obtém os benefícios do novo plano, mas a atualização da licença não altera a data de expiração da licença. Além disso, você também pode adicionar novas licenças de desenvolvedor à sua licença existente, consulte \"Quantos desenvolvedores podem trabalhar no comercial ABP?\"", "LicenseExtendUpgradeDiff": "Qual é a diferença entre extensão de licença e atualização?", + "LicenseExtendUpgradeDiffExplanation": "Extensão: ao estender/renovar sua licença, você continuará a obter suporte premium e obter atualizações principais ou secundárias para os módulos e temas. Além disso, você poderá continuar criando novos projetos. E você ainda poderá utilizar o ABP Suite que agiliza seu desenvolvimento. Ao estender sua licença, 1 ano é adicionado à data de expiração da licença.
Atualização: Ao atualizar sua licença, você será promovido para um plano de licença superior que lhe permitirá obter benefícios adicionais. Confira a tabela de comparação de licenças para ver as diferenças entre os planos de licença. Por outro lado, quando você atualizar, a data de expiração de sua licença não será alterada! Para estender a data de término de sua licença, você precisa estender sua licença.", "LicenseRenewalCost": "Qual é o custo de renovação da licença após 1 ano?", "LicenseRenewalCostExplanation": "O preço de renovação (extensão) da Licença de equipe padrão é $ {0}, a Licença comercial padrão é $ {1} e a Licença corporativa padrão é $ {2}. Se você já é um cliente, faça login em sua conta para verificar os preços de renovação disponíveis.", "HowDoIRenewMyLicense": "Como eu renovo minha licença?", @@ -190,7 +191,7 @@ "ChangingDevelopers": "Posso mudar os desenvolvedores registrados da minha organização no futuro?", "ChangingDevelopersExplanation": "Além de adicionar novos desenvolvedores à sua licença, você também pode alterar os desenvolvedores existentes (você pode remover um desenvolvedor e adicionar um novo ao mesmo assento) sem nenhum custo adicional.", "WhenShouldIRenewMyLicense": "Quando devo renovar minha licença?", - "WhenShouldIRenewMyLicenseExplanation": "Se você renovar sua licença em 1 mês após a expiração da licença, os seguintes descontos serão aplicados: Licença de equipe {0} ; Licença de negócios {1} ; Licença empresarial {2} . Se você renovar sua licença 1 mês após a data de expiração de sua licença, o preço de renovação será o mesmo que o preço de compra da licença e não haverá desconto em sua renovação.", + "WhenShouldIRenewMyLicenseExplanation": "Se você renovar sua licença em {3} dias após a expiração da licença, os seguintes descontos serão aplicados: Licença de equipe {0} ; Licença de negócios {1} ; Licença empresarial {2} . Se você renovar sua licença {3} dias após a data de expiração de sua licença, o preço de renovação será o mesmo que o preço de compra da licença e não haverá desconto em sua renovação.", "TrialPlan": "Você tem um plano experimental?", "DoYouAcceptBankWireTransfer": "Você aceita transferência bancária?", "DoYouAcceptBankWireTransferExplanation": "Sim, aceitamos transferência bancária.
Após enviar a taxa de licença por transferência bancária, envie-nos um e-mail para accounting@abp.io com seu recibo e o tipo de licença solicitada. Nossas informações de conta bancária internacional:", @@ -380,6 +381,27 @@ "TrialLicenseExpiredInfo": "Seu período de licença de teste expirou!", "CommercialNewsletterConfirmationMessage": "Eu concordo com os Termos e Condições e a Política de Privacidade ", "discountForYears": "{0}% de desconto por {1} ano(s)", - "BlackFridayDiscount": "Desconto de Black Friday" + "BlackFridayDiscount": "Desconto de Black Friday", + "WhyUseAbpIoPlatform": "Por que devo usar a plataforma ABP.IO em vez de criar uma nova solução a partir do zero?", + "WhyUseAbpIoPlatformFaqExplanation": "Veja esse documento para uma explicação detalhada do porquê de usar a Plataforma ABP.IO tem uma vantagem significativa sobre fazer tudo você mesmo.", + "OnboardingTrainingFaqTitle": "Você tem treinamento de ABP a bordo?", + "SupportPolicyFaqTitle": "Qual é sua política de apoio?", + "SupportPolicyFaqExplanation": "Nós apoiamos apenas a versão ativa e a versão principal anterior. Não garantimos o lançamento de um patch para a 3ª e mais antigas versões principais. Por exemplo, se a versão ativa for a 7.0.0, lançaremos correções tanto para a 6.x.x como para a 7.x.x.x. Além disso, fornecemos suporte somente para questões relacionadas ao ABP Framework e ABP Commercial. Isso significa que nenhum suporte é dado para as aplicações de terceiros, serviços de nuvem e outras bibliotecas periféricas usadas pelos produtos ABP. Usaremos esforços comercialmente razoáveis para fornecer suporte técnico a nossos clientes durante o horário comercial oficial da \"Volosoft Bilisim A.S\". Por outro lado, não nos comprometemos com um acordo de nível de serviço (SLA) de tempo de resposta, mas tentaremos responder às questões técnicas o mais rápido possível dentro de nosso horário oficial de trabalho. A menos que um acordo especial seja feito com o cliente, fornecemos suporte somente em https://support.abp.io. Também temos suporte privado por e-mail, que só está disponível para os detentores de licenças empresariais.", + "DowngradeLicensePlan": "Posso baixar para um plano de licença mais baixo no futuro?", + "DowngradeLicensePlanExplanation": "Você não pode rebaixar seu plano de licença existente. Mas você pode adquirir um novo plano de licença inferior e continuar seu desenvolvimento com a nova licença. Após adquirir uma licença inferior, você só precisa fazer o login em seu novo plano de licença via comando ABP CLI: abp login -o `.", + "LicenseTransfer": "Uma licença pode ser transferida de um desenvolvedor para outro?", + "LicenseTransferExplanation": "Sim! Ao adquirir uma licença, você se torna o detentor da licença, portanto, você terá acesso à página de gerenciamento da organização. Uma organização tem funções de proprietário e desenvolvedor. Os proprietários podem gerenciar os cargos de desenvolvedor e designar desenvolvedores. Cada desenvolvedor designado fará login via comando ABP CLI no sistema e terá permissões de desenvolvimento e suporte.", + "BlazoriseLicense": "Precisamos comprar a licença Blazorise?", + "BlazoriseLicenseExplanation": "Temos um acordo entre a Volosoft e a Megabit, com este acordo a licença Blazorise é agregada aos produtos comerciais da ABP, portanto, nossos clientes não precisam comprar uma licença Blazorise extra.", + "RenewLicenseEarly": "Se eu renovar minha licença mais cedo, terei o ano todo", + "RenewLicenseEarylExplanation": "Quando você renovar sua licença antes da data de expiração, 1 ano será adicionado à data de expiração de sua licença. Por exemplo, se sua licença expirar em {0}-06-06 e você a renovar em {0}-01-01, sua nova data de expiração será {1}-06-06.", + "WhatHappensWhenLicenseEnds": "O que acontece quando meu período de licença termina?", + "WhatHappensWhenLicenseEndsExplanation1": "A licença comercial da ABP é uma licença perpétua. Após a expiração de sua licença, você pode continuar desenvolvendo seu projeto. E você não é obrigado a renovar sua licença. Sua licença vem com uma atualização de um ano e um plano de suporte fora da caixa. A fim de continuar a obter novos recursos, melhorias de desempenho, correção de bugs, suporte e continuar usando o ABP Suite, você precisa renovar sua licença. Quando sua licença expirar;", + "WhatHappensWhenLicenseEndsExplanation2": "Você não pode criar novas soluções usando o ABP Commercial, mas pode continuar desenvolvendo suas aplicações existentes para sempre.", + "WhatHappensWhenLicenseEndsExplanation3": "Você poderá obter atualizações para os módulos e temas dentro de sua versão MINOR (exceto as versões RC ou Preview). Por exemplo: se você estiver usando a v3.2.0 de um módulo, você ainda poderá obter atualizações para a v3.2.x (v3.2.1, v3.2.5... etc.) desse módulo. Mas você não pode obter atualizações para a próxima versão maior ou menor (como v3.3.0, v3.3.3, 4.x.x... etc.). Por exemplo, quando sua licença expirou, a última versão foi a v4.4.3, e mais tarde, publicou tanto a versão 4.4.4 quanto a 4.5.0, você poderia acessar a v4.4.X, mas não poderia acessar a v4.5.X.", + "WhatHappensWhenLicenseEndsExplanation4": "Você não pode instalar novos módulos e temas adicionados à plataforma comercial da ABP após o término de sua licença.", + "WhatHappensWhenLicenseEndsExplanation5": "Você não pode usar o ABP Suite", + "WhatHappensWhenLicenseEndsExplanation6": "Você não pode mais obter o apoio de premium.", + "WhatHappensWhenLicenseEndsExplanation7": "Você pode estender (renovar) sua licença se quiser continuar obtendo esses benefícios. Se você estender sua licença dentro de {3} dias após a expiração de sua licença, os seguintes descontos serão aplicados: Licença de equipe {0}; Licença Comercial {1}; Licença empresarial {2}." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ro-RO.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ro-RO.json index c1eeae7448..358bd87038 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ro-RO.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ro-RO.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Pe viitor, pot schimba dezvoltatorii ataşaţi organizaţiei mele?", "ChangingDevelopersExplanation": "În plus faţă de adăugarea de noi dezvoltatori la licenţa dumneavoastră, puteţi schimba şi dezvoltatorii existenţi(puteţi elimina un dezvoltator şi adăuga unul nou în locul lui) fără costuri suplimentare.", "WhenShouldIRenewMyLicense": "Când ar trebui să-mi reînnoiesc licenţa?", - "WhenShouldIRenewMyLicenseExplanation": "Dacă vă reînnoiți licența în termen de o lună de la expirarea licenței, se vor aplica următoarele reduceri: Licență de echipă {0}% reducere, Licență de afaceri {1}% reducere, Licență Enterprise {2}% reducere . Dacă vă reînnoiți licența la 1 lună după data de expirare a licenței, prețul de reînnoire va fi același cu prețul de achiziție a licenței și nu va exista nicio reducere la reînnoirea dvs.", + "WhenShouldIRenewMyLicenseExplanation": "Dacă vă reînnoiți licența în termen de {3} de zile de la expirarea licenței, se vor aplica următoarele reduceri: Licență de echipă {0}% reducere, Licență de afaceri {1}% reducere, Licență Enterprise {2}% reducere . Dacă vă reînnoiți licența la {3} de zile după data de expirare a licenței, prețul de reînnoire va fi același cu prețul de achiziție a licenței și nu va exista nicio reducere la reînnoirea dvs.", "TrialPlan": "Aveţi un plan de încercare?", "DoYouAcceptBankWireTransfer": "Acceptaţi transfer bancar?", "DoYouAcceptBankWireTransferExplanation": "Da, acceptăm transfer bancar.
După ce ați trimis taxa de licență prin transfer bancar, trimiteți-ne un e-mail la accounting@abp.io chitanța dvs. și tipul de licență solicitat. Informațiile noastre internaționale despre contul nostru bancar:", @@ -381,4 +381,4 @@ "CommercialNewsletterConfirmationMessage": "Sunt de acord cu Termenii și condițiile și cu Politica de confidențialitate .", "BlackFridayDiscount": "Black Friday Discount" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ru.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ru.json index 7a0427083e..8c53bc9ea8 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ru.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/ru.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Могу ли я сменить зарегистрированных разработчиков моей организации в будущем?", "ChangingDevelopersExplanation": "Помимо добавления новых разработчиков к вашей лицензии, вы также можете изменить существующих разработчиков (вы можете удалить разработчика и добавить нового на то же место) без каких-либо дополнительных затрат.", "WhenShouldIRenewMyLicense": "Когда мне следует продлить лицензию?", - "WhenShouldIRenewMyLicenseExplanation": "Если вы продлите лицензию в течение 1 месяца после истечения срока действия лицензии, будут применяться следующие скидки: групповая лицензия {0}% скидка, бизнес-лицензия {1}% скидка, корпоративная лицензия {2}% скидка . Если вы продлеваете лицензию через 1 месяц после даты истечения срока действия лицензии, цена продления будет такой же, как цена покупки лицензии, и при продлении скидки не будет.", + "WhenShouldIRenewMyLicenseExplanation": "Если вы продлите лицензию в течение {3} дней после истечения срока действия лицензии, будут применяться следующие скидки: групповая лицензия {0}% скидка, бизнес-лицензия {1}% скидка, корпоративная лицензия {2}% скидка . Если вы продлеваете лицензию {3} дней после даты истечения срока действия лицензии, цена продления будет такой же, как цена покупки лицензии, и при продлении скидки не будет.", "TrialPlan": "У вас есть пробный план?", "DoYouAcceptBankWireTransfer": "Вы принимаете банковский перевод?", "DoYouAcceptBankWireTransferExplanation": "Да, мы принимаем банковский перевод.
После отправки платы за лицензию банковским переводом отправьте нам электронное письмо по адресу accounting@abp.io, квитанцию и тип запрошенной лицензии. Информация о нашем международном банковском счете:", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json index 0bb203a2ca..b0a6d6490e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Môžem v budúcnosti zmeniť registrovaných vývojárov svojej organizácie?", "ChangingDevelopersExplanation": "Okrem pridávania nových vývojárov do licencie môžete tiež meniť existujúcich vývojárov (môžete odstrániť vývojára a pridať nového na to isté miesto) bez akýchkoľvek ďalších nákladov.", "WhenShouldIRenewMyLicense": "Kedy si mám obnoviť licenciu?", - "WhenShouldIRenewMyLicenseExplanation": "Ak si licenciu obnovíte do 1 mesiaca po vypršaní platnosti licencie, budú sa uplatňovať tieto zľavy: Tímová licencia {0} ; Obchodná licencia {1} ; Enterprise licencia {2} . Ak si licenciu obnovíte 1 mesiac po dátume skončenia platnosti licencie, cena za obnovenie bude rovnaká ako cena za nákup licencie a na obnovenie nebude poskytnutá žiadna zľava.", + "WhenShouldIRenewMyLicenseExplanation": "Ak si licenciu obnovíte do 30 dní po vypršaní platnosti licencie, budú sa uplatňovať tieto zľavy: Tímová licencia {0} ; Obchodná licencia {1} ; Enterprise licencia {2} . Ak si licenciu obnovíte 30 dní po dátume skončenia platnosti licencie, cena za obnovenie bude rovnaká ako cena za nákup licencie a na obnovenie nebude poskytnutá žiadna zľava.", "TrialPlan": "Máte skúšobný plán?", "DoYouAcceptBankWireTransfer": "Prijímate bankový prevod?", "DoYouAcceptBankWireTransferExplanation": "Áno, akceptujeme bankový prevod.
Po odoslaní licenčného poplatku bankovým prevodom nám pošlite e-mail na adresu accounting@abp.io svoje potvrdenie a požadovaný typ licencie. Informácie o našom medzinárodnom bankovom účte:", @@ -380,6 +380,24 @@ "TrialLicenseExpiredInfo": "Vaše skúšobné licenčné obdobie vypršalo!", "CommercialNewsletterConfirmationMessage": "I agree to the Terms & Conditions and Privacy Policy.", "discountForYears": "{0} % odpusteného prívesku {1} an(ov)", - "BlackFridayDiscount": "Black Friday zľava" + "BlackFridayDiscount": "Black Friday zľava", + "OnboardingTrainingFaqTitle": "Máte školenie ABP onboarding?", + "OnboardingTrainingFaqExplanation": "Áno, máme školiace služby ABP, ktoré vám pomôžu rýchlo spustiť váš projekt ABP. Dozviete sa o ABP od hlavného člena tímu ABP a získate zručnosti na začatie vášho projektu ABP. Na onboardingovom školení vám vysvetlíme, ako nastaviť vaše vývojové prostredie, nainštalovať požadované nástroje, vytvoriť plne funkčnú stránku CRUD. Školenie bude prebiehať naživo a bude sa používať aplikácia Zoom a sme otvorení využívaniu ďalších platforiem online stretnutí. Jazykom školenia bude angličtina. Svoje otázky o ABP môžete klásť aj počas sedení. Pre obe strany bude naplánovaný vhodný čas a dátum. Ak chcete získať ďalšie informácie, kontaktujte nás na adrese info@abp.io.", + "SupportPolicyFaqTitle": "Aká je vaša politika podpory?", + "SupportPolicyFaqExplanation": "Podporujeme iba aktívnu a predchádzajúcu hlavnú verziu. Nezaručujeme vydanie opravy pre 3. a staršiu hlavnú verziu. Napríklad, ak je aktívna verzia 7.0.0, vydáme opravné verzie pre 6.x.x aj 7.x.x. Okrem toho poskytujeme podporu len pre problémy súvisiace s ABP Framework a ABP Commercial. To znamená, že neposkytujeme podporu pre aplikácie tretích strán, cloudové služby a iné periférne knižnice používané produktmi ABP. Vynaložíme komerčne primerané úsilie, aby sme našim zákazníkom poskytli technickú podporu počas oficiálnej pracovnej doby spoločnosti \"Volosoft Bilisim A.S\". Na druhej strane sa nezaväzujeme k času odozvy podľa dohody o úrovni služieb (SLA), ale budeme sa snažiť reagovať na technické problémy čo najrýchlejšie v rámci našej oficiálnej pracovnej doby. Ak sa so zákazníkom nedohodnete inak, poskytujeme podporu len na adrese https://support.abp.io. Máme aj súkromnú e-mailovú podporu, ktorá je k dispozícii len držiteľom licencie Enterprise.", + "DowngradeLicensePlan": "Môžem v budúcnosti prejsť na nižší licenčný plán?", + "DowngradeLicensePlanExplanation": "Existujúci licenčný plán nemôžete znížiť. Môžete si však zakúpiť nový nižší licenčný plán a pokračovať vo vývoji s novou licenciou. Po zakúpení nižšej licencie sa stačí prihlásiť do nového licenčného plánu prostredníctvom príkazu ABP CLI: abp login -o `.", + "LicenseTransfer": "Môže sa licencia preniesť z jedného vývojára na druhého?", + "LicenseTransferExplanation": "Áno! Keď si zakúpite licenciu, stanete sa jej držiteľom, a teda budete mať prístup na stránku správy organizácie. Organizácia má roly vlastníka a vývojára. Vlastníci môžu spravovať miesta pre vývojárov a priraďovať vývojárov. Každý pridelený vývojár sa prihlási do systému prostredníctvom príkazu ABP CLI a bude mať oprávnenia na vývoj a podporu.", + "WhatHappensWhenLicenseEnds": "Čo sa stane po skončení licenčného obdobia?", + "WhatHappensWhenLicenseEndsExplanation1": "Komerčná licencia ABP je trvalá licencia. Po skončení platnosti licencie môžete pokračovať vo vývoji svojho projektu. A nie ste povinní licenciu obnovovať. Vaša licencia sa dodáva s ročným plánom aktualizácií a podpory už v balení. Ak chcete naďalej získavať nové funkcie, vylepšenia výkonu, opravy chýb, podporu a pokračovať v používaní balíka ABP Suite, musíte svoju licenciu obnoviť. Keď vaša licencia vyprší;", + "WhatHappensWhenLicenseEndsExplanation2": "Pomocou ABP Commercial nemôžete vytvárať nové riešenia, ale môžete navždy pokračovať vo vývoji existujúcich aplikácií.", + "WhatHappensWhenLicenseEndsExplanation3": "Budete môcť získať aktualizácie modulov a tém v rámci svojej verzie MINOR (okrem verzií RC alebo Preview). Napríklad: ak používate modul vo verzii 3.2.0, stále môžete získať aktualizácie pre verziu 3.2.x (v3.2.1, v3.2.5... atď.) tohto modulu. Nemôžete však získať aktualizácie pre ďalšiu hlavnú alebo vedľajšiu verziu (napríklad v3.3.0, v3.3.3, 4.x.x... atď.). Napríklad, keď vám vypršala licencia, posledná verzia bola v4.4.3 a neskôr bola zverejnená verzia 4.4.4 aj 4.5.0, budete mať prístup k verzii v4.4.X, ale nebudete mať prístup k verzii v4.5.X.", + "WhatHappensWhenLicenseEndsExplanation4": "Po skončení platnosti vašej licencie nemôžete inštalovať nové moduly a témy pridané do platformy ABP Commercial.", + "WhatHappensWhenLicenseEndsExplanation5": "Balík ABP Suite nemôžete používať.", + "WhatHappensWhenLicenseEndsExplanation6": "Už nemôžete získať prémiovú podporu.", + "WhatHappensWhenLicenseEndsExplanation7": "Ak chcete naďalej využívať tieto výhody, môžete si licenciu predĺžiť (obnoviť). Ak predĺžite svoju licenciu do 30 dní po vypršaní platnosti licencie, budú sa uplatňovať nasledujúce zľavy: Tímová licencia {0}; Obchodná licencia {1}; Podniková licencia {2}", + "BlazoriseLicense": "Musíme si kúpiť licenciu Blazorise?", + "BlazoriseLicenseExplanation": "Máme dohodu medzi spoločnosťami Volosoft a Megabit, na základe ktorej je licencia Blazorise pribalená k produktom ABP Commercial, preto si naši zákazníci nemusia kupovať ďalšiu licenciu Blazorise." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json index 5abffa2189..85acb70552 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sl.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Ali lahko v prihodnosti spremenim registrirane razvijalce svoje organizacije?", "ChangingDevelopersExplanation": "Poleg dodajanja novih razvijalcev vaši licenci lahko spremenite tudi obstoječe razvijalce (lahko odstranite razvijalca in dodate novega na isto mesto) brez dodatnih stroškov.", "WhenShouldIRenewMyLicense": "Kdaj naj podaljšam licenco?", - "WhenShouldIRenewMyLicenseExplanation": "Če licenco podaljšate v 1 mesecu po poteku licence, bodo uporabljeni naslednji popusti: licenca za ekipo {0} % popusta, poslovna licenca {1} % popusta, licenca za podjetje {2} % popusta . Če podaljšate licenco 1 mesec po datumu poteka veljavnosti licence, bo cena podaljšanja enaka nakupni ceni licence in pri podaljšanju ne bo popusta.", + "WhenShouldIRenewMyLicenseExplanation": "Če licenco podaljšate v {3} dni po poteku licence, bodo uporabljeni naslednji popusti: licenca za ekipo {0} % popusta, poslovna licenca {1} % popusta, licenca za podjetje {2} % popusta . Če podaljšate licenco {3} dni po datumu poteka veljavnosti licence, bo cena podaljšanja enaka nakupni ceni licence in pri podaljšanju ne bo popusta.", "TrialPlan": "Ali imate poskusni načrt?", "DoYouAcceptBankWireTransfer": "Ali sprejemate bančno nakazilo?", "DoYouAcceptBankWireTransferExplanation": "Da, sprejemamo bančno nakazilo.
Ko pošljete licenčnino prek bančnega nakazila, nam pošljite e-pošto na accounting@abp.io vaše potrdilo o prejemu in vrsto zahtevane licence. Naši podatki o mednarodnem bančnem računu:", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json index 9a21d6de0a..c40b46f3c1 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json @@ -193,7 +193,7 @@ "ChangingDevelopers": "Gelecekte kuruluşumun kayıtlı geliştiricilerini değiştirebilir miyim?", "ChangingDevelopersExplanation": "Lisansınıza yeni geliştiriciler eklemenin yanı sıra mevcut geliştiricileri de herhangi bir ek ücret ödemeden değiştirebilirsiniz (bir geliştiriciyi kaldırıp aynı koltuğa yeni bir tane ekleyebilirsiniz).", "WhenShouldIRenewMyLicense": "Ehliyetimi ne zaman yenilemeliyim?", - "WhenShouldIRenewMyLicenseExplanation": "Lisansınızın süresi dolduktan sonra 1 ay içinde lisansınızı yenilerseniz, aşağıdaki indirimler uygulanacaktır: Takım Lisansı %{0} indirim, İşletme Lisansı %{1} indirim, Kurumsal Lisans %{2} indirim . Lisansınızın sona erme tarihinden 1 ay sonra lisansınızı yenilerseniz, yenileme fiyatı lisans satın alma fiyatı ile aynı olacak ve yenilemenizde indirim yapılmayacaktır.", + "WhenShouldIRenewMyLicenseExplanation": "Lisansınızın süresi dolduktan sonra {3} gün içinde lisansınızı yenilerseniz, aşağıdaki indirimler uygulanacaktır: Takım Lisansı %{0} indirim, İşletme Lisansı %{1} indirim, Kurumsal Lisans %{2} indirim . Lisansınızın sona erme tarihinden {3} gün sonra lisansınızı yenilerseniz, yenileme fiyatı lisans satın alma fiyatı ile aynı olacak ve yenilemenizde indirim yapılmayacaktır.", "TrialPlan": "Deneme planınız var mı?", "DoYouAcceptBankWireTransfer": "Banka havalesini kabul ediyor musunuz?", "DoYouAcceptBankWireTransferExplanation": "Evet, banka havalesini kabul ediyoruz.
Lisans ücretini banka havalesi yoluyla gönderdikten sonra, dekontunuzu ve talep edilen lisans türünü accounting@abp.io adresinden bize e-posta ile gönderin. Uluslararası banka hesap bilgilerimiz:", @@ -391,18 +391,18 @@ "AddNewDeveloper": "Yeni geliştirici ekle", "FirstNameField": "Ad", "LastNameField": "Soyad", - "AbpCommercialMetaTitle": "ABP Commercial - Eksiksiz Web Geliştirme Platformu : {0} | ABP Commercial", + "AbpCommercialMetaTitle": " {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?", + "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.", + "WhatHappensWhenLicenseEndsExplanation7": "Bu avantajlardan yararlanmaya devam etmek istiyorsanız lisansınızı uzatabilirsiniz (yenileyebilirsiniz). Lisansınızın süresi dolduktan sonra {3} gün 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.", + "TrialPlanExplanation": "Hayır, ABP Commercial için deneme sürümü yoktur. Kod kalitesini ve yaklaşımları anlamak için topluluk sürümünü kontrol edebilirsiniz. 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", @@ -419,7 +419,7 @@ "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!", + "CommunityDescription": "ABP Çerçevesi ile ilgili deneyimlerinizi paylaşın!", "GetStarted": "Başlayın", "Views": "Görünümler", "LatestPosts": "Son Gönderiler", @@ -441,7 +441,7 @@ "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.", + "Bs5Compatible": "Bootstrap 5 uyumlu profesyonel tema, yönetici web siteniz için mükemmel.", "LeptonXTheme": "LeptonX Tema", "LeptonXDark": "LeptonX Koyu", "LeptonXLight": "LeptonX Açık", @@ -454,7 +454,7 @@ "EasilyInstallAndUpgrade": "Kolay kurulum ve yükseltme", "SupportForum": "Destek Forumu", "TrustedBy": "Güvenenler", - "OurPricing": "Fiyatlandırmamız", + "OurPricing": "Fiyatlandırmamız", "Plans": "Planlar", "NameSurname": "Ad Soyad", "Unspecified": "Belirtilmemiş", @@ -465,7 +465,7 @@ "License": "Lisans", "Development": "Geliştirme", "Payment": "Ödeme", - "WatchExplainerVideo": "Hadi Tanışalım! Açıklayıcı Videoyu İzleyin", + "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", @@ -522,7 +522,7 @@ "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_Title": "Fiyatlandırma ve Planlar", "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!", @@ -607,7 +607,7 @@ "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_Title": "ABP 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", @@ -632,12 +632,11 @@ "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_Title": "ABP Suite", "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_Title": "ABP 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.", @@ -655,7 +654,7 @@ "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_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", @@ -722,7 +721,7 @@ "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_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.", @@ -746,6 +745,11 @@ "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?", - "BlackFridayDiscount": "Kara Cuma İndirimi" + "BlackFridayDiscount": "Kara Cuma İndirimi", + "EulaPageTitle": "Son Kullanıcı Lisans Sözleşmesi (EULA)", + "PrivacyPolicyPageTitle": "Gizlilik Politikası - Çerez Politikası", + "TermsConditionsPageTitle": "Şartlar ve Koşullar", + "TrainingsPageTitle": "ABP Eğitim Paketleri", + "ModulesPageTitle": "ABP Önceden Oluşturulmuş Uygulama Modülleri" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/vi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/vi.json index 4d0666772a..e5ed696808 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/vi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/vi.json @@ -190,7 +190,7 @@ "ChangingDevelopers": "Tôi có thể thay đổi các nhà phát triển đã đăng ký của tổ chức của mình trong tương lai không?", "ChangingDevelopersExplanation": "Ngoài việc thêm các nhà phát triển mới vào giấy phép của mình, bạn cũng có thể thay đổi các nhà phát triển hiện có (bạn có thể xóa một nhà phát triển và thêm một nhà phát triển mới vào cùng một chỗ ngồi) mà không phải trả thêm bất kỳ chi phí nào.", "WhenShouldIRenewMyLicense": "Khi nào tôi nên gia hạn giấy phép của mình?", - "WhenShouldIRenewMyLicenseExplanation": "Nếu bạn gia hạn giấy phép của mình trong vòng 1 tháng sau khi giấy phép của bạn hết hạn, các chiết khấu sau sẽ được áp dụng: Giảm giá {0}% cho Giấy phép Nhóm, Giảm giá {1}% Giấy phép Kinh doanh, Giảm giá {2}% Giấy phép Doanh nghiệp . Nếu bạn gia hạn giấy phép 1 tháng sau ngày giấy phép hết hạn, giá gia hạn sẽ giống như giá mua giấy phép và sẽ không có chiết khấu khi gia hạn của bạn.", + "WhenShouldIRenewMyLicenseExplanation": "Nếu bạn gia hạn giấy phép của mình trong vòng {3} ngày sau khi giấy phép của bạn hết hạn, các chiết khấu sau sẽ được áp dụng: Giảm giá {0}% cho Giấy phép Nhóm, Giảm giá {1}% Giấy phép Kinh doanh, Giảm giá {2}% Giấy phép Doanh nghiệp . Nếu bạn gia hạn giấy phép {3} ngày sau ngày giấy phép hết hạn, giá gia hạn sẽ giống như giá mua giấy phép và sẽ không có chiết khấu khi gia hạn của bạn.", "TrialPlan": "Bạn có kế hoạch dùng thử không?", "DoYouAcceptBankWireTransfer": "Bạn có chấp nhận chuyển khoản ngân hàng không?", "DoYouAcceptBankWireTransferExplanation": "Có, chúng tôi chấp nhận chuyển khoản ngân hàng.
Sau khi gửi phí cấp phép qua chuyển khoản ngân hàng, hãy gửi email cho chúng tôi theo địa chỉ accounting@abp.io biên lai của bạn và loại giấy phép được yêu cầu. Thông tin tài khoản ngân hàng quốc tế của chúng tôi:", 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 be657892f7..06e0c6ca9e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hans.json @@ -160,7 +160,7 @@ "SearchQuestionPlaceholder": "搜索常见的问题", "WhatIsTheABPCommercial": "什么是ABP商业版?", "WhatAreDifferencesThanAbpFramework": "ABP框架与ABP商业版有什么不同?", - "AbpCommercialMetaTitle": "ABP 商业版 - 完整的网页开发平台 : {0} | ABP 商业版 ", + "AbpCommercialMetaTitle": " {0} | ABP 商业版 ", "AbpCommercialMetaDescription": "ABP 商业版是在开源ABP框架之上构建的一组预构建应用程序模块、快速开发工具、UI主题和服务架构", "ABPCommercialExplanation": "ABP商业版是一套基于开源ABP框架之上的高级模块,工具,主题和服务. ABP商业版由ABP框架背后的同一团队开发和支持.", "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP框架是模块化,主题化,微服务兼容的ASP.NET Core应用程序开发框架. 它提供了一个完整的架构和强大的基础设施,让你专注于自己的业务代码而不是重复自己的每一个项目. 它基于软件开发的最佳实践和你已经知道的流行工具

ABP框架是完全免费,开源和由社区驱动的. 它还提供了一个免费的主题和一些预构建的模块 (如 identity管理和租户管理).

", @@ -202,13 +202,13 @@ "WhatHappensWhenLicenseEndsExplanation4": "你不能在你的许可证到期后安装ABP商业平台上添加的新模块和主题。", "WhatHappensWhenLicenseEndsExplanation5": "你不能使用ABP Suite。", "WhatHappensWhenLicenseEndsExplanation6": "你不能再获得高级支持。", - "WhatHappensWhenLicenseEndsExplanation7": "如果您想继续获得这些好处,您可以延长(续订)您的许可证。 如果您在许可证到期后 1 个月内延长许可证,将应用以下折扣:团队许可证 {0}; 营业执照{1}; 企业许可证 {2}。", + "WhatHappensWhenLicenseEndsExplanation7": "如果您想继续获得这些好处,您可以延长(续订)您的许可证。 如果您在许可证到期后 {3} 天内延长许可证,将应用以下折扣:团队许可证 {0}; 营业执照{1}; 企业许可证 {2}。", "discountForYears": "{0}% 折扣 {1} 年", "WhatHappensWhenLicenseEndsExplanation8": "您生成的 ABP 项目未存储在我们的服务器上。 因此,您有责任保留下载的源代码。 当您的许可证到期时,将无法获取您生成的 ABP 项目源代码。", "WhenShouldIRenewMyLicense": "我什么时候应该续订我的许可?", - "WhenShouldIRenewMyLicenseExplanation": "如果您在许可证到期后 1 个月 内续订许可证,将适用以下折扣:团队许可证 {0}; 营业执照{1}; 企业许可证 {2}。 但是,如果您在许可证到期后 1 个月 后续订许可证,则续订价格将与许可证购买价格相同,并且您的续订不会有任何折扣。", + "WhenShouldIRenewMyLicenseExplanation": "如果您在许可证到期后 {3} 天 内续订许可证,将适用以下折扣:团队许可证 {0}; 营业执照{1}; 企业许可证 {2}。 但是,如果您在许可证到期后 {3} 天 后续订许可证,则续订价格将与许可证购买价格相同,并且您的续订不会有任何折扣。", "TrialPlan": "你们有试用计划吗?", - "TrialPlanExplanation": "ABP商业团队许可证有14天的试用期。若要了解更多信息,请访问这里。此外,我们为团队许可证提供30天的金额返还保证。你只需要在30天内请求退款。商业和企业许可证将提供60%的金额返还保证。这是因为商业和企业许可证包含了所有模块和主题的全部源代码。", + "TrialPlanExplanation": "不,ABP商业版没有试用版。您可以通过查看社区版了解代码质量和方法。我们还为团队许可证提供30天无条件退款保证!您可以在前30天内申请退款。对于商业和企业许可证,我们在30天内提供60%的退款。这是因为商业和企业许可证包含所有模块和主题的完整源代码。", "DoYouAcceptBankWireTransfer": "你们接受银行电汇吗?", "DoYouAcceptBankWireTransferExplanation": "是的,我们接受银行电汇。
在通过银行转账发送许可费后,将您的收据和所需的许可类型通过电子邮件发送至accounting@abp.io。 我们的国际银行账户信息:", "HowToUpgrade": "可用新版本时如何升级现有应用程序?", @@ -369,7 +369,6 @@ "FreeTrial": "免费试用", "AcceptsMarketingCommunications": " 是的,我想接收 ABP 商业营销通讯。", "PurposeOfUsage": "使用目的", - "Industry": "行业", "Choose": "- 选择 -", "CompanyOrganizationName": "公司/组织名称", "CompanySize": "公司规模", @@ -512,7 +511,7 @@ "AddBasket": "添加到购物车", "SendTrainingRequest": "发送培训请求", "OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* 本文件的英文版本为最新版本,如有任何争议,以英文版本为准。", - "Pricing_Page_Title": "计划 & 定价", + "Pricing_Page_Title": "价格和计划", "Pricing_Page_Description": "现在就选择您的业务及需要的特性和功能。 购买 ABP 商业许可证创建无限量项目。", "Pricing_Page_HurryUp": "赶快行动吧!", "Pricing_Page_BuyLicense": "在 1 月 16 日前以 2021 年价格 购买许可证!", @@ -598,7 +597,7 @@ "Purchase_PricePerDeveloper": "{0} {1} 每个开发者", "Purchase_IncludedDeveloperInfo": "{0} {1} 包括在内。", "Purchase_LicenseExtraDeveloperPurchaseMessage": "{0} 许可 包含 {1} 个开发者。 您可以现在或以后添加其他开发人员。", - "StartupTemplates_Page_Title": "启动模板", + "StartupTemplates_Page_Title": "ABP启动模板", "StartupTemplates_Page_Description": "ABP 商业版 允许您构建任何复杂程度的解决方案。 它提供了两种主要的预构建启动解决方案。 您可以选择最接近您要求的解决方案,并在此基础上构建您自己的定制解决方案。", "MicroserviceStartupSolutionForDotnet": ".NET 微服务启动解决方案", "MonolithSolutionForDotnet": ".NET 的单体(模块化)解决方案", @@ -623,12 +622,11 @@ "Faq_Page_Currency": "货币", "Faq_Page_VatNumber": "增值税号", "Faq_Page_OtherCurrenciesInfo": "对于其他货币,请参阅所有账户", - "ModuleDetail_Page_Title": "模块详细信息 - {0}", "ProjectCreatedSuccess_Page_Title": "您的项目已创建", "ProjectCreatedSuccess_Page_Description": "您的 ABP 项目创建成功!", - "Suite_Page_Title": "ABP 套件 - 创建 CRUD 页面", + "Suite_Page_Title": "ABP Suite", "Suite_Page_Description": "ABP Commercial 提供快速应用程序开发工具以提高开发人员的工作效率。 ABP 套件 允许您轻松创建 CRUD 页面。", - "Themes_Page_Title": "现代和实用的 UI 主题", + "Themes_Page_Title": "ABP主题", "Themes_Page_Description": "ABP 商业版 提供多种专业、现代的 UI 主题。 创建免费演示以快速查看 UI 的外观。", "Tools_Page_Title": "快速应用程序开发工具", "Tools_Page_Description": "ABP 商业版 提供快速应用程序开发工具以提高开发人员的工作效率。 ABP 套件 允许您轻松创建 CRUD 页面。", @@ -804,6 +802,29 @@ "UpgradePaymentInfoSection_LicenseRenewalPrice": "许可证续订", "Total": "总计", "SupportPolicyFaqTitle": "您的支持政策是什么?", - "SupportPolicyFaqExplanation": "我们只支持有效的和以前的主要版本。 我们不保证为第 3 个和更早的主要版本发布补丁。 例如,如果有效版本是 7.0.0,我们将发布 6.x.x 和 7.x.x 的补丁版本。 此外,我们仅对 ABP Framework 和 ABP Commercial 相关问题提供支持。 这意味着不支持 ABP 产品使用的第 3 方应用程序、云服务和其他外围库。 我们将尽商业上合理的努力在“Volosoft Bilisim A.S”的正式营业时间内为我们的客户提供技术支持。 另一方面,我们不承诺服务级别协议 (SLA) 响应时间,但我们会尽量在我们的官方工作时间内尽快响应技术问题。 除非与客户达成特殊协议,否则我们仅在 https://support.abp.io 上提供支持。 我们还提供私人电子邮件支持,仅适用于企业许可证持有者。" + "SupportPolicyFaqExplanation": "我们只支持有效的和以前的主要版本。 我们不保证为第 3 个和更早的主要版本发布补丁。 例如,如果有效版本是 7.0.0,我们将发布 6.x.x 和 7.x.x 的补丁版本。 此外,我们仅对 ABP Framework 和 ABP Commercial 相关问题提供支持。 这意味着不支持 ABP 产品使用的第 3 方应用程序、云服务和其他外围库。 我们将尽商业上合理的努力在“Volosoft Bilisim A.S”的正式营业时间内为我们的客户提供技术支持。 另一方面,我们不承诺服务级别协议 (SLA) 响应时间,但我们会尽量在我们的官方工作时间内尽快响应技术问题。 除非与客户达成特殊协议,否则我们仅在 https://support.abp.io 上提供支持。 我们还提供私人电子邮件支持,仅适用于企业许可证持有者。", + "TotalDevelopers": "{0}位开发者", + "CustomPurchaseExplanation": "定制您的特定需求", + "WhereDidYouHearAboutUs": "您是从哪里了解到我们的?", + "Twitter": "Twitter", + "Facebook": "Facebook", + "Youtube": "YouTube", + "Google": "Google", + "Github": "GitHub", + "Friend": "朋友介绍", + "Other": "其他", + "WhereDidYouHearAboutUs_explain": "请说明...", + "DeletingMemberWarningMessage": "\"{0}\"将从开发者列表中删除。如果您愿意,您可以将此空位分配给另一个开发者。", + "AdditionalInfo": "如果开发人员席位超出您的要求,您可以减少它们。 您可以发送电子邮件至 info@abp.io 以删除您的一些开发人员席位。 清除未使用的开发人员席位将降低许可证续订成本。 如果需要,您可以在有效许可期限内重新购买额外的开发人员席位。 请注意,由于此许可证中有 {0} 个开发人员,因此您无法减少此数量。", + "LinkExpiredErrorMessage": "您正在尝试访问的链接已过期。", + "ExpirationDate": "过期时间", + "SpringCampaignDiscount": "春季促销折扣", + "WhyUseAbpIoPlatform": "为什么我应该使用 ABP.IO 平台而不是从头开始创建新的解决方案?\n", + "WhyUseAbpIoPlatformFaqExplanation": "请参阅该文件以详细解释为什么使用ABP.IO平台比自己做所有事情有显著的优势。", + "EulaPageTitle": "最终用户许可协议(EULA)", + "PrivacyPolicyPageTitle": "隐私政策 - Cookies政策", + "TermsConditionsPageTitle": "条款和条件", + "TrainingsPageTitle": "ABP培训套餐", + "ModulesPageTitle": "ABP内置的应用模块" } } 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 f3c9575cd2..19afb1a5bb 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/zh-Hant.json @@ -192,7 +192,7 @@ "ChangingDevelopers": "我將來可以更改我組織的註冊開發人員嗎?", "ChangingDevelopersExplanation": "除了將新的開發人員添加到你的許可中之外,你還可以更改現有的開發人員(可以刪除一個開發人員並將新的開發人員添加到同一位置),而無需任何額外費用.", "WhenShouldIRenewMyLicense": "我什麽時候應該續訂我的許可?", - "WhenShouldIRenewMyLicenseExplanation": "如果您在許可證到期後 1 個月內續訂許可證,將享受以下折扣:團隊許可證 {0} ; 商業許可證 {1} ;企業許可證 {2}. 如果您在許可證到期後 1 個月續訂許可證,續訂價格將與許可證購買價格相同,並且續訂不會有折扣。", + "WhenShouldIRenewMyLicenseExplanation": "如果您在許可證到期後 {3} 天內續訂許可證,將享受以下折扣:團隊許可證 {0} ; 商業許可證 {1} ;企業許可證 {2}. 如果您在許可證到期後 {3} 天續訂許可證,續訂價格將與許可證購買價格相同,並且續訂不會有折扣。", "TrialPlan": "你們有試用計劃嗎?", "DoYouAcceptBankWireTransfer": "你們接受銀行電匯嗎?", "DoYouAcceptBankWireTransferExplanation": "是的,我們接受銀行電匯。
在通過銀行轉賬發送許可費後,將您的收據和所需的許可類型通過電子郵件發送至accounting@abp.io。 我們的國際銀行賬戶信息:", @@ -384,6 +384,24 @@ "BlazoriseLicense": "我们是否需要购买Blazorise许可证?", "BlazoriseLicenseExplanation": "我公司Volosoft和公司Megabit之间有合作协议,根据此协议,购买将同时包含了Blazorise许可证与ABP商业产品,因此我们的客户不需要再额外购买Blazorise许可证。", "discountForYears": "{0}% 折扣 {1} 年", - "BlackFridayDiscount": "黑色星期五折扣" + "BlackFridayDiscount": "黑色星期五折扣", + "OnboardingTrainingFaqTitle": "你們有 ABP 入職培訓嗎?", + "OnboardingTrainingFaqExplanation": "是的,我們有 ABP 培訓服務來幫助您快速啟動 ABP 項目。您將從 ABP 核心團隊成員那裡了解 ABP,並獲得開始您的 ABP 項目的技能。在入職培訓中,我們將解釋如何設置您的開發環境、安裝所需的工具、創建功能齊全的 CRUD 頁面。培訓將進行直播並使用 Zoom 應用程序,我們對使用其他在線會議平台持開放態度。培訓語言為英語。您也可以在會議期間提出有關 ABP 的問題。將為雙方計劃一個方便的時間和日期。要獲取更多信息,請通過 info@abp.io 聯繫我們。", + "SupportPolicyFaqTitle": "您的支持政策是什麼?", + "SupportPolicyFaqExplanation": "我們只支持活動和以前的主要版本。我們不保證為第 3 個和更早的主要版本發布補丁。例如,如果活動版本是 7.0.0,我們將發布 6.x.x 和 7.x.x 的補丁版本。此外,我們僅對 ABP Framework 和 ABP Commercial 相關問題提供支持。這意味著不支持 ABP 產品使用的第 3 方應用程序、雲服務和其他外圍庫。我們將盡商業上合理的努力,在“Volosoft Bilisim A.S”的正式營業時間內為我們的客戶提供技術支持。另一方面,我們不承諾服務級別協議 (SLA) 響應時間,但我們會盡量在我們的官方工作時間內盡快響應技術問題。除非與客戶達成特殊協議,否則我們僅在 https://support.abp.io 上提供支持。我們還提供私人電子郵件支持,僅適用於企業許可證持有者。", + "DowngradeLicensePlan": "我以後可以降級到更低的許可計劃嗎?", + "DowngradeLicensePlanExplanation": "您不能降級現有的許可計劃。但是您可以購買新的較低許可計劃並在新許可上繼續您的開發。購買較低的許可證後,您只需通過 ABP CLI 命令登錄到新的許可證計劃:`abp login -o `。", + "LicenseTransfer": "許可證可以從一個開發商轉讓給另一個開發商嗎?", + "LicenseTransferExplanation": "是的!購買許可證後,您將成為許可證持有者,因此您可以訪問組織管理頁面。組織具有所有者和開發者角色。所有者可以管理開發者席位並分配開發者。每個指定的開發人員將通過 ABP CLI 命令登錄到系統,並將擁有開發和支持權限。", + "RenewLicenseEarly": "如果我提前更新我的駕照,我會得到一整年嗎?", + "RenewLicenseEarylExplanation": "當您在許可證到期日期之前續訂許可證時,您的許可證到期日期將增加 1 年。例如,如果您的許可證在 {0}-06-06 到期,而您在 {0}-01-01 續簽,則新的許可證到期日期將為 {1}-06-06。", + "WhatHappensWhenLicenseEnds": "當我的許可期限結束時會發生什麼?", + "WhatHappensWhenLicenseEndsExplanation1": "ABP 商業許可是永久許可。許可證到期後,您可以繼續開發您的項目。而且您沒有義務更新您的許可證。您的許可證附帶開箱即用的一年更新和支持計劃。為了繼續獲得新功能、性能增強、錯誤修復、支持和繼續使用 ABP Suite,您需要更新您的許可證。當您的許可證到期時;", + "WhatHappensWhenLicenseEndsExplanation2": "您無法使用 ABP Commercial 創建新的解決方案,但您可以永遠繼續開發現有的應用程序。", + "WhatHappensWhenLicenseEndsExplanation3": "您將能夠在您的次要版本(RC 或預覽版除外)中獲取模塊和主題的更新。例如:如果您正在使用模塊的 v3.2.0,您仍然可以獲得該模塊的 v3.2.x(v3.2.1、v3.2.5...等)的更新。但是您無法獲得下一個主要或次要版本的更新(如 v3.3.0、v3.3.3、4.x.x.. 等)。比如你的license過期了,最新發布的是v4.4.3,後來又發布了4.4.4和4.5.0兩個版本,你可以訪問v4.4.X,但是你就不行了訪問 v4.5.X。", + "WhatHappensWhenLicenseEndsExplanation4": "許可證到期後,您將無法安裝添加到 ABP 商業平台的新模塊和主題。", + "WhatHappensWhenLicenseEndsExplanation5": "您不能使用 ABP 套件。", + "WhatHappensWhenLicenseEndsExplanation6": "您無法再獲得高級支持。", + "WhatHappensWhenLicenseEndsExplanation7": "如果您想繼續獲得這些好處,您可以延長(更新)您的許可證。如果您在許可到期後 {3} 天 內延長許可,將應用以下折扣:團隊許可 {0};營業執照{1};企業許可證{2}。" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json index 0264fe4e31..84c3b34110 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/ar.json @@ -187,6 +187,7 @@ "Index_Page_CommunityIntroduction": "إن هذا محور لإطار عمل ABP و.NET وتطوير البرامج. يمكنك قراءة المقالات ومشاهدة مقاطع الفيديو التعليمية والحصول على معلومات حول تقدم تطوير ABP والأحداث المتعلقة بـ ABP ومساعدة المطورين الآخرين ومشاركة خبرتك مع منتدى ABP.", "TagsInArticle": "العلامات في المقال", "WelcomeToABP": "أهلا بكم في ABP", - "IConsentToMedium": ".https://medium.com/volosoft أوافق على نشر هذا المنشور على" + "IConsentToMedium": ".https://medium.com/volosoft أوافق على نشر هذا المنشور على", + "DiscordPageTitle": "مجتمع ABP Discord" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json index 8cfb154295..24d07c54bf 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json @@ -184,6 +184,9 @@ "Layout_MetaDescription": "ABP Community is an environment where people can share posts about ABP framework and follows the projects.", "Index_Page_CommunityIntroduction": "This is a hub for ABP Framework, .NET and software development. You can read the articles, watch the video tutorials, get informed about ABP’s development progress and ABP-related events, help other developers and share your expertise with the ABP community.", "TagsInArticle": "Tags in article", - "IConsentToMedium": "I consent to the publication of this post at https://medium.com/volosoft." + "IConsentToMedium": "I consent to the publication of this post at https://medium.com/volosoft.", + "SearchResultsFor": "Search results for \"{0}\"", + "SeeMoreVideos": "See more videos", + "DiscordPageTitle": "ABP Discord Community" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json index be267cebc0..d090608518 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json @@ -2,21 +2,20 @@ "culture": "fi", "texts": { "Permission:CommunityPost": "Yhteisön artikkeli", - "Permission:Edit": "Muokata", + "Permission:Edit": "Muokkaus", "Waiting": "Odottaa", "Approved": "Hyväksytty", "Rejected": "Hylätty", - "Wait": "Odota", - "Approve": "Hyväksyä", - "Reject": "Hylätä", + "Wait": "Odottaa", + "Approve": "Hyväksy", + "Reject": "Hylkää", "ReadPost": "Lue artikkeli", "Status": "Tila", "ContentSource": "Sisältölähde", "Details": "Yksityiskohdat", - "Url": "URL-osoite", "Title": "Otsikko", "CreationTime": "Luomisaika", - "Save": "Tallentaa", + "Save": "Tallenna", "SameUrlAlreadyExist": "Sama URL-osoite on jo olemassa, jos haluat lisätä tämän artikkelin, vaihda URL-osoite!", "UrlIsNotValid": "URL-osoite ei kelpaa.", "UrlNotFound": "URL-osoitetta ei löydy.", @@ -29,14 +28,12 @@ "ContributionGuide": "Contribution Guide", "BugReport": "Virhe raportti", "SeeAllPosts": "Katso kaikki viestit", - "WelcomeToABPCommunity!": "Tervetuloa ABP Community!", - "MyProfile": "Profiilini", - "MyOrganizations": "Omat organisaatiot", + "WelcomeToABP": "Tervetuloa ABP:hen", "EmailNotValid": "Ole hyvä ja syötä toimiva sähköpostiosoite.", "FeatureRequest": "Ominaisuuspyyntö", "CreatePostTitleInfo": "Viestiluettelossa näytettävän viestin nimi.", "CreatePostSummaryInfo": "Lyhyt yhteenveto viestistä, joka näytetään postituslistalla.", - "CreatePostCoverInfo": "Lisää tehokkaan artikkelin luomiseksi kansikuva. Lataa 16:9 -kuvasuhteen kuvat parhaan näkymän saamiseksi. Tiedoston enimmäiskoko: 1MB.", + "CreatePostCoverInfo": "Lisää kansikuva, jotta voit luoda tehokkaan viestin. Lataa 16:9 -kuvasuhteen kuvat parhaan näkymän saamiseksi. Tiedoston enimmäiskoko: 1MB.", "ThisExtensionIsNotAllowed": "Tätä laajennusta ei sallita.", "TheFileIsTooLarge": "Tiedosto on liian suuri.", "GoToThePost": "Siirry artikkeliin", @@ -45,7 +42,7 @@ "Done": "Tehty", "Open": "Avata", "Closed": "Suljettu", - "LatestQuestionOnThe": "Viimeisin kysymys", + "RecentQuestionFrom": "Viimeisin kysymys henkilöltä {0}", "Stackoverflow": "Pinoaminen", "Votes": "ääntä", "Answer": "Vastaus", @@ -59,7 +56,7 @@ "QuestionItemErrorMessage": "Viimeisimmät kysymystiedot Stackoverflow'sta epäonnistui.", "Oops": "Oho!", "CreatePostSuccessMessage": "Artikkeli on lähetetty onnistuneesti. Se julkaistaan sivuston järjestelmänvalvojan tarkistuksen jälkeen.", - "ChooseCoverImage": "Valitse kansikuva...", + "Browse": "Selaa", "CoverImage": "Kansikuva", "ShareYourExperiencesWithTheABPFramework": "Jaa kokemuksesi ABP-puitteista!", "Optional": "Valinnainen", @@ -80,7 +77,7 @@ "LatestGithubAnnouncements": "Viimeisimmät Github-ilmoitukset", "SeeAllAnnouncements": "Katso kaikki ilmoitukset", "LatestBlogPost": "Viimeisin blogiviesti", - "Edit": "Muokata", + "Edit": "Muokkaa", "ProfileImageChange": "Vaihda profiilikuva", "BlogItemErrorMessage": "Viimeisimpiä blogiviestitietoja ei saatu ABP: ltä.", "PlannedReleaseDate": "Suunniteltu julkaisupäivä", @@ -88,6 +85,8 @@ "PostRequestFromGithubIssue": "Artikkelipyyntöjä ei ole nyt.", "LatestPosts": "Uusimmat viestit", "ArticleRequests": "Artikkelipyynnöt", + "ArticleRequestsDescription": "Haluatko nähdä tietyn sisällön täällä? Voit pyytää yhteisöä luomaan sen!", + "LatestContentRequests": "Uusimmat sisältöpyynnöt", "AllPostRequests": "Katso kaikki artikkelipyynnöt", "SubscribeToTheNewsletter": "Tilaa uutiskirje", "NewsletterEmailDefinition": "Hanki tietoa ABP: n tapahtumista, kuten uusista julkaisuista, ilmaisista lähteistä, artikkeleista ja muusta.", @@ -102,20 +101,19 @@ "Language": "Kieli", "CreatePostLanguageInfo": "Viestin sisällön kieli.", "VideoPost": "Videoposti", - "Post": "Artikla", - "Read": "Lukea", + "Post": "Artikkeli", + "Read": "Lue", "CreateGithubPostUrlInfo": "Artikkelin alkuperäinen GitHub-URL-osoite.", - "CreateVideoContentUrlInfo": "Viestin alkuperäinen Youtube-URL-osoite.", + "CreateVideoContentUrlInfo": "Artikkelin alkuperäinen Youtube-URL-osoite.", "CreateExternalPostUrlInfo": "Artikkelin alkuperäinen ulkoinen URL-osoite.", "VideoContentForm": "Lähetä video YouTubessa", "GithubPostForm": "Lähetä artikkeli GitHubista", "ExternalPostForm": "Lähetä ulkoinen sisältö", - "HowToPost": "Kuinka lähettää?", + "HowToPost": "Kuinka lähettää viesti?", "Posts": "Viestit", "VideoUrl": "Videon URL-osoite", "GithubPostUrl": "Github-artikkelien URL-osoite", "ExternalPostUrl": "Ulkoisen artikkelin URL-osoite", - "CreatePostCoverInfo": "Lisää kansikuva, jotta voit luoda tehokkaan viestin. Lataa 16:9 -kuvasuhteen kuvat parhaan näkymän saamiseksi. Tiedoston enimmäiskoko: 1MB.", "ThankYouForContribution": "Kiitos osallistumisesta ABP Community.", "GithubPost": "Github-artikkeli", "GithubPostSubmitStepOne": "1. Kirjoita artikkeli mistä tahansa julkisesta GitHub-arkistosta Markdown-muodossa. esimerkki ", @@ -144,6 +142,50 @@ "Volo.AbpIo.Domain:060002": "Artikkelin sisältö ei ole saatavilla Githubin (\"{PostUrl}\") -resurssista.", "Volo.AbpIo.Domain:060003": "Artikkelin sisältöä ei löytynyt!", "SeeMore": "Katso Lisää", - "IConsentToMedium": "Hyväksyn tämän viestin julkaisemisen osoitteessa https://medium.com/volosoft." + "JoinTheABPCommunity": "Liity ABP-yhteisöön", + "ABPCommunityTalks": "ABP Community Talks", + "LiveDemo": "Live-demo", + "GetLicense": "Hanki lisenssi", + "GetStarted": "Aloita", + "SourceCode": "Lähdekoodi", + "LeaveComment": "Jätä kommentti", + "ShowMore": "Näytä lisää", + "NoPublishedPostsYet": "Ei vielä julkaistuja viestejä.", + "Name": "Nimi", + "Surname": "Sukunimi", + "WebSite": "Verkkosivusto", + "FullURL": "Koko URL-osoite", + "JobTitle": "Työnimike", + "Prev": "Ed", + "Previous": "Edellinen", + "Next": "Seuraava", + "Share": "Jaa", + "SortBy": "Järjestä", + "NoPublishedEventsYet": "Ei vielä julkaistuja tapahtumia.", + "SubscribeYoutubeChannel": "Tilaa Youtube-kanava", + "Enum:EventType:0": "Keskustelut", + "MemberNotPublishedPostYet": "Tämä jäsen ei ole vielä julkaissut yhtään viestiä.", + "TimeAgo": "{0} sitten", + "Discord_Page_JoinCommunityMessage": "Liity ABP Discord -yhteisöön", + "Discord_Page_Announce": "Meillä on ilo julkistaa ABP Community Discord Server!", + "Discord_Page_Description_1": "ABP-yhteisö on kasvanut ensimmäisestä päivästä lähtien. Halusimme viedä sen seuraavaan vaiheeseen luomalla virallisen ABP Discord -palvelimen, jotta ABP-yhteisö voi olla vuorovaikutuksessa toistensa kanssa käyttämällä pikaviestinnän ihmeitä.", + "Discord_Page_Description_2": "ABP Community Discord Server on paikka, jossa voit esitellä luomuksiasi ABP Frameworkin avulla, jakaa sinulle toimivia vinkkejä, saada viimeisimmät uutiset ja ilmoitukset ABP Frameworkista, keskustella vain yhteisön jäsenten kanssa ideoiden vaihtamiseksi ja pitää hauskaa!", + "Discord_Page_Description_3": "Tämä ABP Community Discord -palvelin on virallinen, ja ABP Core Team on läsnä palvelimella valvomassa.", + "Discord_Page_JoinToServer": "Liity ABP Discord Serveriin", + "Events_Page_MetaTitle": "ABP-yhteisötapahtumat", + "Events_Page_MetaDescription": "ABP-tiimin isännöimät live-ohjelmat ovat rentoja istuntoja, jotka ovat täynnä yhteisöllistä sisältöä, demoja, kysymyksiä ja vastauksia sekä keskusteluja siitä, mitä ABP:ssä tapahtuu.", + "Events_Page_Title": "ABPyhteisön keskustelut", + "Members_Page_WritingFromUser": "Lue kirjoittajan {0} kirjoitus ABP-yhteisössä.", + "Post_Create_Page_MetaTitle": "Uusi viesti", + "Post_Create_Page_MetaDescription": "Luo viestisi, jotta voit jakaa kokemuksiasi ABP-kehyksestä ja osallistua ABP-yhteisöön.", + "Post_Create_Page_CreateNewPost": "Luo uusi viesti", + "Post_Index_Page_MetaDescription": "ABP Communityn tarkoituksena on luoda osallistumisen mahdollistava ympäristö ABP-kehystä käyttäville kehittäjille.", + "Layout_Title": "{0} | ABP-yhteisö", + "Layout_MetaDescription": "ABP Community on ympäristö, jossa ihmiset voivat jakaa julkaisuja ABP-kehyksestä ja seurata projekteja.", + "Index_Page_CommunityIntroduction": "Tämä on ABP Frameworkin, .NET:n ja ohjelmistokehityksen keskus. Voit lukea artikkeleita, katsoa opetusvideoita, saada tietoa ABP:n kehityksen edistymisestä ja ABP:hen liittyvistä tapahtumista, auttaa muita kehittäjiä ja jakaa asiantuntemustasi ABP-yhteisön kanssa.", + "TagsInArticle": "Tagit artikkelissa", + "IConsentToMedium": "Hyväksyn tämän viestin julkaisemisen osoitteessa https://medium.com/volosoft.", + "SearchResultsFor": "Hakutulokset haulle \"{0}\"", + "SeeMoreVideos": "Katso lisää videoita" } } \ 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 f3b7a791c8..81c659c500 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json @@ -190,6 +190,7 @@ "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.", - "IConsentToMedium": "Bu yazının https://medium.com/volosoft adresinde yayınlanmasına izin veriyorum." + "IConsentToMedium": "Bu yazının https://medium.com/volosoft adresinde yayınlanmasına izin veriyorum.", + "DiscordPageTitle": "ABP Discord Topluluğu" } } \ 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 d460707b70..6d2083ed76 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json @@ -13,7 +13,6 @@ "Status": "状态", "ContentSource": "内容来源", "Details": "详情", - "Url": "url", "Title": "标题", "CreationTime": "创建时间", "Save": "保存", @@ -185,6 +184,9 @@ "Layout_MetaDescription": "ABP 社区是一个人们可以分享有关 ABP 框架的帖子并关注项目的环境。", "Index_Page_CommunityIntroduction": "这是 ABP 框架、.NET 和软件开发的中心。 您可以阅读文章,观看视频教程,了解 ABP 的开发进度和 ABP 相关事件,帮助其他开发人员并与 ABP 社区分享您的专业知识。", "TagsInArticle": "文章中的标签", - "IConsentToMedium": "我同意在 https://medium.com/volosoft 上发布这篇文章。" + "IConsentToMedium": "我同意在 https://medium.com/volosoft 上发布这篇文章。", + "SearchResultsFor": " span class=\"fw-bold\">\"{0}\"的搜索结果", + "SeeMoreVideos": "查看更多视频", + "DiscordPageTitle": "ABP Discord社区" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/fi.json new file mode 100644 index 0000000000..201c63e800 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/fi.json @@ -0,0 +1,6 @@ +{ + "culture": "fi", + "texts": { + "Buy": "Osta" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/zh-Hans.json index df772fa60b..2bbfd5b749 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Docs/Localization/Resources/zh-Hans.json @@ -1,5 +1,6 @@ { "culture": "zh-Hans", "texts": { + "Buy": "购买" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/fi.json new file mode 100644 index 0000000000..87bd42a2cd --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Support/Localization/Resources/fi.json @@ -0,0 +1,6 @@ +{ + "culture": "fi", + "texts": { + "FAQ": "FAQ" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ar.json index 791062cfbc..592665c698 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ar.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ar.json @@ -404,6 +404,9 @@ "SeeTheScreenshot": "انظر الى الصورة", "Details": "التفاصيل", "ApplicationModuleExplanation1": "إنشاء حل وحدة تطبيق ذات طبقات كاملة وقابلة لإعادة الاستخدام.", - "ApplicationModuleExplanation2": "يمكنك استخدام هذا الخيار لإنشاء وحدات نمطية لتطبيقك المعياري." + "ApplicationModuleExplanation2": "يمكنك استخدام هذا الخيار لإنشاء وحدات نمطية لتطبيقك المعياري.", + "CreateSolutionFolder": "إنشاء مجلد الحل", + "CreateSolutionFolderOption": "يحدد ما إذا كان المشروع سيكون في مجلد جديد في مجلد الإخراج أو مجلد الإخراج مباشرة.", + "BooksPageTitle": "كتب ABP" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/cs.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/cs.json index 636bdf818f..7fa321b7b9 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/cs.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/cs.json @@ -274,7 +274,8 @@ "FirstEdition": "První vydání", "ThankYou": "Děkuji!", "CheckboxMandatory": "Chcete-li pokračovat, musíte toto zaškrtnout!", - "SelectUITheme": "Vyberte téma uživatelského rozhraní" - + "SelectUITheme": "Vyberte téma uživatelského rozhraní", + "CreateSolutionFolder": "Vytvořit složku řešení", + "CreateSolutionFolderOption": "Určuje, zda bude projekt v nové složce ve výstupní složce nebo přímo výstupní složce." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/de.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/de.json index f978fb8784..28821200c8 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/de.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/de.json @@ -274,7 +274,8 @@ "FirstEdition": "Erste Ausgabe", "ThankYou": "Dankeschön!", "CheckboxMandatory": "Sie müssen dies überprüfen, um fortzufahren!", - "SelectUITheme": "Wählen Sie UI-Design aus" - + "SelectUITheme": "Wählen Sie UI-Design aus", + "CreateSolutionFolder": "Lösungsordner erstellen", + "CreateSolutionFolderOption": "Gibt an, ob sich das Projekt in einem neuen Ordner im Ausgabeordner oder direkt im Ausgabeordner befindet." } } \ 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 35698cd39c..63e795efee 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -416,6 +416,9 @@ "CompanyInfo": "Company Info", "Date": "Date", "WhoWeAre_Partner": "Who We Are", - "WhoWeAre_Expert": "About Me" + "WhoWeAre_Expert": "About Me", + "CreateSolutionFolder": "Create Solution Folder", + "CreateSolutionFolderOption": "Specifies if the project will be in a new folder in the output folder or directly the output folder.", + "BooksPageTitle": "ABP Books" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json index f63600d48a..88f28ede88 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/es.json @@ -274,7 +274,8 @@ "FirstEdition": "Primera edición", "ThankYou": "¡Gracias!", "CheckboxMandatory": "¡Debes marcar esto para continuar!", - "SelectUITheme": "Seleccione el tema de la interfaz de usuario" - + "SelectUITheme": "Seleccione el tema de la interfaz de usuario", + "CreateSolutionFolder": "Crear carpeta de soluciones", + "CreateSolutionFolderOption": "Especifica si el proyecto estará en una nueva carpeta en la carpeta de salida o directamente en la carpeta de salida." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json index bcfa1901a6..c41f58399d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json @@ -2,7 +2,7 @@ "culture": "fi", "texts": { "GetStarted": "Aloitus - Käynnistysmallit", - "Create": "Luoda", + "Create": "Luo", "NewProject": "Uusi projekti", "DirectDownload": "Suora lataus", "ProjectName": "Projektin nimi", @@ -14,7 +14,7 @@ "CreateNow": "Luo nyt", "TheStartupProject": "Käynnistysprojekti", "Tutorial": "Opetusohjelma", - "UsingCLI": "CLI: n käyttö", + "UsingCLI": "CLI:n käyttö", "SeeDetails": "Katso yksityiskohdat", "AbpShortDescription": "ABP Framework on täydellinen infrastruktuuri nykyaikaisten verkkosovellusten luomiseen noudattamalla ohjelmistokehityksen parhaita käytäntöjä ja käytäntöjä.", "SourceCodeUpper": "LÄHDEKOODI", @@ -23,9 +23,9 @@ "Architecture": "Arkkitehtuuri", "Modular": "Modulaarinen", "DontRepeatYourself": "Älä toista itseäsi", - "DeveloperFocused": "Kehittäjä kohdennettu", - "FullStackApplicationInfrastructure": "Täyden pinon sovellusinfrastruktuuri.", - "DomainDrivenDesign": "Toimialueohjattu suunnittelu", + "DeveloperFocused": "Kehittäjä-kohdennettu", + "FullStackApplicationInfrastructure": "Full stack sovellusinfrastruktuuri.", + "DomainDrivenDesign": "Domain Driven Design", "DomainDrivenDesignExplanation": "Suunniteltu ja kehitetty DDD-mallien ja -periaatteiden perusteella. Tarjoaa kerrostetun mallin sovelluksellesi.", "Authorization": "Valtuutus", "AuthorizationExplanation": "Edistynyt käyttöoikeudet käyttäjän, roolin ja tarkan käyttöjärjestelmän avulla. Rakennettu Microsoft Identity -kirjastoon.", @@ -64,7 +64,7 @@ "TransactionManagement": "Tapahtumien hallinta", "AuditLogging": "Tarkastusten kirjaaminen", "Caching": "Välimuisti", - "Multitenancy": "Monivärinen", + "Multitenancy": "Monivuokraus", "DataFiltering": "Tietojen suodatus", "ConventionOverConfiguration": "Kokoonpanon määritys", "ConventionOverConfigurationExplanation": "ABP toteuttaa oletusarvoisesti yleiset sovelluskäytännöt minimaalisella tai nolla-kokoonpanolla.", @@ -136,8 +136,8 @@ "ValueObject": "Arvo-objekti", "ApplicationService": "Sovelluspalvelu", "DataTransferObject": "Tiedonsiirtokohde", - "AggregateRootEntity": "Kokonaisjuuri, entiteetti", - "AutoRESTAPIsExplanation": "ABP voi määrittää sovelluspalvelut automaattisesti API-ohjaimiksi sopimuksen mukaan.", + "AggregateRootEntity": "Aggregate Root, entiteetti", + "AutoRESTAPIsExplanation": "ABP voi määrittää sovelluspalvelut automaattisesti API-kontrollereiksi sopimuksen mukaan.", "DynamicClientProxiesExplanation": "Kuluta helposti API: si JavaScript- ja C# -asiakkailta.", "DistributedEventBusWithRabbitMQIntegrationExplanation": "Julkaise ja kuluta jaettuja tapahtumia helposti käyttämällä sisäänrakennettua hajautettua tapahtumaväylää, jossa on käytettävissä RabbitMQ-integraatio.", "TestInfrastructureExplanation": "Kehys on kehitetty yksikkö- ja integraatiotestaus ajatellen. Tarjoaa sinulle perusluokkia helpottamaan. Käynnistysmalleissa on valmiiksi konfiguroitu testaus.", @@ -159,9 +159,9 @@ "UiFramework": "Käyttöliittymäkehys", "EmailAddress": "Sähköpostiosoite", "Mobile": "Matkapuhelin", - "ReactNative": "Reagoi Native", + "ReactNative": "React Native", "Strong": "Vahva", - "Complete": "Saattaa loppuun", + "Complete": "Valmis", "BasedLayeringModel": "Perustuva kerrosmalli", "Microservice": "Mikropalvelu", "Compatible": "Yhteensopiva", @@ -172,8 +172,9 @@ "DynamicClientProxyDocument": "Katso dynaamisen asiakkaan välityspalvelimen dokumentaatiot JavaScript ja C#.", "EmailSMSAbstractionsDocument": "Katso lisätietoja sähköpostitse ja tekstiviestien lähettäminen -asiakirjoista.", "CreateProjectWizard": "Tämä ohjattu toiminto luo uuden projektin käynnistysmallista, joka on määritetty oikein aloittamaan projekti.", - "TieredOption": "Luo porrastetun ratkaisun, jossa Web- ja Http-API-kerrokset erotetaan fyysisesti. Jos sitä ei ole valittu, luodaan kerrostettu ratkaisu, joka on vähemmän monimutkainen ja sopii useimpiin tilanteisiin.", + "TieredOption": "Luo monikerros ratkaisun, jossa Web- ja Http-API-kerrokset erotetaan fyysisesti. Jos sitä ei ole valittu, luodaan kerrostettu ratkaisu, joka on vähemmän monimutkainen ja sopii useimpiin tilanteisiin.", "SeparateIdentityServerOption": "Erottaa palvelinpuolen kahteen sovellukseen: Ensimmäinen on identiteettipalvelimelle ja toinen palvelinpuolen HTTP-sovellusliittymälle.", + "ProgressiveWebApplicationOption": "Määrittää projektin progressiiviseksi verkkosovellukseksi (PWA)", "UseslatestPreVersion": "Käyttää uusinta julkaisua edeltävää versiota", "ReadTheDocumentation": " Lue Dokumentaatio ", "Documentation": "Dokumentointi", @@ -214,7 +215,11 @@ "SeeDocs": "Katso Docs", "None": "Ei mitään", "Application": "Sovellus", + "ApplicationExplanation": "Luo täysin kerrostetun ratkaisun, joka perustuu Domain Driven Design -käytäntöihin. Suositellaan pitkäaikaisiin projekteihin, jotka tarvitsevat ylläpidettävän ja laajennettavan koodikannan.", + "ApplicationNoLayer": "Sovellus (yksi kerros)", + "ApplicationNoLayerExplanation": "Luo yksikerroksisen verkkosovelluksen. Suositellaan yksinkertaisemman ja helposti ymmärrettävän arkkitehtuurin sovelluksen rakentamiseen.", "Module": "Moduuli", + "ModuleExplanation": "Luo uudelleenkäytettävän, täysin kerrostetun sovellusmoduuliratkaisun. Voit käyttää tätä vaihtoehtoa luodaksesi moduuleja modulaariselle sovelluksellesi.", "PackageName": "Paketin nimi", "LicenseURL": "Lisenssin URL-osoite", "License": "Lisenssi", @@ -274,7 +279,145 @@ "FirstEdition": "Ensimmäinen painos", "ThankYou": "Kiitos!", "CheckboxMandatory": "Sinun on tarkistettava tämä jatkaaksesi!", - "SelectUITheme": "Valitse käyttöliittymän teema" - + "CreateSolutionFolder": "Luo ratkaisukansio", + "CreateSolutionFolderOption": "Määrittää, onko projekti tuloskansion uudessa kansiossa vai suoraan tulostekansiossa.", + "UserInterface": "Käyttöliittymä", + "APIGateway": "API-yhdyskäytävä", + "Database": "Tietokanta", + "Saas": "Saas", + "OpenSourceWebApp": "Avoimen lähdekoodin
verkkosovellus", + "Framework": "Framework", + "AuditLoggingExplanation": "Seuraa automaattisesti kaikkia järjestelmäsi toimintoja ja tietojen muutoksia.", + "AbpNewCommandExplanation": "Luo uusia ratkaisuja käyttämällä ABP-käynnistysmalleja.", + "AbpAddModuleCommandExplanation": "Asenna valmiiksi rakennetut sovellusmoduulit ratkaisuusi", + "AbpUpdateCommandExplanation": "Päivittää automaattisesti kaikki ABP:hen liittyvät NuGet- ja NPM-paketit ratkaisussasi.", + "ExploreAllCLICommands": "Tutustu kaikkiin CLI-komentoihin", + "ExploreDocumentationAndGuides": "Tutustu kattavaan dokumentaatioon ja oppaisiin.", + "Documentations": "Dokumentointi", + "Views": "Näkymät", + "EnterYouEmailToGetNews": "Kirjoita sähköpostiosoitteesi saadaksesi viimeisimmät uutiset ABP Frameworkista", + "Tiered": "Monikerros", + "SeparateIdentityServer": "Erillinen identiteettipalvelin", + "ProgressiveWebApplication": "Progressiivinen verkkosovellus", + "Preview": "Esikatsele", + "CreateANewSolution": "Luo uusi ratkaisu", + "ABPFrameworkFeatures": "ABP-kehyksen Ominaisuudet", + "Commercial": "Kaupallinen", + "ThirdPartyTools": "Kolmannen osapuolen työkalut", + "Back": "Takaisin", + "Community": "Yhteisö", + "SeeMore": "Katso lisää", + "DetailsOfTheEBook": "E-kirjan tiedot", + "JoinOurMarketingNewsletter": "Liity markkinointiuutiskirjeeseemme", + "FrameworkNewsletterConfirmationMessage": "Hyväksyn käyttöehdot ja tietosuojakäytäntö.", + "GetYourFreeEBook": "Hanki ilmainen DDD-e-kirjasi ", + "EverythingYouNeedToKnow": "Kaikki mitä sinun tarvitsee tietää.", + "PreOrderNow": "Ennakkotilaa nyt", + "UITheming": "UI Theming", + "UIThemingExplanation": "Luo uudelleenkäytettäviä käyttöliittymäteemoja ja asetteluja tai käytä jotakin valmiista käyttöliittymäteemoista.", + "DataFilteringExplanation2": "Suodata automaattisesti tietokannan kyselyt toteuttaaksesi helposti malleja, kuten pehmeän poiston ja usean vuokrauksen.", + "NeedHelp": "Tarvitsetko apua?", + "GiveYourProjectAName": "Anna projektillesi nimi", + "SelectProjectType": "Valitse Projektin tyyppi", + "SelectUIFramework": "Valitse UI Framework", + "SelectDatabaseProvider": "Valitse Tietokannan tarjoaja", + "SelectDatabaseManagementSystem": "Valitse Tietokannan hallintajärjestelmä", + "InstallingTheABPCLI": "ABP CLI:n asentaminen", + "CreateYourProjectNow": "Luo projektisi nyt", + "OrderOn": "Tilaa {0}", + "DownloadFreeDDDBook": "Lataa ilmainen DDD-kirja", + "WhatIsABPFramework": "Mikä on ABP-kehys?", + "TenantDatabase": "Vuokralaisen {0} tietokanta", + "SharedDatabase": "Jaettu tietokanta", + "ConnectionResolver": "Yhteyden ratkaisija", + "TenantBasedDataFilter": "Vuokralaispohjainen tietosuodatin", + "ApplicationCode": "Sovelluskoodi", + "TenantResolution": "Vuokralaisen päättely", + "TenantUser": "Vuokralaisen {0} käyttäjä", + "CardTitle": "Kortin otsikko", + "View": "Näytä", + "Model": "Malli", + "Email": "Sähköposti", + "Password": "Salasana", + "Address": "Osoite", + "Gender": "Sukupuoli", + "Male": "Mies", + "Female": "Nainen", + "Submit": "Lähetä", + "Unspecified": "Määrittelemätön", + "StaticFileMiddleware": "Staattisen tiedoston väliohjelmisto", + "RazorViewEngine": "Razor View Engine", + "PhysicalFiles": "Fyysiset tiedostot (wwwroot)", + "EmbeddedFiles": "Upotetut tiedostot (DLL)", + "DynamicFiles": "Dynaamiset tiedostot (muisti)", + "BuildSolutionsWithAbp": "Rakenna ylläpidettäviä .NET-ratkaisuja noudattamalla ohjelmistokehityksen parhaita käytäntöjä käyttämällä ABP:tä.", + "BuyOnAmazon": "Osta Amazonista", + "BuyOnPackt": "Osta Packtista", + "Discounted": "Alennettu", + "MasteringAbpFramework_Book_KeyFeatures": "Avainominaisuudet", + "MasteringAbpFramework_Book_Key_Features_Description_1": "Luo kestäviä, ylläpidettäviä, modulaarisia ja skaalautuvia ohjelmistoratkaisuja ABP Frameworkin avulla.", + "MasteringAbpFramework_Book_Key_Features_Description_2": "Opi toteuttamaan SOLID-periaatteita ja verkkotunnuslähtöistä suunnittelua verkkosovelluksissasi.", + "MasteringAbpFramework_Book_Key_Features_Description_3": "Tutustu kuinka ABP Framework nopeuttaa kehityssykliäsi automatisoimalla toistuvia tehtäviä.", + "MasteringAbpFramework_Book_Description": "Kirjan kuvaus", + "MasteringAbpFramework_Book_Description_Details_1": "ABP Framework on täydellinen infrastruktuuri nykyaikaisten verkkosovellusten luomiseen seuraavilla ohjelmistoilla\n parhaiden käytäntöjen ja käytäntöjen kehittäminen. ABP:n korkean tason kehyksen ja ekosysteemin avulla voit\n ottaa käyttöön Älä toista itseäsi (DRY) -periaatteen ja keskittyä liiketoimintakoodiisi.", + "MasteringAbpFramework_Book_Description_Details_2": "Tämä ABP Frameworkin luojan kirjoittama kirja auttaa sinua saamaan täydellisen käsityksen\n frameworkista ja modernit verkkosovellusten kehitystekniikat. Vaiheittaisilla selityksillä olennaisista\n käsitteitä ja käytännön esimerkeistä, ymmärrät nykyaikaisen verkkoratkaisun vaatimukset ja kuinka ABP\n Framework tekee omien ratkaisujen kehittämisestä miellyttävää. Löydät yleiset vaatimukset\n yritysverkkosovellusten kehittämiseen ja ABP:n tarjoamaan infrastruktuuriin tutustumiseen. Läpi\n kirjan, opit ohjelmistokehityksen parhaita käytäntöjä ylläpidettävien ja modulaaristen verkkosovellusten rakentamiseen.", + "MasteringAbpFramework_Book_Description_Details_3": "Tämän kirjan luettuasi pystyt luomaan täydellisen verkkoratkaisun, jota on helppo kehittää,\n huoltaa ja testata.", + "MasteringAbpFramework_Book_WhatYouWillLearn": "Mitä opit", + "MasteringAbpFramework_Book_What_You_Will_Learn_1": "Määritä kehitysympäristö ja aloita ABP Frameworkin käyttö.", + "MasteringAbpFramework_Book_What_You_Will_Learn_2": "Työskentele Entity Framework Coren ja MongoDB:n kanssa kehittääksesi tiedon käyttökerroksesi.", + "MasteringAbpFramework_Book_What_You_Will_Learn_3": "Ymmärrä monialaiset huolenaiheet ja kuinka ABP automatisoi toistuvia tehtäviä.", + "MasteringAbpFramework_Book_What_You_Will_Learn_4": "Ota hallintaan toimialuelDomain Driven Design -ähtöisen suunnittelun toteuttaminen ABP Frameworkin avulla.", + "MasteringAbpFramework_Book_What_You_Will_Learn_5": "Rakenna käyttöliittymäsivuja ja komponentteja ASP.NET Core MVC:llä (Razor Pages) ja Blazorilla.", + "MasteringAbpFramework_Book_What_You_Will_Learn_6": "Työskentele monivuokrauksen kanssa modulaaristen verkkosovellusten luomiseksi.", + "MasteringAbpFramework_Book_What_You_Will_Learn_7": "Ymmärrä modulaarisuus ja luo uudelleenkäytettäviä sovellusmoduuleja.", + "MasteringAbpFramework_Book_What_You_Will_Learn_8": "Kirjoita yksikkö-, integrointi- ja käyttöliittymätestejä ABP Frameworkin avulla.", + "MasteringAbpFramework_Book_WhoIsThisBookFor": "Kenelle tämä kirja on tarkoitettu", + "MasteringAbpFramework_Book_WhoIsThisBookFor_Description": "Tämä kirja on tarkoitettu verkkokehittäjille, jotka haluavat oppia ohjelmistoarkkitehtuureja ja parhaita käytäntöjä\n ylläpidettävien web-pohjaisten ratkaisujen kehittämiseen Microsoft-tekniikoilla ja ABP Frameworkilla. C#:n\n ja ASP.NET Core: perustiedot tarvitaan tätä kirjaa luettaessa.", + "ComputersAndTechnology": "Tietokoneet ja tekniikka", + "BuildingMicroserviceSolutions": "Building Microservice Solutions", + "MicroserviceBookPracticalGuide": "Tämä kirja on opas mikropalvelupohjaisten sovellusten kehittämiseen ja hallintaan ABP-kehyksen avulla. Siinä viitataan .NET Microservice Sample Reference Application: eShopOnContainers -sovellukseen ja käsitellään ABP Frameworkia käyttäviä arkkitehtonisia suunnittelu- ja toteutusmenetelmiä. Tämän kirjan loppuun mennessä opit, kuinka ABP lähestyy yleisiä mikropalveluiden monimutkaisia ongelmia, kuten valtuutusta, hajautettuja tapahtumia, mikropalvelujen välistä viestintää, käyttöönottoa jne.", + "IntroducingTheSolution": "Esittelyssä eShopOnAbp-ratkaisu", + "RunningTheSolution": "Ratkaisun suorittaminen", + "UnderstandingTheAuthenticationSystem": "Todennusjärjestelmän ymmärtäminen", + "ExploringTheApplications": "Sovellusten tutkiminen", + "UnderstandingTheAPIGateways": "API-yhdyskäytävien ymmärtäminen", + "DevelopingTheMicroservices": "Mikropalveluiden kehittäminen", + "UnderstandingTheInfrastructure": "Infrastruktuurin ymmärtäminen", + "DiggingInTheUseCases": "Käyttötapausten kuvaaminen", + "DeployingTheSolution": "Ratkaisun käyttöönotto", + "ThisBookIsInDraftStageAndIsNotCompletedYet": "Tämä kirja on luonnosvaiheessa eikä ole vielä valmis.", + "Authors": "Tekijät", + "MicroserviceEBook": "Microservice E-Book", + "SelectUITheme": "Valitse käyttöliittymän teema", + "LeptonXLiteTheme": "LeptonX Lite -teema", + "BasicTheme": "Perusteema", + "LeptonXLiteThemeInfo": " Moderni ja tyylikäs Bootstrap-käyttöliittymäteema. Ihanteellinen, jos haluat tuotantovalmiin käyttöliittymäteeman. Tämä on uusin teema ja oletuksena.", + "BasicThemeInfo": "Minimalistinen käyttöliittymäteema tavallisilla Bootstrap-väreillä ja -tyyleillä. Ihanteellinen, jos rakennat oman käyttöliittymäteeman.", + "SeeDocumentation": "Katso dokumentaatio.", + "SeeFullScreen": "🖼️ Katso kuvakaappaus", + "BuildingMicroserviceSolutionsShortDescription": "Tämä kirja on opas mikropalvelupohjaisten sovellusten kehittämiseen ja hallintaan ABP-kehyksen avulla.", + "InstallAbpCliMessage": "Asenna ABP CLI komentoriviltä, jos et ole asentanut sitä aiemmin:", + "Terminal": "Terminaali", + "Copy": "Kopio", + "RunTheFollowingCommand": "Suorita seuraava komento komentorivillä:", + "ChangeSolutionOptionsBelow": "Voit muuttaa alla olevia ratkaisuvaihtoehtoja.", + "MultiLayerApplication": "Monikerroksinen sovellus", + "MultiLayerApplicationExplanation1": "Luo täysin kerrostetun ratkaisun, joka perustuu Domain Driven Designin käytäntöihin.", + "MultiLayerApplicationExplanation2": "Suositellaan pitkäaikaisiin projekteihin, jotka tarvitsevat ylläpidettävän ja laajennettavan koodikannan.", + "SingleLayerApplication": "Single-layer
sovellus", + "SingleLayerApplicationExplanation1": "Luo yksikerroksisen verkkosovelluksen.", + "SingleLayerApplicationExplanation2": "Suositellaan yksinkertaisemman ja helposti ymmärrettävän arkkitehtuurin sovelluksen rakentamiseen.", + "ApplicationModule": "Sovellus
moduuli", + "SeeTheScreenshot": "Katso kuvakaappaus", + "ApplicationModuleExplanation1": "Luo uudelleenkäytettävän, täysin kerrostetun sovellusmoduuliratkaisun.", + "ApplicationModuleExplanation2": "Voit käyttää tätä vaihtoehtoa luodaksesi moduuleja modulaariselle sovelluksellesi.", + "Expert_": "Asiantuntija", + "Partner_": "Kumppanuus", + "WebSite": "Verkkosivusto", + "Expert_Year": "Asiantuntijavuosi", + "CompanyInfo": "Yritystiedot", + "Date": "Päivämäärä", + "WhoWeAre_Partner": "Keitä olemme", + "WhoWeAre_Expert": "Minusta" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json index 90314de979..e66bc4082a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json @@ -274,7 +274,8 @@ "FirstEdition": "Première édition", "ThankYou": "Merci!", "CheckboxMandatory": "Vous devez vérifier cela pour continuer !", - "SelectUITheme": "Sélectionnez le thème de l'interface utilisateur" - + "SelectUITheme": "Sélectionnez le thème de l'interface utilisateur", + "CreateSolutionFolder": "Créer un dossier de solutions", + "CreateSolutionFolderOption": "Spécifie si le projet sera dans un nouveau dossier dans le dossier de sortie ou directement dans le dossier de sortie." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json index 35232cf828..1b4bd1c879 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json @@ -274,7 +274,8 @@ "FirstEdition": "प्रथम संस्करण", "ThankYou": "शुक्रिया!", "CheckboxMandatory": "आगे बढ़ने के लिए आपको इसे जांचना होगा!", - "SelectUITheme": "यूआई थीम का चयन करें" - + "SelectUITheme": "यूआई थीम का चयन करें", + "CreateSolutionFolder": "समाधान फ़ोल्डर बनाएँ", + "CreateSolutionFolderOption": "निर्दिष्ट करता है कि प्रोजेक्ट आउटपुट फ़ोल्डर या सीधे आउटपुट फ़ोल्डर में एक नए फ़ोल्डर में होगा या नहीं।" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json index 2a6ebf2feb..a3f4d84e26 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hu.json @@ -392,6 +392,8 @@ "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" + "SeeFullScreen": "🖼️ Nézze meg a képernyőképet", + "CreateSolutionFolder": "Hozzon létre megoldási mappát", + "CreateSolutionFolderOption": "Meghatározza, hogy a projekt egy új mappában legyen-e a kimeneti mappában vagy közvetlenül a kimeneti mappában." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/is.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/is.json index f3449c4798..15941b2f20 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/is.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/is.json @@ -274,7 +274,8 @@ "FirstEdition": "Fyrsta útgáfa", "ThankYou": "Þakka þér!", "CheckboxMandatory": "Þú þarft að smella hér til að halda áfram!", - "SelectUITheme": "Veldu UI þema" - + "SelectUITheme": "Veldu UI þema", + "CreateSolutionFolder": "Búðu til lausnarmöppu", + "CreateSolutionFolderOption": "Tilgreinir hvort verkefnið verður í nýrri möppu í úttaksmöppunni eða beint í framleiðslumöppunni." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json index 2e37c54280..5cc16b4646 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json @@ -274,7 +274,8 @@ "FirstEdition": "Prima Edizione", "ThankYou": "Grazie!", "CheckboxMandatory": "Devi mettere la spunta su questo per procedere!", - "SelectUITheme": "Seleziona Tema dell'interfaccia utente" - + "SelectUITheme": "Seleziona Tema dell'interfaccia utente", + "CreateSolutionFolder": "Crea cartella della soluzione", + "CreateSolutionFolderOption": "Specifica se il progetto si troverà in una nuova cartella nella cartella di output o direttamente nella cartella di output." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/nl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/nl.json index eb3ac12a86..4bc2cffb2a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/nl.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/nl.json @@ -274,7 +274,8 @@ "FirstEdition": "Eerste editie", "ThankYou": "Bedankt!", "CheckboxMandatory": "Je moet dit aanvinken om verder te gaan!", - "SelectUITheme": "Selecteer UI-thema" - + "SelectUITheme": "Selecteer UI-thema", + "CreateSolutionFolder": "Oplossingsmap maken", + "CreateSolutionFolderOption": "Geeft aan of het project zich in een nieuwe map in de uitvoermap bevindt of direct in de uitvoermap." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pl-PL.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pl-PL.json index 1adc7b009f..cb33d69ab6 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pl-PL.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pl-PL.json @@ -274,7 +274,8 @@ "FirstEdition": "Pierwsza edycja", "ThankYou": "Dziękuję Ci!", "CheckboxMandatory": "Musisz to sprawdzić, aby kontynuować!", - "SelectUITheme": "Wybierz motyw interfejsu" - + "SelectUITheme": "Wybierz motyw interfejsu", + "CreateSolutionFolder": "Utwórz folder rozwiązania", + "CreateSolutionFolderOption": "Określa, czy projekt znajdzie się w nowym folderze w folderze wyjściowym, czy bezpośrednio w folderze wyjściowym." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pt-BR.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pt-BR.json index 75fc4d8864..b468c06403 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pt-BR.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/pt-BR.json @@ -274,7 +274,8 @@ "FirstEdition": "Primeira edição", "ThankYou": "Obrigado!", "CheckboxMandatory": "Você precisa verificar isso para continuar!", - "SelectUITheme": "Selecione o tema da interface do usuário" - + "SelectUITheme": "Selecione o tema da interface do usuário", + "CreateSolutionFolder": "Criar Pasta de Solução", + "CreateSolutionFolderOption": "Especifica se o projeto estará em uma nova pasta na pasta de saída ou diretamente na pasta de saída." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ro-RO.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ro-RO.json index e91664d726..003309aa74 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ro-RO.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ro-RO.json @@ -275,7 +275,7 @@ "ThankYou": "Vă mulţumim!", "CheckboxMandatory": "Trebuie să bifaţi asta pentru a continua!", "SelectUITheme": "Selectați Tema UI", - - + "CreateSolutionFolder": "Creați folderul de soluții", + "CreateSolutionFolderOption": "Specifică dacă proiectul va fi într-un folder nou în folderul de ieșire sau direct folderul de ieșire." } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ru.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ru.json index 05378ac5e1..8df80b7422 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ru.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/ru.json @@ -274,6 +274,8 @@ "FirstEdition": "Первое издание", "ThankYou": "Спасибо!", "CheckboxMandatory": "Вам необходимо проверить это, чтобы продолжить!", - "SelectUITheme": "Выберите тему пользовательского интерфейса" + "SelectUITheme": "Выберите тему пользовательского интерфейса", + "CreateSolutionFolder": "Создать папку решения", + "CreateSolutionFolderOption": "Указывает, будет ли проект находиться в новой папке в выходной папке или непосредственно в выходной папке." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sk.json index a7cdab3afd..f578158624 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sk.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sk.json @@ -274,7 +274,8 @@ "FirstEdition": "Prvá edícia", "ThankYou": "Ďakujem!", "CheckboxMandatory": "Ak chcete pokračovať, musíte to skontrolovať!", - "SelectUITheme": "Vyberte tému používateľského rozhrania" - + "SelectUITheme": "Vyberte tému používateľského rozhrania", + "CreateSolutionFolder": "Vytvorte priečinok riešení", + "CreateSolutionFolderOption": "Určuje, či bude projekt v novom priečinku vo výstupnom priečinku alebo priamo vo výstupnom priečinku." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sl.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sl.json index b7a60ef904..c7a1f495f2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sl.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sl.json @@ -274,7 +274,8 @@ "FirstEdition": "Prva izdaja", "ThankYou": "Hvala vam!", "CheckboxMandatory": "Za nadaljevanje morate to preveriti!", - "SelectUITheme": "Izberite temo uporabniškega vmesnika" - + "SelectUITheme": "Izberite temo uporabniškega vmesnika", + "CreateSolutionFolder": "Ustvarite mapo rešitev", + "CreateSolutionFolderOption": "Podaja, ali bo projekt v novi mapi v izhodni mapi ali neposredno v izhodni mapi." } } \ 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 969bd72694..6834f5da0d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json @@ -374,7 +374,10 @@ "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.", - "SelectUITheme": "Vyberte téma uživatelského rozhraní" + "CreateSolutionFolder": "Çözüm Klasörü Oluşturun", + "CreateSolutionFolderOption": "Projenin çıktı klasöründe yeni bir klasörde mi yoksa doğrudan çıktı klasöründe mi olacağını belirtir.", + "SelectUITheme": "UI Temasını Seçin", + "BooksPageTitle": "ABP Kitapları" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/vi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/vi.json index faa0b53211..b67ff75480 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/vi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/vi.json @@ -274,7 +274,8 @@ "FirstEdition": "Ấn bản đầu tiên", "ThankYou": "Cảm ơn bạn!", "CheckboxMandatory": "Bạn cần kiểm tra điều này để tiếp tục!", - "SelectUITheme": "Chọn chủ đề giao diện người dùng" - + "SelectUITheme": "Chọn chủ đề giao diện người dùng", + "CreateSolutionFolder": "Tạo thư mục giải pháp", + "CreateSolutionFolderOption": "Chỉ định xem dự án sẽ nằm trong một thư mục mới trong thư mục đầu ra hay trực tiếp trong thư mục đầu ra." } } \ 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 a6ef45db1d..7fca00dd3e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json @@ -408,6 +408,17 @@ "ApplicationModule": "应用程序模块", "SeeTheScreenshot": "如截图所示", "ApplicationModuleExplanation1": "创建可重用的、完全分层的应用程序模块解决方案。", - "ApplicationModuleExplanation2": "您可以使用此选项为您的模块化应用程序创建模块。" + "ApplicationModuleExplanation2": "您可以使用此选项为您的模块化应用程序创建模块。", + "Expert_": "专家", + "Partner_": "合作伙伴", + "WebSite": "网站", + "Expert_Year": "专业年限", + "CompanyInfo": "公司信息", + "Date": "日期", + "WhoWeAre_Partner": "关于我们", + "WhoWeAre_Expert": "关于我", + "CreateSolutionFolder": "创建解决方案文件夹", + "CreateSolutionFolderOption": "指定项目是位于输出文件夹中的新文件夹中,还是直接位于输出文件夹中。", + "BooksPageTitle": "ABP书籍" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hant.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hant.json index baa41ba088..e3e3ded37c 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hant.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hant.json @@ -274,7 +274,8 @@ "FirstEdition": "第一版", "ThankYou": "謝謝!", "CheckboxMandatory": "你需要檢查這個才能繼續!", - "SelectUITheme": "選擇 UI 主題" - + "SelectUITheme": "選擇 UI 主題", + "CreateSolutionFolder": "創建解決方案文件夾", + "CreateSolutionFolderOption": "指定項目是位於輸出文件夾中的新文件夾中,還是直接位於輸出文件夾中。" } } \ No newline at end of file diff --git a/common.props b/common.props index 22b86f2a2f..8c0959fa3c 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 7.1.1 + 7.2.0 $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index c6c3d69ad7..ca2067980e 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -66,6 +66,8 @@ After you have installed these NuGet packages, you need to configure your projec } ```` +> You have to configure a storage for Hangfire. + 2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class ````csharp diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md index 84e746f73d..c6e19fb48a 100644 --- a/docs/en/Background-Jobs.md +++ b/docs/en/Background-Jobs.md @@ -75,6 +75,42 @@ This job simply uses `IEmailSender` to send emails (see [email sending document] A background job should not hide exceptions. If it throws an exception, the background job is automatically re-tried after a calculated waiting time. Hide exceptions only if you don't want to re-run the background job for the current argument. +#### Cancelling Background Jobs + +If your background task is cancellable, then you can use the standard [Cancellation Token](Cancellation-Token-Provider.md) system to obtain a `CancellationToken` to cancel your job when requested. See the following example that uses the `ICancellationTokenProvider` to obtain the cancellation token: + +```csharp +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace MyProject +{ + public class LongRunningJob : AsyncBackgroundJob, ITransientDependency + { + private readonly ICancellationTokenProvider _cancellationTokenProvider; + + public LongRunningJob(ICancellationTokenProvider cancellationTokenProvider) + { + _cancellationTokenProvider = cancellationTokenProvider; + } + + public override async Task ExecuteAsync(LongRunningJobArgs args) + { + foreach (var id in args.Ids) + { + _cancellationTokenProvider.Token.ThrowIfCancellationRequested(); + await ProcessAsync(id); // code omitted for brevity + } + } + } +} +``` + +> A cancellation operation might be needed if the application is shutting down and we don't want to block the application in the background job. This example throws an exception if the cancellation is requested. So, the job will be retried the next time the application starts. If you don't want that, just return from the `ExecuteAsync` method without throwing any exception (you can simply check the `_cancellationTokenProvider.Token.IsCancellationRequested` property). + #### Job Name Each background job has a name. Job names are used in several places. For example, RabbitMQ provider uses job names to determine the RabbitMQ Queue names. diff --git a/docs/en/Background-Workers-Hangfire.md b/docs/en/Background-Workers-Hangfire.md index 5a456b1092..9506eb97a7 100644 --- a/docs/en/Background-Workers-Hangfire.md +++ b/docs/en/Background-Workers-Hangfire.md @@ -40,6 +40,50 @@ public class YourModule : AbpModule > Hangfire background worker integration provides an adapter `HangfirePeriodicBackgroundWorkerAdapter` to automatically load any `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived classes as `IHangfireBackgroundWorker` instances. This allows you to still to easily switch over to use Hangfire as the background manager even you have existing background workers that are based on the [default background workers implementation](Background-Workers.md). +## Configuration + +You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package). + +After you have installed these NuGet packages, you need to configure your project to use Hangfire. + +1.First, we change the `Module` class (example: `HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method: + +````csharp + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + //... other configarations. + + ConfigureHangfire(context, configuration); + } + + private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) + { + context.Services.AddHangfire(config => + { + config.UseSqlServerStorage(configuration.GetConnectionString("Default")); + }); + } +```` + +> You have to configure a storage for Hangfire. + +2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class + +````csharp + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + + // ... others + + app.UseHangfireDashboard(); //should add to the request pipeline before the app.UseConfiguredEndpoints() + app.UseConfiguredEndpoints(); + } +```` + ## Create a Background Worker `HangfireBackgroundWorkerBase` is an easy way to create a background worker. diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/POST.md b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/POST.md new file mode 100644 index 0000000000..14571157b6 --- /dev/null +++ b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/POST.md @@ -0,0 +1,37 @@ +# Migrating from MS-SQL to Postgresql + +![sql-server-to-postgres](images/sql-server-to-postgres.jpg) + +## Introduction + +Database migration is a common practice for organizations that want to move from one database system to another. This can be for a variety of reasons, including cost, performance, and features. In this article, we will discuss the process of migrating a database from MS-SQL to PostgreSQL, the challenges that may arise during the migration, and how to overcome them. And we recently moved the main database of https://abp.io platform from MS-SQL to PostgreSQL. + +In our case, we decided to switch our database from Microsoft SQL Server (MS-SQL) to PostgreSQL because we wanted to move our on-premise platform to Azure. We’ve also found out that the cost of the license for MS-SQL on Azure was significantly higher than PostgreSQL. After conducting a cost-benefit analysis, we decided to migrate our database to PostgreSQL to save costs. + +Before migrating to Azure, we decided to switch our database from MS-SQL to PostgreSQL on-premise first. This gave us the opportunity to test and fine-tune the migration process before making the final switch to Azure. + +## Challenges + +Despite using a third-party tool(DBConvert for MySQL & PostgreSQL) for the migration, we faced three main problems when exporting data to PostgreSQL. Firstly, some tables with plain text had utf-8 encoding problems. We overcame this problem by dump-restoring these tables. + +![db-converter](images/db-converter.jpg) + + +Secondly, our database had to be case-insensitive, but PostgreSQL does not have this as a default configuration. We handled it using `citext` with the ABP migration service. + +![citext-1](images/citext-1.jpg) +![citext-2](images/citext-2.jpg) +![citext-3](images/citext-3.jpg) + + +While everything was proceeding very smooth, we faced one last problem: importing binary data, such as the content of the NuGet packages. It was hard to understand that the binaries of the NuGet packages were different. Our paid commercial NuGet packages are being stored as binary data in the database. Therefore, it was the most compelling part of this migration to transfer the NuGet packages. Fortunately, we overcame the binary error. And we decided to write a custom .NET tool to move only the binary data from MS-SQL to PostgreSQL, thanks to the ABP Core team! + + +## Conclusion + +One of the benefits of using PostgreSQL is the low license costs of the Azure platform. As the main contributors of ABP, we also use ABP Framework under the hood of our abp.io websites, we could easily switch to PostgreSQL. For those who want to switch their ABP project to PostgreSQL, check out [docs.abp.io/en/abp/latest/Entity-Framework-Core-PostgreSQL](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-PostgreSQL). We had not used any MS-SQL specific function, therefore there was no need to make any changes in the repository classes. This means that the applications that were previously using MS-SQL can seamlessly switch to PostgreSQL without any modifications. + +Thanks to the flexibility of ABP, it has [PostgreSQL package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.PostgreSql) which is 100% compatible with PostgreSQL. This helped us to make this migration very smooth and seamless. + +In conclusion, migrating a database from MS-SQL to PostgreSQL can be challenging, but it can bring significant cost savings in the long run. By testing and fine-tuning the migration process before making the final switch, we were able to overcome the challenges we’d faced during the migration process. Thanks to the flexibility of ABP, we were able to make the transition with minimal code changes. Also we didn't see any big performance differences between MS-SQL and PostgreSQL. + diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg new file mode 100644 index 0000000000..4dc01228fa Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg new file mode 100644 index 0000000000..a70b0312bf Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg new file mode 100644 index 0000000000..c8a95d61a5 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg new file mode 100644 index 0000000000..9c5371d36c Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql-postges.png b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql-postges.png new file mode 100644 index 0000000000..c1766bc713 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql-postges.png differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql.png b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql.png new file mode 100644 index 0000000000..2333842ea6 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql.png differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg new file mode 100644 index 0000000000..645de45341 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/POST.md b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/POST.md new file mode 100644 index 0000000000..82384b3f67 --- /dev/null +++ b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/POST.md @@ -0,0 +1,49 @@ + +# On-Prem to Azure: Migration of abp.io Platform to Azure + + +![abpio-azure](images/abpio-azure.png) + +Migrating a Kubernetes platform with a database from our own dedicated servers to Azure can be a compelling task, but it can be a necessary one to take advantage of the benefits that the cloud service offers. In this post, we will discuss the reasons for migrating from a on-premise platform to Azure, the steps taken to create and configure the [abp.io platform](https://abp.io) on Azure, and the benefits gained from the migration. + +### On-Premise Server: The old platform + +There were several reasons for migrating from the old on-premise platform to Azure. First, the Kubernetes cluster and the database were on the same Windows server. Additionally, the Linux virtual machines in Kubernetes were on the Windows server and had limited resources. Furthermore, the Kubernetes maintenance was quite challenging, which was another reason for the migration. + +### Reasons for Moving to Azure: The new platform + +The decision to move to the cloud was made to eliminate the disadvantages of the old platform. The migration to Azure meant that the resources would be independent of each other but faster in terms of communicating with each other. The platform would also take advantage of cloud security and availability. The managed Kubernetes service that Azure offers comes with autoscaling and loadbalancing, which makes the platform more reliable. Finally, the migration to Azure would provide a better quality service to global customers and the community. + +### Before Moving to Azure + +Before migrating to Azure, we decided to switch our database from MS-SQL to PostgreSQL on-premise first. This gave us the opportunity to test and fine-tune the migration process before making the final switch to Azure.You can check the details of database migration from this article [Migrating from MS-SQL to Postgresql](https://community.abp.io/posts/migrating-from-mssql-to-postgresql-lbi5anlv). + +The platform was tested in a staging environment created on Azure with the same resources as the production environment. The staging environment was used to test and optimize the migration process, including the migration of data from the old platform to Azure, which was tested multiple times to ensure success. + +### Creating and Configuring the abp.io Platform on Azure + +Several steps were taken to create and configure the abp.io platform on Azure. [Terraform](https://www.terraform.io/) was used to create the infrastructure (VM, Private Network, AKS, Postgresql Flexible Server...), while [Ansible](https://www.ansible.com/) was used to configure the Azure resources like Terraform, Helm, Kubectl, Docker, VPN, Redis, Prometheus, Grafana, ElasticSearch, Kibana and so on.... Azure DevOps pipelines and release were used for AKS deployment. The most time-consuming part in this process was to prepare, test and optimize the terraform and ansible settings files. + +To access our platform, which is configured on a private network in Azure, we require a VPN connection. To enable this, we have installed [Wireguard](https://www.wireguard.com/) - an open source VPN service - by creating a virtual machine using Terraform and configuring it with Ansible in Azure. This approach has made the process efficient and streamlined. + +![terra-wire](images/terra-wire.png) + +The most important step was to transfer the data in both the database and Kubernetes of the volumes. rsync (remote sync commands) were used to transfer the data from Kubernetes volumes to Azure NFS through the VPN machine. Additionally, `pg_dump` and `pg_restore` were used to transfer the PostgreSQL database through the machine with VPN. + +Before the production environment, the data migration was tested many times for the staging environment. We estimated this migration to take max 1.5 hours. The ABP community was informed that there may be interruptions during the hours designated for the transition to Azure. To inform our customers and community, we created a status page before this migration. The new status page is [status.abp.io](https://status.abp.io). From now on we wil make all the infrastructural announcements on [status.abp.io](https://status.abp.io). We used [Upptime](https://upptime.js.org) which is an open-source uptime monitor and status page provider. During the migration of the production environment, the websites and databases were still up and running. After the data transfer, the only remaining step was to direct the traffic of the already standing abp.io sites to the Azure Kubernetes service via Cloudflare. + +We would like to happily state that **we were offline for only 4 minutes** during this transition. + +![az-infra](images/az-infra.png) + + +### Benefits of Moving to Azure + +The migration to Azure resulted in several benefits. The platform is now more reliable, scalable, secure, solid with built-in one-click backup and recovery capabilities for abp.io. Additionally, the critical resources are in a private network, making them more secure than the old environment. When we initially compared the speed of our abp.io sites before and after migrating to Azure, we were pleasantly surprised by the significant improvement in performance. To be honest, we did not expect such a speed increase. + +![speed](images/speed.png) + +In conclusion, migrating a Kubernetes platform with a database from on-premise to Azure is a complex process that requires careful planning and execution. However, the benefits gained from the migration make the process worthwhile. By moving to Azure, the abp.io platform now has a more reliable and available infrastructure that is more secure than the old environment. The migration also resulted in significant improvements in connection speeds, which ultimately provides a better service to global customers and the community. + + + diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/abpio-azure.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/abpio-azure.png new file mode 100644 index 0000000000..18e2379268 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/abpio-azure.png differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/az-infra.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/az-infra.png new file mode 100644 index 0000000000..facbb4141b Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/az-infra.png differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/speed.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/speed.png new file mode 100644 index 0000000000..a9c99ea321 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/speed.png differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/terra-wire.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/terra-wire.png new file mode 100644 index 0000000000..ad003ac362 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/terra-wire.png differ diff --git a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/POST.md new file mode 100644 index 0000000000..0cecf0fb72 --- /dev/null +++ b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/POST.md @@ -0,0 +1,82 @@ +# ABP.IO Platform 7.1 Final Has Been Released! + +[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.1 versions have been released today. + +## What's New With Version 7.1? + +All the new features were already explained in detail in the [7.1 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7.1-RC-Has-Been-Published), so no need to go over them again. Check it out for more details. + +## Getting Started with 7.1 + +### Creating New Solutions + +You can create a new solution with the ABP Framework version 7.1 by either using the `abp new` command or generating the CLI command 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 it to the latest version. + +If you haven't installed it yet: + +```bash +dotnet tool install -g Volo.Abp.Cli +``` + +To update the existing CLI: + +```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 + +This version includes a very minor breaking change and it doesn't affect most of the applications. Check [the migration guide](https://docs.abp.io/en/abp/7.1/Migration-Guides/Abp-7_1) for the details. + +## Community News + +## ABP Community Talks 2023.2 + +The next ABP Community Talks will take place on March 30, at 18:00 (UTC). + +![abp-comm-talks-2023-2](abp-comm-talks-2023-2.png) + +In this episode, core ABP Framework developers will discuss the benefits of using the ABP Framework as a .NET developer instead of creating your own solution from scratch. They will answer most of the common doubts about using ABP and application frameworks in general. You will better understand how ABP makes a developer’s life easier and more enjoyable while cutting production costs. We will also have a question - answer session after the talk, as always. I think this talk will be useful for every .NET developer whether they use ABP or not. + +**[CLICK HERE to register for the event and join us](https://kommunity.com/volosoft/events/abp-community-talks-20232-why-use-abp-framework-as-a-net-developer-e3254183)**. + +## Introducing the first ABP .NET Conference! + +As the ABP team, we've executed more than 10 [online events](https://community.abp.io/events) and gained a good experience of software talks. In May, we are organizing a full-featured software conference, named **ABP Dotnet Conference 2023**! + +![abp-conf-2023](abp-conf-2023.png) + +We are still organizing the speakers, talks and schedule. There will be 12 sessions about software development and .NET. These will also include a few ABP-related talks. You can **follow https://abp.io/conference website** and buy early bird tickets from now. + +### New ABP Community Posts + +There are exciting articles contributed by the ABP community as always. I will highlight some of them here: + +* [Creating Dockerfile for ABP Applications](https://community.abp.io/posts/creating-dockerfile-for-abp-applications-caj4fkxa) by [Anto Subash](https://community.abp.io/members/antosubash) +* [IdentityUser Relationship and Extending it](https://community.abp.io/posts/identityuser-relationship-and-extending-it-xtv79mpx) by [Onur Pıçakçı](https://community.abp.io/members/onurpicakci) +* [Streamline Localization in Your ABP Project](https://community.abp.io/posts/streamline-localization-in-your-abp-project-1t12rmjc) by [Salih Özkara](https://community.abp.io/members/salih) +* [.Net Microservice template with ABP](https://community.abp.io/posts/.net-microservice-template-with-abp-53r52ryy) by [Anto Subash](https://community.abp.io/members/antosubash) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. + +## About the Next Version + +The next feature version will be 7.2. 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/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-comm-talks-2023-2.png b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-comm-talks-2023-2.png new file mode 100644 index 0000000000..730e48dc0b Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-comm-talks-2023-2.png differ diff --git a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-conf-2023.png b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-conf-2023.png new file mode 100644 index 0000000000..889ddbdc0a Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-conf-2023.png differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/POST.md b/docs/en/Blog-Posts/2023-03-24-status.abp.io/POST.md new file mode 100644 index 0000000000..42fd131f3b --- /dev/null +++ b/docs/en/Blog-Posts/2023-03-24-status.abp.io/POST.md @@ -0,0 +1,114 @@ +# Creating a Custom Status Page for abp.io with Upptime + +## Introduction +In today's digital world, providing reliable and transparent information about your platform's availability is essential to maintaining trust among your community and customers. With the growing number of abp.io users, we needed a dedicated status page [status.abp.io](https://status.abp.io/) to keep everyone informed about our platform's health. To achieve this, we utilized the open-source project [Upptime](https://upptime.js.org/) and built a custom status page on [GitHub Pages](https://pages.github.com/). In this article, we'll guide you through the process of creating our own status page and customizing it to suit our needs. + +![status-abpio](./images/status-abpio.png) + +## Why we chose Upptime +[Upptime](https://github.com/upptime/upptime) is an open-source, easy-to-use, and cost-effective solution for monitoring websites and APIs. It offers essential features, such as downtime alerts, response time monitoring, and status history. We decided to use Upptime because of its compatibility with GitHub Pages, ease of customization, comprehensive [documentation ](https://upptime.js.org/docs/) and discord notifications. + +![gh-status](./images/gh-status.png) + +#### Advantages of Upptime +* Open-source: Allows easy customization and community support. + +* GitHub Pages compatibility: Seamless integration with GitHub Pages for hosting. + +* Cost-effective: Utilizes GitHub Actions, which provides free monitoring within the GitHub Actions usage limits. + +* Comprehensive documentation: Easy-to-follow instructions for setting up and customizing the status page. + +#### Disadvantages of Upptime +* Limited monitoring capabilities: Upptime offers basic monitoring features but lacks advanced capabilities found in dedicated monitoring tools. + +* Dependence on GitHub Actions: Upptime relies on GitHub Actions, which may pose limitations for users unfamiliar with GitHub's ecosystem or those with large-scale projects. + +* No built-in alerting system: Users must rely on third-party integrations or custom solutions for notifications, requiring additional configuration. + +* Limited customization options: Upptime allows for some customization, but options are limited compared to comprehensive monitoring platforms. + +* Self-hosted limitations: As a self-hosted solution, users are responsible for maintaining and managing their own infrastructure, which may not be ideal for those who prefer a fully managed monitoring solution. + + +## How to set up the status page on GitHub Pages +To get started with our custom status page, we followed the instructions in the [Upptime documentation](https://upptime.js.org/docs/). Here's a summary of the steps we took: + +* Fork the Upptime [template repository](https://github.com/upptime/upptime) to our own GitHub account as [abpio-status](https://github.com/abpframework/abpio-status). + +* Configure the GitHub Actions workflow. We configured the GitHub Actions workflow by adding the following lines to the [`.github/workflows/uptime.yml`](https://github.com/abpframework/abpio-status/blob/master/.github/workflows/uptime.yml) + +* Add the monitored endpoints. We added the monitored endpoints (our abp.io websites) to the [.upptimerc.yml](https://github.com/abpframework/abpio-status/blob/master/.upptimerc.yml) file. This file is located in the root of the repository and contains a list of URLs that Upptime monitors. + +```yaml +sites: + - name: abp.io + url: https://abp.io/health-status + - name: community.abp.io + url: https://community.abp.io/health-status + - name: commercial.abp.io + url: https://commercial.abp.io/health-status + - name: nuget.abp.io + url: https://nuget.abp.io/health-status + - name: docs.abp.io + url: https://docs.abp.io/health-status + - name: support.abp.io + url: https://support.abp.io/health-status + - name: blog.abp.io + url: https://blog.abp.io/health-status + - name: commercial-demo.abp.io + url: https://commercial-demo.abp.io/health-status +``` +* Enable GitHub Pages. Finally, we enabled GitHub Pages for our forked repository by going to the repository's settings and selecting the gh-pages branch as the source. This made our status page accessible at [status.abp.io](https://status.abp.io/). + +## Customizing the status page + +After setting up the default Upptime status page, we focused on customizing it to align with our brand and provide a consistent experience for our community and customers. We made the following changes: + +* Updating the logo and favicon. We replaced the default logo and favicon with our own abp.io branded assets. This involved adding the new image files to the repository and updating the references in the `.upptimerc.yml` file: + +* Customizing the color scheme and typography. We customized the color scheme and typography to match our corporate identity by editing the `.upptimerc.yml` file: + +```yaml +status-website: + theme: dark + # Add your custom domain name, or remove the `cname` line if you don't have a domain + # Uncomment the `baseUrl` line if you don't have a custom domain and add your repo name there + cname: status.abp.io + # baseUrl: /abpio-status + logoUrl: https://commercial.abp.io/assets/svg/abp-logo-light.svg + favicon: https://raw.githubusercontent.com/abpframework/abpio-status/master/assets/abp-logo-without-text.svg + faviconSvg: https://raw.githubusercontent.com/abpframework/abpio-status/master/assets/abp-logo-without-text.svg +``` +## Creating GitHub Issues for Maintenance Information on status.abp.io + +To provide maintenance information for your status page, you can create GitHub issues in your repository. This allows you to inform your users about planned downtime or ongoing maintenance work. + +![issue](./images/issue.png) + +## Discord Notifications + +### Create a Discord Webhook + +To set up Discord notifications for your status.abp.io status page using [Upptime documentation](https://upptime.js.org/docs/notifications#discord), follow these steps: + +* In Discord, go to "Server Settings" > "Integrations" > "Create Webhook." +* Customize the Webhook name, choose a channel, and copy the Webhook URL. + +### Configure GitHub Actions +* In your Upptime repository, go to the "Settings" tab. +* Click on "Secrets" and then "New repository secret." +* Add secret: Name it DISCORD_WEBHOOK_URL and paste the Webhook URL as the value. +* Add environment variables NOTIFICATION_DISCORD_WEBHOOK and NOTIFICATION_DISCORD set it to true. + +Your status page will now send notifications to your Discord channel whenever there's a change in your platform's status. + +![discord](./images/discord.png) + + +## Conclusion +If your primary goal is to create a simple, cost-effective status page with basic monitoring features, Upptime is an excellent choice. Its open-source nature, seamless integration with GitHub Pages, and comprehensive documentation make it a user-friendly option. + +If you require advanced monitoring capabilities or prefer a fully managed monitoring solution, you may want to explore dedicated monitoring tools, such as Pingdom, Uptime Robot, or Datadog. These tools typically offer more robust monitoring features, built-in alerting systems, and customizable dashboards. + +Creating a custom status page for abp.io using Upptime and GitHub Pages proved to be an efficient and cost-effective solution. By following the documentation and customizing the template, we were able to provide our community and customers with a reliable source of information about our platform's availability. With this new status page [status.abp.io](https://status.abp.io/), we can continue to build trust and transparency as our platform grows and evolves. diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/discord.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/discord.png new file mode 100644 index 0000000000..b9f66bfead Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/discord.png differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/gh-status.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/gh-status.png new file mode 100644 index 0000000000..b48229b479 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/gh-status.png differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/issue.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/issue.png new file mode 100644 index 0000000000..39fda7a869 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/issue.png differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/status-abpio.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/status-abpio.png new file mode 100644 index 0000000000..528a5755d7 Binary files /dev/null and b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/status-abpio.png differ diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 9feb810730..49b5ca830b 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -164,6 +164,7 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample * `--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. +* `--with-public-website`: **Public Website** is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. See some [examples for the new command](CLI-New-Command-Samples.md) here. diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/POST.md b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/POST.md new file mode 100644 index 0000000000..63936521c6 --- /dev/null +++ b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/POST.md @@ -0,0 +1,44 @@ +## Streamline Localization in Your ABP Project + +Making localization changes to an ABP project can be a daunting task, especially if you're dealing with multiple languages and translations. During development, it's easy to overlook some changes and that leads to inconsistencies across different languages. Fortunately, I have developed a tool that can help streamline the localization process and ensure consistency across different languages. + +The tool is a console application that uses JSON files to manage localization keys and their translations. It addresses three common scenarios that can arise during localization: + + +1. When the argument count of a key changes, it can be difficult to update the translations for all languages. My tool solves this problem by scanning all JSON files in the project folder and identifying any keys that have mismatched argument counts. It then offers two options to the user: delete the mismatched translations or export them as a JSON file for manual editing. + +2. When a new key is added to the project, forgetting to add its translations to all the other languages is easy. My tool helps to avoid this issue by scanning the default language's JSON file and identifying any keys that don't have translations in other languages. It then exports these keys as a JSON file that can be used to add missing translations. + +3. When a key's name is changed, it's important to update its translations in all the other languages. My tool makes this task simple by scanning all the JSON files in the project folder and updating any translations of the old key name with the new one. + +The tool also includes an export feature that allows users to modify translations outside of the application and import them back into the JSON files. + +## How it Helps + +With my Localization Key Synchronizer tool, you can perform complex localization changes more quickly and easily than by manually sifting through files and making changes one-by-one. This can save you significant time and effort, especially if you're working with a large number of languages or translations. + +## How it Works + +When you run the Localization Key Synchronizer tool, it presents you with three options: + +1. Find Asynchronous Keys +2. Apply Changes in the Exported File +3. Replace Keys + +If you select "Find Asynchronous Keys," the tool prompts you to enter the default language path. Once you've entered the path, the tool displays all of the JSON files in the same folder as a multi-select list. After selecting one or more files, you are asked whether you want to find keys that do not match the number of arguments, missing keys, or both. If you select "Missing Keys," the tool prompts you to enter the absolute path to export the missing keys. After you've entered the path, the export process starts, and the tool closes. + +![](./images/Part1.gif) + +If you select "Apply Changes in the Exported File" at the main menu, the tool prompts you to enter the path to the exported file. After you've entered the path, the import process starts, and the tool closes. + +![](./images/Part2.gif) + +If you select "Replace Keys," the tool prompts you to enter the localization folder path, the old key, the new key, and the JSON files to apply the changes to. Once you've entered all the required information and made your selections, the tool performs the replacements and closes. + +![](./images/Part3.gif) + +## Conclusion + +If you're struggling to manage localization changes in an ABP project, give my Localization Key Synchronizer tool a try. It can help streamline your workflow and make the process much more manageable. You can find the tool on [GitHub](https://github.com/abpframework/abp/tree/dev/tools/localization-key-synchronizer). + +To use the tool, simply run the console application and follow the prompts. It's a user-friendly solution that helps to ensure localization consistency in your ABP project. Give it a try and let me know what you think! diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part1.gif b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part1.gif new file mode 100644 index 0000000000..f2e6b66e64 Binary files /dev/null and b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part1.gif differ diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part2.gif b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part2.gif new file mode 100644 index 0000000000..9bc70d361d Binary files /dev/null and b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part2.gif differ diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part3.gif b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part3.gif new file mode 100644 index 0000000000..8a8ae32d25 Binary files /dev/null and b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part3.gif differ diff --git a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/POST.md b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/POST.md new file mode 100644 index 0000000000..14571157b6 --- /dev/null +++ b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/POST.md @@ -0,0 +1,37 @@ +# Migrating from MS-SQL to Postgresql + +![sql-server-to-postgres](images/sql-server-to-postgres.jpg) + +## Introduction + +Database migration is a common practice for organizations that want to move from one database system to another. This can be for a variety of reasons, including cost, performance, and features. In this article, we will discuss the process of migrating a database from MS-SQL to PostgreSQL, the challenges that may arise during the migration, and how to overcome them. And we recently moved the main database of https://abp.io platform from MS-SQL to PostgreSQL. + +In our case, we decided to switch our database from Microsoft SQL Server (MS-SQL) to PostgreSQL because we wanted to move our on-premise platform to Azure. We’ve also found out that the cost of the license for MS-SQL on Azure was significantly higher than PostgreSQL. After conducting a cost-benefit analysis, we decided to migrate our database to PostgreSQL to save costs. + +Before migrating to Azure, we decided to switch our database from MS-SQL to PostgreSQL on-premise first. This gave us the opportunity to test and fine-tune the migration process before making the final switch to Azure. + +## Challenges + +Despite using a third-party tool(DBConvert for MySQL & PostgreSQL) for the migration, we faced three main problems when exporting data to PostgreSQL. Firstly, some tables with plain text had utf-8 encoding problems. We overcame this problem by dump-restoring these tables. + +![db-converter](images/db-converter.jpg) + + +Secondly, our database had to be case-insensitive, but PostgreSQL does not have this as a default configuration. We handled it using `citext` with the ABP migration service. + +![citext-1](images/citext-1.jpg) +![citext-2](images/citext-2.jpg) +![citext-3](images/citext-3.jpg) + + +While everything was proceeding very smooth, we faced one last problem: importing binary data, such as the content of the NuGet packages. It was hard to understand that the binaries of the NuGet packages were different. Our paid commercial NuGet packages are being stored as binary data in the database. Therefore, it was the most compelling part of this migration to transfer the NuGet packages. Fortunately, we overcame the binary error. And we decided to write a custom .NET tool to move only the binary data from MS-SQL to PostgreSQL, thanks to the ABP Core team! + + +## Conclusion + +One of the benefits of using PostgreSQL is the low license costs of the Azure platform. As the main contributors of ABP, we also use ABP Framework under the hood of our abp.io websites, we could easily switch to PostgreSQL. For those who want to switch their ABP project to PostgreSQL, check out [docs.abp.io/en/abp/latest/Entity-Framework-Core-PostgreSQL](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-PostgreSQL). We had not used any MS-SQL specific function, therefore there was no need to make any changes in the repository classes. This means that the applications that were previously using MS-SQL can seamlessly switch to PostgreSQL without any modifications. + +Thanks to the flexibility of ABP, it has [PostgreSQL package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.PostgreSql) which is 100% compatible with PostgreSQL. This helped us to make this migration very smooth and seamless. + +In conclusion, migrating a database from MS-SQL to PostgreSQL can be challenging, but it can bring significant cost savings in the long run. By testing and fine-tuning the migration process before making the final switch, we were able to overcome the challenges we’d faced during the migration process. Thanks to the flexibility of ABP, we were able to make the transition with minimal code changes. Also we didn't see any big performance differences between MS-SQL and PostgreSQL. + diff --git a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg new file mode 100644 index 0000000000..4dc01228fa Binary files /dev/null and b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg differ diff --git a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg new file mode 100644 index 0000000000..a70b0312bf Binary files /dev/null and b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg differ diff --git a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg new file mode 100644 index 0000000000..c8a95d61a5 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg differ diff --git a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg new file mode 100644 index 0000000000..9c5371d36c Binary files /dev/null and b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg differ diff --git a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg new file mode 100644 index 0000000000..645de45341 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/Post.md b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/Post.md new file mode 100644 index 0000000000..48db4e9557 --- /dev/null +++ b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/Post.md @@ -0,0 +1,307 @@ +## Introduction +In this article, I will talk about the relationships of IdentityUser in every web application that can be created with the ABP framework. When you read this article, you will learn how to extend the user entity in the applications you develop using the ABP framework with a primitive type, extending the user by associating the user with another entity (User-many-to-one-X). + + +## Creating the Solution +>For the source code of the application: https://github.com/onurpicakci/Abp-Identity-Relationship + +In this article we will use EF Core as the database provider and MVC as the user interface framework. But Angular, Blazor Server and Blazor WebAssembly also work. ABP Framework offers starter templates to get started faster. We can create a new starter template using the ABP CLI: + +```bash +abp new IdentityRelationship +``` + +After the project is created, you can run the `IdentityRelationship.DbMigrator` project to create the database and seed the initial data. You can then run the `IdentityRelationship.Web` project to see our application run. + +> The default admin username is **admin** and the password is **1q2w3E\*** + +![solution-image](images/solution-image.png) + +## Module Entity Extensions + +The module entity extension system is a high level extension system that allows you to define new properties for existing entities of the depended modules. It automatically adds properties to the entity, database, HTTP API and the user interface in a single point. + +### Extending the User Entity With a Primitive Type + +Open the `IdentityRelationshipModuleExtensionConfigurator` class inside the `Domain.Shared` project of your solution and change the `ConfigureExtraProperties` method as shown below to add an `IdentificationNumber` property to the `IdentityUser` entity of the [Identity Module](https://docs.abp.io/en/abp/latest/Modules/Identity). + +```csharp +public static void ConfigureExtraProperties() +{ + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance.Modules() + .ConfigureIdentity(identity => + { + identity.ConfigureUser(user => + { + user.AddOrUpdateProperty( //property type: string + "IdentificationNumber", //property name + property => + { + //validation rules + property.Attributes.Add(string.Empty); + property.Attributes.Add( + new StringLengthAttribute(64) { + MinimumLength = 4 + } + ); + + //...other configurations for this property + } + ); + }); + }); + }); +} +``` +> This method is called inside the IdentityRelationshipDomainSharedModule at the beginning of the application. OneTimeRunner is a utility class that guarantees to execute this code only one time per application, since multiple calls are unnecessary. + +If you want to localize, open the `IdentityRelationship.Domain.Shared` project and create a new localization in your `/Localization/IdentityRelationship/en.json` file. + +```json + "IdentificationNumber": "Identification Number" + ``` +Once you define a property, it appears in the create and update forms of the related entity: + + ![identification-number](images/identification-number.png) + + New properties also appear in the data table of the related page: + + ![users-page](images/users-page.png) + + +## Navigation Properties / Foreign Keys + +It is supported to add an extension property to an entity that is the Id of another entity (foreign key). + +### Example: Let's associate a department in the database with a user + +First, create a `Departments` folder in the `IdentityRelationship.Domain` project and add the `Department` class inside: +```csharp +using System; +using Volo.Abp.Domain.Entities; + +namespace IdentityRelationship.Departments; + +public class Department : AggregateRoot +{ + public string Name { get; set; } +} +``` + +EF Core requires that you relate the entities with your `DbContext`. The easiest way to do so is adding a DbSet property to the `IdentityRelationshipDbContext` class in the `IdentityRelationship.EntityFrameworkCore` project, as shown below: + +```csharp + public DbSet Departments { get; set; } +``` + +Then in the `IdentityRelationship.EntityFrameworkCore` project of your solution, open the `/EntityFrameworkCore/IdentityRelationshipEfCoreEntityExtensionMappings` class and update your code: + +```csharp +public static class IdentityRelationshipEfCoreEntityExtensionMappings +{ + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + IdentityRelationshipGlobalFeatureConfigurator.Configure(); + IdentityRelationshipModuleExtensionConfigurator.Configure(); + + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance + .MapEfCoreProperty( + "DepartmentId", + (entityBuilder, propertyBuilder) => { propertyBuilder.HasMaxLength(128); } + ); + + }); + } +} +``` + +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?_ga=2.21022651.140118448.1679289046-1173891759.1672473062) document to improve your understanding of what we are doing. + +We need to create a new migration to see the changes in the database. Open your `EntityFrameworkCore` project in the terminal and run the following command (this depends on the IDE you are using). + +![terminal-image](images/open-terminal.png) + +```bash +dotnet ef migrations add Create_Department_Entity +``` + +Finally, run the `IdentityRelationship.DbMigrator` project to update the database. + +When you look at your database, you can see that the `Department` table has been added and `DepartmentId` has been added to your `AbpUsers` table. + +![database-tables](images/database-tables.png) + +![users-table](images/users-table.png) + +> It's good to have some initial data in the database before running the application. This section introduces the [Data Seeding](https://docs.abp.io/en/abp/latest/Data-Seeding) system of the ABP framework. You can skip this section if you don't want to create the data seeding, but it is suggested to follow along and learn this useful ABP Framework feature. + +Create a class deriving from the `IDataSeedContributor` in the `IdentityRelationship.Domain` project by copying the following code: + +```csharp +using System; +using System.Threading.Tasks; +using IdentityRelationship.Departments; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace IdentityRelationship; + +public class IdentityRelationshipDataSeederContributor + : IDataSeedContributor, ITransientDependency +{ + private readonly IRepository _departmentRepository; + + public IdentityRelationshipDataSeederContributor(IRepository departmentRepository) + { + _departmentRepository = departmentRepository; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (await _departmentRepository.GetCountAsync() <= 0) + { + await _departmentRepository.InsertAsync( + new Department + { + Name = "Human Resources" + }, + autoSave: true + ); + + await _departmentRepository.InsertAsync( + new Department + { + Name = "Production" + }, + autoSave: true + ); + } + } +} +``` +- This code simply uses the `IRepository` (the default [repository](https://docs.abp.io/en/abp/latest/Repositories)) to insert two books to the database in case there weren't any books in it. + +Run the `IdentityRelationship.DbMigrator` application to update the database: + +![run-dbmigrator](images/run-dbmigrator.png) + +Again, open the `IdentityRelationshipModuleExtensionConfigurator` class in the Domain.Shared project and add the following code: + +```csharp + +user.AddOrUpdateProperty( + "DepartmentId", + property => + { + property.UI.Lookup.Url = "/api/app/department"; + property.UI.Lookup.DisplayPropertyName = "name"; + } +); +``` + +The `UI.Lookup.Url` option takes a URL to get the list of departments to select on the edit/create forms. This endpoint can be a typical controller, an auto API controller or any type of endpoint that returns a proper JSON response. + +To localize, open the `IdentityRelationship.Domain.Shared` project and add it to your `/Localization/IdentityRelationship/en.json` file: + +```json +"DepartmentId": "Department" +``` + +Create a `Departments` folder in the `IdentityRelationship.Application.Contracts` project of your solution and add the `DepartmentDto` class in it + +```csharp +using System; +using Volo.Abp.Application.Dtos; +namespace IdentityRelationship.Departments; + +public class DepartmentDto : EntityDto + +{ + public string Name { get; set; } +} +``` +Now let's create an `IDepartmentAppService` interface in the `Departments` folder + +```csharp +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace IdentityRelationship.Departments; + +public interface IDepartmentAppService : IApplicationService +{ + public Task> GetAsync(); +} +``` + +Time to implement the `IDepartmentAppService` interface. Create a `Departments` folder in your `IdentityRelationship.Application` project and add the `DepartmentAppService` class inside. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using IdentityRelationship.Departments; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Domain.Repositories; + +namespace IdentityRelationship.Department; + +public class DepartmentAppService : IdentityRelationshipAppService, IDepartmentAppService +{ + private readonly IRepository _departmentRepository; + + public DepartmentAppService(IRepository departmentRepository) + { + _departmentRepository = departmentRepository; + } + + public async Task> GetAsync() + { + var departments = await _departmentRepository.GetListAsync(); + return new ListResultDto( + ObjectMapper.Map, List>(departments)); + } +} +``` +`DepartmentAppService `is using the `ObjectMapper` to convert the `Department` objects to `DepartmentDto` objects. So, we need to define this mapping in the AutoMapper configuration. + +Open the `IdentityRelationshipApplicationAutoMapperProfile` class inside the `IdentityRelationship.Application` project and add the following line to the constructor: + +```csharp +using AutoMapper; +using IdentityRelationship.Departments; + +namespace IdentityRelationship; + +public class IdentityRelationshipApplicationAutoMapperProfile : Profile +{ + public IdentityRelationshipApplicationAutoMapperProfile() + { + CreateMap(); + } +} +``` + +Run your `IdentityRelationship.Web` project and add a department to one of your users. + +![user-department-image](images/user-department.png) + +And it shows the department name on the data table: + +![users-page-department-image](images/users-page-department.png) + +## Conclusion +In this article I talked about the use of the IdentityUser relationship and how to extend it. Thank you for reading the article, I hope it was useful. See you soon! + +## References +- https://docs.abp.io/en/abp/latest/Module-Entity-Extensions +- https://learn.microsoft.com/en-us/ef/core/modeling/relationships +- https://community.abp.io/posts/how-to-add-custom-properties-to-the-user-entity-rixchoha \ No newline at end of file diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/database-tables.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/database-tables.png new file mode 100644 index 0000000000..a1cef0bc26 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/database-tables.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/identification-number.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/identification-number.png new file mode 100644 index 0000000000..75fdd94768 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/identification-number.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/open-terminal.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/open-terminal.png new file mode 100644 index 0000000000..9f1069d02b Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/open-terminal.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/run-dbmigrator.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/run-dbmigrator.png new file mode 100644 index 0000000000..6e42848862 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/run-dbmigrator.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/solution-image.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/solution-image.png new file mode 100644 index 0000000000..cd54f473f4 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/solution-image.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/user-department.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/user-department.png new file mode 100644 index 0000000000..feb97234c3 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/user-department.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-page-department.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-page-department.png new file mode 100644 index 0000000000..c923c9fc4b Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-page-department.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-page.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-page.png new file mode 100644 index 0000000000..5622e11373 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-page.png differ diff --git a/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-table.png b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-table.png new file mode 100644 index 0000000000..81b2cf90dd Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/images/users-table.png differ diff --git a/docs/en/Community-Articles/2023-03-20-Dapper/POST.md b/docs/en/Community-Articles/2023-03-20-Dapper/POST.md new file mode 100644 index 0000000000..491c9a010c --- /dev/null +++ b/docs/en/Community-Articles/2023-03-20-Dapper/POST.md @@ -0,0 +1,297 @@ +# Using Dapper with the ABP Framework + +[Dapper](https://github.com/DapperLib/Dapper) is a simple and lightweight object mapper for .NET. A key feature of Dapper is its [high performance](https://github.com/DapperLib/Dapper#performance) compared to other ORMs. In this article, I will show how to use it in your ABP projects. But, we'll see when to use it first. + +### Source Code + +You can find the [full source code of the demo application here](https://github.com/abpframework/abp-samples/tree/master/Dapper). + +## When to Use Dapper? + +In the ABP Framework, we suggest to use Dapper in combination with Entity Framework Core (EF Core) for the following reasons: + +* EF Core is much easier to use (you don't need to manually write SQL queries and work with low level database objects). +* EF Core abstracts different DBMS dialects, so it will be easier to change your DBMS later. +* The EF Core's change tracking system automatically updates the changes in the database. +* EF Core is better compatible with Object Oriented Programming (OOP) practices and is more type safe to work with. So, the EF Core code is more understandable and maintainable. + +In most of your use cases, you typically work with one or a few entities and a maintainable codebase can be chosen instead of a slight performance difference. However, there may be certain places in your application where it matters: + +* You may work with a lot of entities, so you'd like to query faster (Indeed, EF Core's [AsNoTracking()](https://learn.microsoft.com/en-us/ef/core/querying/tracking) extension can help in most cases). +* You may be performing too many database operations in a single request. +* EF Core may not create an optimized SQL query and you may want to manually write it for better performance. + +For such cases, Dapper can be a good choice. You can easily write SQL queries and bind the result to your objects. + +## Creating a new ABP Solution + +To demonstrate the useage of Dapper, I've created an ABP solution. You can find the [full source code of the demo application here](https://github.com/abpframework/abp-samples/tree/master/Dapper). If you want to create the same solution from scratch, follow the steps below: + +Install the ABP CLI if you haven't installed it before: + +````bash +dotnet tool install -g Volo.Abp.Cli +```` + +Create a new solution with the ABP Framework's non-layered startup template with MVC UI and EF Core database: + +````bash +abp new DapperDemo -t app-nolayers +```` + +> The startup template and UI selection don't matter for this article. I selected these options to keep the demo solution simple. + +After creating the solution, run the following command to migrate the database (run the command in the folder of the `.csproj` file: + +````csharp +dotnet run --migrate-database +```` + +> If you've created a layered solution, then run the `DbMigrator` application inside the solution. If you have trouble by creating the solution, please refer to the [Quick Start](https://docs.abp.io/en/abp/latest/Tutorials/Todo/Single-Layer/Index) document. + +## Setting Up the Entity Framework Core Part + +We will use EF Core with Dapper, so we need to configure EF Core first. I will use the following `Book` entity as an example: + +````csharp +public class Book : AuditedAggregateRoot +{ + public string Name { get; set; } + public DateTime PublishDate { get; set; } + public float Price { get; set; } +} +```` + +If you are using a layered solution, entities are located in the `Domain` project. For my demo solution, I just placed it in the `Entities` folder of the single-layer project: + +![book-class-in-rider](book-class-in-rider.png) + +Once I created the `Book` entity, I should add it to my `DbContext` class: + +````csharp +public class DapperDemoDbContext : AbpDbContext +{ + // 1: ADD A DBSET PROPERTY + public DbSet Books { get; set; } + + public DapperDemoDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + //...other code parts + + // 2: MAP YOUR ENTITY TO A DATABASE TABLE + builder.Entity(b => + { + b.ToTable("Books"); + b.Property(x => x.Name).IsRequired().HasMaxLength(128); + }); + } +} + +```` + +Now, we can add a new database migration: + +````bash +dotnet ef migrations add Added_Book +```` + +And apply the changes to the database: + +````bash +dotnet ef database update +```` + +At this point, you should be able to see the Books table if you check your database: + +![book-database-table](book-database-table.png) + +> As you see, the `Books` table contains more fields than the `Book` entity's property count. Other properties are inherited from the `AuditedAggregateRoot` table. You could inherit from the `BasicAggregateRoot` class if you don't want these properties. + +## Seeding the Database + +ABP's [data seeding](https://docs.abp.io/en/abp/latest/Data-Seeding) system is a great way to add some test data to the database. The following class inserts two books to the `Books` table when I migrate the database: + +````csharp +public class DapperDemoDataSeederContributor : IDataSeedContributor, ITransientDependency +{ + private readonly IRepository _bookRepository; + + public DapperDemoDataSeederContributor(IRepository bookRepository) + { + _bookRepository = bookRepository; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (await _bookRepository.GetCountAsync() > 0) + { + return; + } + + await _bookRepository.InsertAsync( + new Book + { + Name = "1984", + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + } + ); + + await _bookRepository.InsertAsync( + new Book + { + Name = "The Hitchhiker's Guide to the Galaxy", + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + } + ); + } +} +```` + +After creating the `DapperDemoDataSeederContributor` class, I can re-run the following command: + +````bash +dotnet run --migrate-database +```` + +Now, I can see the records in the database: + +![book-table-data](book-table-data.png) + +Now, everything is ready to try querying from the `Books` table with Dapper. + +## Using Dapper Without the Integration Package + +ABP provides an integration package for Dapper. However, I first want to demonstrate using Dapper without the integration package. + +### Installing the Dapper Package + +First, install the [Dapper](https://www.nuget.org/packages/Dapper) package to your project. You can use a command-line terminal, locate the root path of your project (`.csproj` file that you want to install it in) and run the following command: + +````bash +dotnet add package Dapper +```` + +> If your application is layered, then we suggest to add the `Dapper` package to your `EntityFrameworkCore` integration project in your solution. + +### Executing a Dapper Query + +I will query from the `Books` table, but I don't want to use the `Book` entity to map the result (because I don't need all the properties). So, I am creating a new class for the query result: + +````csharp +public class BookDataView +{ + public Guid Id { get; set; } + public string Name { get; set; } + public float Price { get; set; } +} +```` + +Now, we can use Dapper's `QueryAsync` extension method as shown below: + +````csharp +public class DemoService : ITransientDependency +{ + private readonly IRepository _bookRepository; + + public DemoService(IRepository bookRepository) + { + _bookRepository = bookRepository; + } + + [UnitOfWork] + public virtual async Task> GetListAsync() + { + var database = (await _bookRepository.GetDbContextAsync()).Database; + var dbConnection = database.GetDbConnection(); + var dbTransaction = database.CurrentTransaction?.GetDbTransaction(); + + var queryResult = await dbConnection.QueryAsync( + "SELECT Id, Name, Price FROM Books", + transaction: dbTransaction + ); + + return queryResult.ToList(); + } +} +```` + +Let's examine this class: + +* I've injected the ABP's standard [generic repository](https://docs.abp.io/en/abp/latest/Repositories) service into the `DemoService` constructor. +* `_bookRepository.GetDbContextAsync()` returns the underlying `DbContext` object of EF Core. We need to have the [Volo.Abp.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore) package reference to be able to access that method. If you have created a single-layer solution, the reference will already have existed. If you have created a layered solution you may need to manually add this package to the project that contains the `DemoService` class. Because the layered solution isolates the EF Core dependency from the rest of the solution. +* Dapper needs a `DbConnection` and a `DbTransaction` object (as optional) to execute a query. We are getting them over the `database` object obtained from the `DbContext`. We suggest to always pass the current `DbTransaction` object while working with Dapper. Because, if there is a database transaction on the same database connection that you execute queries on, and you don't pass the transaction object, you'll get an exception. +* Finally, we can use Dapper's `QueryAsync` extension method to execute the database query. +* Notice that the `GetListAsync` method is made as `virtual` and marked with the `UnitOfWork` attribute to enable the [Unit Of Work](https://docs.abp.io/en/abp/latest/Unit-Of-Work) for that method. It ensures the database connection is available in the body of the `GetListAsync` method. + +That's all. You can execute any Dapper operation using the `DbConnection` and `DbTransaction` objects obtained from the `_bookRepository` object. Please refer to Dapper's documentation for other operations. + +> We've obtained the `DbContext` object from a repository. However, a repository is not required to obtain a `DbContext`. Instead, you could inject the `IDbContextProvider` service (`IDbContextProvider` for this demo) and call its `GetDbContextAsync` method. + +## Using the Volo.Abp.Dapper Package + +In the previous section, you saw that you don't need an ABP integration package to be able to use Dapper in your ABP applications. However, there is an integration package here: [Volo.Abp.Dapper](https://www.nuget.org/packages/Volo.Abp.Dapper). In fact, that package doesn't contain much services. It just provides a convenient base class to create Dapper based repository classes. + +### Installing the Volo.Abp.Dapper Package + +You can use the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI#add-package) to easily install ABP packages to your projects. Execute the following command in the folder of the `.csproj` file that you want to install the package on: + +````bash +abp add-package Volo.Abp.Dapper +```` + +> You can check the [ABP Dapper document](https://docs.abp.io/en/abp/latest/Dapper) for alternative installation options. + +### Creating a Repository Class + +In the `DemoService` example, we used database objects out of a [repository](https://docs.abp.io/en/abp/latest/Repositories) class. If you want to implement layering to your solution and abstracting database operations, it can be better to create a repository class to execute the Dapper operations. + +Here's a repository class that executes the same database query: + +````csharp +public class BookRepository : DapperRepository, ITransientDependency +{ + public BookRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public virtual async Task> GetListAsync() + { + var connection = await GetDbConnectionAsync(); + var queryResult = await connection.QueryAsync( + "SELECT Id, Name, Price FROM Books", + transaction: await GetDbTransactionAsync() + ); + return queryResult.ToList(); + } +} +```` + +Let's examine this class: + +* It inherits from the `DapperRepository` class, which provides useful methods and properties for database operations. It also implements the `IUnitOfWorkEnabled` interface, so ABP makes the database connection (and transaction if requested) available in the method body by implementing dynamic proxies (a.k.a. interception). +* The `GetListAsync` method's been made `virtual`. That's needed to make the interception process working. It wouldn't be needed if we introduce `IBookRepository` to that class and always use it by injecting the `BookRepository` class (in this case, it will use interface proxying - however, this is too much details for the purpose of this article). +* We've used the `GetDbConnectionAsync` and `GetDbTransactionAsync` methods to obtain the current database connection and transaction (that is managed by ABP's [unit of work](https://docs.abp.io/en/abp/latest/Unit-Of-Work) system). + +You can then inject the `BookRepository` class when you want to get a list of `BookDataView` whenever it is needed. In the demo project, I used it inside the `IndexModel.cshtml.cs` to show a list of books on the page: + +![list-of-books](list-of-books.png) + +## Conclusion + +In this article, I've explained ABP's Dapper integration and demonstrated how you can execute Dapper operations in your applications. I suggest to use Dapper when it is really needed and adds any significant value (generally a performance gain) to your application. Otherwise, EF Core is much more convenient for most of the database operations and you will have a more maintainable codebase using EF Core. + +## Source Code + +You can find the [full source code of the demo application here](https://github.com/abpframework/abp-samples/tree/master/Dapper). + +## See Also + +* [ABP Dapper documentation](https://docs.abp.io/en/abp/latest/Dapper) diff --git a/docs/en/Community-Articles/2023-03-20-Dapper/book-class-in-rider.png b/docs/en/Community-Articles/2023-03-20-Dapper/book-class-in-rider.png new file mode 100644 index 0000000000..db1e5791b4 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20-Dapper/book-class-in-rider.png differ diff --git a/docs/en/Community-Articles/2023-03-20-Dapper/book-database-table.png b/docs/en/Community-Articles/2023-03-20-Dapper/book-database-table.png new file mode 100644 index 0000000000..d1584118d9 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20-Dapper/book-database-table.png differ diff --git a/docs/en/Community-Articles/2023-03-20-Dapper/book-table-data.png b/docs/en/Community-Articles/2023-03-20-Dapper/book-table-data.png new file mode 100644 index 0000000000..6b9ea69b59 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20-Dapper/book-table-data.png differ diff --git a/docs/en/Community-Articles/2023-03-20-Dapper/list-of-books.png b/docs/en/Community-Articles/2023-03-20-Dapper/list-of-books.png new file mode 100644 index 0000000000..23b190afa6 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-20-Dapper/list-of-books.png differ diff --git a/docs/en/Community-Articles/2023-03-27-What-is-new-in-NET8/cover.jpg b/docs/en/Community-Articles/2023-03-27-What-is-new-in-NET8/cover.jpg new file mode 100644 index 0000000000..aa0f3694d4 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-27-What-is-new-in-NET8/cover.jpg differ diff --git a/docs/en/Community-Articles/2023-03-27-What-is-new-in-NET8/post.md b/docs/en/Community-Articles/2023-03-27-What-is-new-in-NET8/post.md new file mode 100644 index 0000000000..5ae3ff2ce0 --- /dev/null +++ b/docs/en/Community-Articles/2023-03-27-What-is-new-in-NET8/post.md @@ -0,0 +1,226 @@ +# What’s New in .NET 8 🧐 ? Discover ALL .NET 8 Features⚡🚀 + +In this post, I'll briefly mention the new features of .NET 8 and the changes. + +## `dotnet publish` and `dotnet pack` Release Mode 🏭 + +With this new version, `dotnet publish` and `dotnet pack` commands will build and pack with the `Release` mode. Before it was producing in `Debug` mode. To be able to produce in Debug mode, you need to set this parameter `-p:PublishRelease` as false. + +```bash +dotnet publish -> /app/bin/Release/net8.0/app.dll +dotnet publish -p:PublishRelease=false -> /app/bin/Debug/net8.0/app.dll +``` + +--- + + + +## System.Text.Json Serialization 🧱 + +[System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) replaced Newtonsoft.Json in the recent versions. We are also using `System.Text.Json` in the [ABP Framework](https://abp.io) now. There are several enhancements to object serialization and deserialization. + +The latest version of the [source generator](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation) now offers improved performance and reliability for Native AOT apps when used with ASP.NET Core. It also allows serializing types with [`required`](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/required-properties) and [`init`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init) properties already supported in reflection-based serialization. Additionally, there is now an option to customize the handling of members that are not present in the JSON payload, see https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/missing-members. Support for serializing properties from interface hierarchies. The [JsonNamingPolicy](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonnamingpolicy?view=net-8.0&preserve-view=true#properties) feature has been expanded to include new naming policies for `snake_case` and `kebab-case` property name conversions. Finally, [JsonSerializerOptions.MakeReadOnly](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.makereadonly#system-text-json-jsonserializeroptions-makereadonly) method allows for explicit control over when a `JsonSerializerOptions` instance is frozen, and you can check its status using the [IsReadOnly](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.isreadonly#system-text-json-jsonserializeroptions-isreadonly) property. + +--- + + + +## Randomness + +AI programming is very popular these days. And the need to produce more random content arose. + +### GetItems() 🧮 + +Two new methods: [Random.GetItems](https://learn.microsoft.com/en-us/dotnet/api/system.random.getitems) and [RandomNumberGenerator.GetItems](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator.getitems) have been introduced that enable developers to randomly select a set number of items from a given input set. The example below demonstrates the usage of the `System.Random.GetItems()` method using an instance obtained from the `Random.Shared` property to randomly insert 31 items into an array. + +```csharp +private static ReadOnlySpan countries = new[] +{ + new CountryPhoneCode("Turkey", "90"), + new CountryPhoneCode("China", "86"), + new CountryPhoneCode("Germany", "49"), + new CountryPhoneCode("Finland", "358"), + new CountryPhoneCode("Spain", "34") +}; + +var randomValues = Random.Shared.GetItems(countries, 2); +foreach (var x in randomValues) +{ + Console.WriteLine(x.Name + " -> " + x.CountryPhoneCode); +} + +/************** +- Output - +Germany -> 49 +Finland -> 358 +**************/ +``` + +--- + + + +### Shuffle() 🔀 + +If you need to randomize the order of a span in your application, you can take advantage of two new methods: [Random.Shuffle](https://learn.microsoft.com/en-us/dotnet/api/system.random.shuffle) and [RandomNumberGenerator.Shuffle](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator.shuffle?view=net-8.0). These methods are particularly handy when you want to minimize the impact of training bias in machine learning by varying the order in which training and testing data are presented. Using these methods, you can ensure that the first thing in your dataset is only sometimes used for training, and the last is only sometimes reserved for testing. + +```csharp +var trainingData = GetData(); +Random.Shared.Shuffle(trainingData); + +IDataView source = mlContext.Data.LoadFromEnumerable(trainingData); + +DataOperationsCatalog.TrainTestData splittedData = mlContext.Data.TrainTestSplit(source); +model = chain.Fit(splittedData.TrainSet); + +IDataView resultPredictions = model.Transform(split.TestSet); +``` + +--- + + + +## Performance Improvements 🚀 + +In .NET 8, various new types have been introduced to enhance application performance. + +- The [System.Collections.Frozen](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen) namespace in .NET 8 includes the [FrozenDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozendictionary-2) and [FrozenSet](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozenset-1) collection types. These types are designed to prevent changes to keys and values once a collection is created, resulting in faster read operations such as `TryGetValue()`. They are particularly useful for collections populated on first use and then persisted for a long-lived service. + + ```csharp + private static readonly FrozenDictionary frozenData = LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true); + ////// + if (frozenData.TryGetValue(key, out bool setting) && setting) + { + Process(); + } + ``` + +- [Buffers.IndexOfAnyValues](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.indexofanyvalues-1) is a new type in .NET 8, designed to be passed to methods that search for the first occurrence of any value in a passed collection. The new overloads of methods like [String.IndexOfAny](https://learn.microsoft.com/en-us/dotnet/api/system.string.indexofany?view=net-8.0#system-string-indexofany(system-char())) and [MemoryExtensions.IndexOfAny](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.indexofany) accept an instance of the new type. When you create an instance of [Buffers.IndexOfAnyValues](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.indexofanyvalues-1), all the necessary data for optimizing subsequent searches is derived at that time. + +- [Text.CompositeFormat](https://learn.microsoft.com/en-us/dotnet/api/system.text.compositeformat) is a new type in .NET 8 useful for optimizing format strings that aren't known at compile time (such as format strings loaded from a resource file). While some extra time is spent upfront to perform tasks like parsing the string, it saves the work from being done each time the format string is used. + + ```csharp + private static readonly CompositeFormat range = CompositeFormat.Parse(Load()); + ////////// + static string GetMessage(int min, int max) => + string.Format(CultureInfo.InvariantCulture, range, min, max); + ``` + +- In .NET 8, two new types are introduced to implement the fast [XxHash3](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash3) and [XxHash128](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash128) hash algorithms. + +--- + + + +## Improvements in System.Numerics and System.Runtime.Intrinsics 🔥 + +There are several enhancements made to the [System.Numerics](https://learn.microsoft.com/en-us/dotnet/api/system.numerics) and [System.Runtime.Intrinsics](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics) namespaces. These improvements include better hardware acceleration for [Vector256](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256-1), [Matrix3x2](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.matrix3x2), and [Matrix4x4](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.matrix4x4) in .NET 8. + +[Vector256](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256-1) was redesigned to utilize `2x Vector128` operations internally to achieve partial acceleration of certain functions on `Arm64` processors where `Vector128.IsHardwareAccelerated == true` but `Vector256.IsHardwareAccelerated == false`. The introduction of [Vector512](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector512-1) is also included in .NET 8. + +Additionally, the `ConstExpected` attribute has been added to hardware intrinsic to alert users when a non-constant value might cause unexpected performance issues. + +Lastly, the [Lerp(TSelf, TSelf, TSelf)](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.ifloatingpointieee754-1.lerp#system-numerics-ifloatingpointieee754-1-lerp(-0-0-0)) API has been added to [IFloatingPointIeee754](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.ifloatingpointieee754-1), enabling the efficient and accurate linear interpolation of two values in `float`([Single](https://learn.microsoft.com/en-us/dotnet/api/system.single)), `double` ([Double](https://learn.microsoft.com/en-us/dotnet/api/system.double)), and [Half](https://learn.microsoft.com/en-us/dotnet/api/system.half). + +--- + + + +## New Data Validation Attributes 🛡️ + +The [DataAnnotations](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations) namespace, aimed specifically for validation in cloud-native services. The existing `DataAnnotations` validators are primarily used for validating user data, like form fields. However, the new attributes are meant to validate data, not entered by users, like [configuration options](https://learn.microsoft.com/en-us/dotnet/core/extensions/options#options-validation). Apart from the new attributes, the [RangeAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute) and [RequiredAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.requiredattribute) types also received new properties. + +- [RequiredAttribute.DisallowAllDefaultValues](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.requiredattribute.disallowalldefaultvalues#system-componentmodel-dataannotations-requiredattribute-disallowalldefaultvalues): The attribute forces that structs for inequality with their default values. +- [RangeAttribute.MinimumIsExclusive](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute.minimumisexclusive#system-componentmodel-dataannotations-rangeattribute-minimumisexclusive) & [RangeAttribute.MaximumIsExclusive](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute.maximumisexclusive#system-componentmodel-dataannotations-rangeattribute-maximumisexclusive): Specifies whether the allowable range includes its boundaries or not. +- [DataAnnotations.LengthAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.lengthattribute): Specifies the lower and upper limits for strings or collections using the `Length` attribute. For instance, the `[Length(5, 100)]` attribute specifies that a collection must have at least 5 elements and at most 100 elements. +- [DataAnnotations.Base64StringAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.base64stringattribute): Validates a valid `Base64` format. +- [DataAnnotations.AllowedValuesAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.allowedvaluesattribute) & [DataAnnotations.DeniedValuesAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.deniedvaluesattribute): Specifies accepted allow lists or not allowed deny lists. For instance: `[AllowedValues("red", "green", "blue")]` or `[DeniedValues("yellow", "purple")]`. + + + +--- + + + +## Function Pointers Introspection Support ↩️ + +[Function pointers](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#function-pointers) were released with .NET 5. There was no support for reflection at that time. As a result, using `typeof` or reflection on a function pointer, such as `typeof(delegate*())` or `FieldInfo.FieldType`, respectively, would return an [IntPtr](https://learn.microsoft.com/en-us/dotnet/api/system.intptr). However, in .NET 8, a [System.Type](https://learn.microsoft.com/en-us/dotnet/api/system.type) object is returned instead, providing access to function pointer metadata, such as calling conventions, return type, and parameters. This functionality is implemented only in the `CoreCLR` runtime and [MetadataLoadContext](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.metadataloadcontext). + +--- + + + +## Native AOT 🏭 + +The [publishing as native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) was initially introduced in .NET 7, the option to publish an application as native AOT enables the creation of a self-contained version of the app that does not require a separate runtime, bundling everything into a single file. + +In .NET 8, the support for native AOT now encompasses the `x64` and `Arm64` architectures on macOS. Moreover, native AOT applications on Linux are now up to 50% smaller in size. Here's the table, illustrates the size of a minimal app published with native AOT, containing the entire .NET runtime: + + + +* **Linux x64** (with `-p:StripSymbols=true`) + * .NET 7 ➡ 3.76MB + * .NET 8 ➡ 1.84 MB +* **Windows x64** + * .NET 7 ➡ 2.85 MB + * .NET 8 ➡ 1.77 MB + +--- + + + +## Code Generation Improvements 📃 + +.NET 8 includes enhancements to code generation and just-in-time (*JIT*) compilation: + +- JIT throughput improvements +- Arm64 performance improvements +- Profile-guided optimization (PGO) improvements +- Support for AVX-512 ISA extensions +- SIMD improvements +- Cloud-native improvements +- Loop and general optimizations + +--- + + + +## .NET 8 DevOps Improvements 📦 + +### NET Container Image Changes + +There are some changes with .NET 8 on image containers. First, [Debian 12](https://wiki.debian.org/DebianBookworm) is the **default Linux distribution** in the container images. + +Secondly, the images include a `non-root` user to make the images `non-root` capable. To run as `non-root`, add the line `USER app` at the end of your `Dockerfile`. + +Besides, the **default port has also changed** from `80` to `8080` and a **new environment variable** `ASPNETCORE_HTTP_PORTS` is available to change ports easily. + +Also, the format for the `ASPNETCORE_HTTP_PORTS` variable is easier compared to the format required by `ASPNETCORE_URLS`, and it accepts a list of ports. If you change the port back to `80` using one of these variables, it won’t be possible to run as `non-root`. + +Finally, .NET 8 is now supported on **Chiseled Ubuntu** images, available at the [Ubuntu/DotNet-deps Docker Hub](Ubuntu/DotNet-deps Docker Hub). Chiseled images are designed to have a smaller attack surface as they are stripped down to be ultra-compact, and do not include a package manager or shell. Chiseled images are non-root, making them ideal for developers looking for the benefits of appliance-style computing. These images are regularly published to the [.NET nightly artifact registry](https://mcr.microsoft.com/product/dotnet/nightly/aspnet/tags) for easy access. + +### Building Your .NET on Linux + +Previously, building .NET from source in earlier versions required creating a `source tarball` from the corresponding release commit in the [dotnet/installer repository](dotnet/installer repository). However, in .NET 8, this step is no longer necessary as the [dotnet/dotnet repository](https://github.com/dotnet/dotnet) allows building .NET directly on Linux using [dotnet/source-build](https://github.com/dotnet/source-build) to create runtimes, tools, and SDKs. Red Hat and Canonical also use this build for .NET. Building in a container is the easiest approach for most people since the `dotnet-buildtools/prereqs` container images have all the necessary dependencies. [The build instructions]() provide more information. + +### Minimum support baselines for Linux + +The support requirements for Linux have been updated for .NET 8, with changes to the minimum support baselines: + +1. All architectures will target Ubuntu 16.04 for building .NET, which is important for setting the minimum required version of `glibc` for .NET 8. Versions of Ubuntu earlier than 16.04, such as 14.04, will not even allow .NET 8 to start. +2. **Red Hat Enterprise Linux 7 is no longer supported** with .NET 8. Only supporting RHEL 8 and later. + +For further details, please refer to the [support for Red Hat Enterprise Linux Family](https://github.com/dotnet/core/blob/main/linux-support.md#red-hat-enterprise-linux-family-support) page. + + + +--- + +Become a pioneer and try the new features of .NET 8 now. +Adapt it to your project or start a new .NET 8 project. +[Claim your copy of .NET 8](https://dotnet.microsoft.com/next) today 🏎️ ! + +〰️〰️〰️ + +Happy Coding ⌨️ + diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/POST.md b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/POST.md new file mode 100644 index 0000000000..4904da48cf --- /dev/null +++ b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/POST.md @@ -0,0 +1,372 @@ +# Convert Create/Edit Modals to Page +In this document we will explain how to convert BookStore's Books create & edit modals to regular blazor pages. + +## Before +![bookstore-crud-before](images/old.gif) + +## After + +![bookstore-crud-after](images/new.gif) + + +# Books.razor Page +Books.razor page is the main page of the books management. Create & Update operations are done in this page. So we'll remove create & update operations from this page and move a separate blazor component for each operation. Each component will be a page. + +- Remove both Create & Update modals. + + ![remove-all-modals](images/books-remove-modals.png) + +- Replace **NewBook** button with a link to **CreateBook** page. + + ```html + + ``` + +- Inject `NavigationManager` to `Books.razor` page. + ```csharp + @inject NavigationManager NavigationManager + ``` + +- Replace **Edit** button with a link to **UpdateBook** page. + + ```html + + ``` + + ```csharp + private void NavigateToEdit(Guid id) + { + NavigationManager.NavigateTo($"books/{id}/edit"); + } + ``` + +- Remove all methods in the `Books.razor` page except constructor. And add `GoToEditPage` as below: + + ```csharp + protected void GoToEditPage(BookDto book) + { + NavigationManager.NavigateTo($"books/{book.Id}"); + } + ``` + + ![bookstore-remove-methods](images/books-remove-methods.png) + +- Change Edit button to a link in the table. + + ```html + + ``` + + +# CreateBooks Page +Create new `CreateBook.razor` and `CreateBook.razor.cs` files in your project. + +- `CreateBook.razor` + +```html +@page "/books/new" +@attribute [Authorize(BookStorePermissions.Books.Create)] +@inherits BookStoreComponentBase + +@using Acme.BookStore.Books; +@using Acme.BookStore.Localization; +@using Acme.BookStore.Permissions; +@using Microsoft.Extensions.Localization; +@using Volo.Abp.AspNetCore.Components.Web; + +@inject IStringLocalizer L +@inject AbpBlazorMessageLocalizerHelper LH +@inject IBookAppService AppService +@inject NavigationManager NavigationManager + + + + + @L["NewBook"] + + + + + + + @L["Author"] + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + + +``` + +- `CreateBook.razor.cs` + +```csharp +using Acme.BookStore.Books; +using Blazorise; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; + +namespace Acme.BookStore.Blazor.Pages; + +public partial class CreateBook +{ + protected Validations CreateValidationsRef; + protected CreateUpdateBookDto NewEntity = new(); + IReadOnlyList authorList = Array.Empty(); + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + authorList = (await AppService.GetAuthorLookupAsync()).Items; + + if (!authorList.Any()) + { + throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); + } + + NewEntity.AuthorId = authorList.First().Id; + + if (CreateValidationsRef != null) + { + await CreateValidationsRef.ClearAll(); + } + } + + protected virtual async Task CreateEntityAsync() + { + try + { + var validate = true; + if (CreateValidationsRef != null) + { + validate = await CreateValidationsRef.ValidateAll(); + } + if (validate) + { + await AppService.CreateAsync(NewEntity); + NavigationManager.NavigateTo("books"); + } + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } +} +``` + +# EditBooks Page +Create new `EditBook.razor` and `EditBook.razor.cs` files in your project. + +- `EditBook.razor` + +```html +@page "/books/{Id}" +@attribute [Authorize(BookStorePermissions.Books.Edit)] +@inherits BookStoreComponentBase +@using Acme.BookStore.Books; +@using Acme.BookStore.Localization; +@using Acme.BookStore.Permissions; +@using Microsoft.Extensions.Localization; +@using Volo.Abp.AspNetCore.Components.Web; + +@inject IStringLocalizer L +@inject AbpBlazorMessageLocalizerHelper LH +@inject IBookAppService AppService +@inject NavigationManager NavigationManager + + + + + @EditingEntity.Name + + + + + + + @L["Author"] + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + + +``` + +- `EditBook.razor.cs` + +```csharp +using Acme.BookStore.Books; +using Blazorise; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; + +namespace Acme.BookStore.Blazor.Pages; + +public partial class EditBook +{ + protected CreateUpdateBookDto EditingEntity = new(); + protected Validations EditValidationsRef; + IReadOnlyList authorList = Array.Empty(); + + [Parameter] + public string Id { get; set; } + + public Guid EditingEntityId { get; set; } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + // Blazor can't parse Guid as route constraint currently. + // See https://github.com/dotnet/aspnetcore/issues/19008 + EditingEntityId = Guid.Parse(Id); + + authorList = (await AppService.GetAuthorLookupAsync()).Items; + + if (!authorList.Any()) + { + throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); + } + + var entityDto = await AppService.GetAsync(EditingEntityId); + + EditingEntity = ObjectMapper.Map(entityDto); + + if (EditValidationsRef != null) + { + await EditValidationsRef.ClearAll(); + } + } + + protected virtual async Task UpdateEntityAsync() + { + try + { + var validate = true; + if (EditValidationsRef != null) + { + validate = await EditValidationsRef.ValidateAll(); + } + if (validate) + { + await AppService.UpdateAsync(EditingEntityId, EditingEntity); + + NavigationManager.NavigateTo("books"); + } + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } +} +``` + +You can check the following commit for details: +https://github.com/abpframework/abp-samples/commit/aae61ad6d66ebf6191dd4dcfb4e23d30bd680a4e \ No newline at end of file diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-methods.png b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-methods.png new file mode 100644 index 0000000000..a2df67fee2 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-methods.png differ diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png new file mode 100644 index 0000000000..0e9fd6e8d4 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png differ diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/new.gif b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/new.gif new file mode 100644 index 0000000000..42784eb281 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/new.gif differ diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/old.gif b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/old.gif new file mode 100644 index 0000000000..80a97a80f2 Binary files /dev/null and b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/old.gif differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/abp-framework.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/abp-framework.png new file mode 100644 index 0000000000..b1157793bf Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/abp-framework.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/abp-suite.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/abp-suite.png new file mode 100644 index 0000000000..e4ad6d6014 Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/abp-suite.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/architecture-layers.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/architecture-layers.png new file mode 100644 index 0000000000..081376668b Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/architecture-layers.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/conclusion.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/conclusion.png new file mode 100644 index 0000000000..cecdd739b4 Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/conclusion.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/cover.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/cover.png new file mode 100644 index 0000000000..4a0aaf8d4c Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/cover.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/csharp-microservice-framework.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/csharp-microservice-framework.png new file mode 100644 index 0000000000..2550a9209d Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/csharp-microservice-framework.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/ddd.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/ddd.png new file mode 100644 index 0000000000..45f6261a57 Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/ddd.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/essential-features.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/essential-features.png new file mode 100644 index 0000000000..e709f00327 Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/essential-features.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/essentials.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/essentials.png new file mode 100644 index 0000000000..dee5928bbd Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/essentials.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/modular.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/modular.png new file mode 100644 index 0000000000..96943ef81f Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/modular.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/pre-built-modules.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/pre-built-modules.png new file mode 100644 index 0000000000..0d5f5a7e9a Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/pre-built-modules.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/the-abp-platform.png b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/the-abp-platform.png new file mode 100644 index 0000000000..bae6f08238 Binary files /dev/null and b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/images/the-abp-platform.png differ diff --git a/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/post.md b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/post.md new file mode 100644 index 0000000000..b844fb5540 --- /dev/null +++ b/docs/en/Community-Articles/2023-04-03-What-is-ABP-Framework/post.md @@ -0,0 +1,118 @@ +# ABP Framework: An Open Source Web Application Development Framework + + + +## What is ABP Framework? + +![ABP Framework](images/abp-framework.png) + +ABP Framework is an open-source web application development framework that provides developers with a set of tools to build modern, scalable, and maintainable web applications. ABP Framework is also a C# web framework that is based on the ASP.NET web framework. It is one of the [most popular repository](https://github.com/abpframework/abp) for open source application framework. + +ABP Framework is a modular and extensible framework that uses the clean architecture principles and is built on top of the latest .NET technologies. The framework comes with a set of pre-built modules, including user management, role management, permission management, content management system (CMS) modules, which makes it easier for developers to create line of business applications. + + + +## Clean Architecture + +When you want to start a new scratch project, you first google for Dotnet Framework Architecture. There are some boilerplate dotnet startup templates but these are only an orchestration of some popular tools. ABP is not a template but it's a full stack open source application development framework. + +When you say "Clean Architecture ASP.NET Core", the first web development framework that comes to mind is undoubtedly the ABP Framework. It is built using clean architecture principles, which help developers build scalable and maintainable applications. Clean architecture separates the application into distinct layers, each with a clear responsibility. The dotnet architecture layers include the presentation layer, application layer, domain layer, and infrastructure layer. Each layer has a clear responsibility, which helps in separating concerns and keeping the code organized. This makes ABP Framework one of the best asp net frameworks. + +![Clean architecture - ABP Layers](images/architecture-layers.png) + +## C# Web Framework for Web Development + +ABP Framework is built using C#, which is a modern programming language that is widely used in the development of web applications. C# provides developers with a set of features that make it easy to write clean and maintainable code. ABP Framework is a web framework that is designed to work with C# and provides developers with a set of tools that makes it easy to build modern web applications. If you are looking for an ASP NET Core shared framework download, then go to https://abp.io/get-started and create your project. + +![ABP Framework Essentials](images/essentials.png) + +## Yet another ASP.NET Web Framework + +There are a few full stack AspNet Core frameworks around. Many of them are one developer projects which can be risky for you to start a long running project. ABP Framework is built on top of the latest ASP NET Core Framework, which provides developers with a set of features that makes it easy to build modern web applications. And most important part is, ABP is backed with a large group of developers and it has almost 10K stars on GitHub. ASP.NET provides developers with a set of tools that makes it easy to build web applications using a model-view-controller (MVC) architecture. + +![ABP.io Platform](images/the-abp-platform.png) + + + +## Implementing Domain Driven Design with C# + +ABP Framework provides developers with a set of tools that make it easy to implement domain-driven design principles. The framework comes with a set of pre-built modules, including user management, role management, permission management, and content management system (CMS) modules, which makes it easier for developers to create complex applications. + +![Implementing Domain Driven Design with C#](images/ddd.png) + + + +## Open Source Web Application + +ABP Framework is an open-source web application development framework that is free to use and distribute. The framework is licensed under the MIT license, which means that developers can use it for commercial and non-commercial purposes without any restrictions. + + + +## .NET Application Framework with Pre-Built Modules + +ABP Framework is built using the latest .NET technologies and provides developers with a set of tools that makes it easy to build modern web applications. ABP contains several important modules of a line of business applications. + +![ABP Pre-built Modules](images/pre-built-modules.png) + + + +## C# Microservice Framework + +ABP Framework is a C# microservices framework that is designed to help developers build scalable and maintainable microservices. The framework is also known as .NET .net microservices framework. It provides developers with a set of tools that makes it easy to build microservices using clean architecture principles. + +![C# Microservice Framework](images/csharp-microservice-framework.png) + + + +## CRUD Tool Dotnet + +ABP Framework has a commercial version as well. The paid version comes with a premium support, rich themes and there's a very handy tool called [ABP Suite](https://commercial.abp.io/tools/suite) for "CRUD page generation ASP.NET". While ABP is not a low-code or no-code platform, ABP Suite provides ASP NET rapid application development. There are many ASP.NET rapid development tools but these are only tools without a framework support. If you are looking for web based rapid application development tools (also open-source), ABP is the way to go! + +![ABP Suite](images/abp-suite.png) + +## Modular Development + +One of the best sides of the ABP Framework is the modular development side. It's born as a modular system. There are several open source web frameworks around but many of them lack of modularity. + +![Modular Development](images/modular.png) + +Let's see the key features of the ABP Framework: + +- Multi-tenancy support +- Cross-cutting concerns implemented +- Full-stack microservice solution +- SaaS framework +- ASP.NET modular monolith +- Has an ASP.NET user management module +- Distributed events +- Layered architecture +- Free framework for website +- Several framework templates + + + +## The Essential Features of ABP + +ABP Framework is an open source SaaS framework. This crucial feature distinguishes it from other open source web development frameworks. + +![Essential Features](images/essential-features.png) + +The following essential ASP.NET features are available in the ABP Framework: + +ASP.NET modularity, ASP.NET modular development, ASP.NET localization, ASP.NET multi-tenancy, ASP.NET SaaS, ASP.NET SaaS framework, ASP.NET distributed events, ASP.NET distributed event bus, ASP.NET cross-cutting concerns, ASP.NET blob storing, ASP.NET audit logging, ASP.NET microservice, ASP.NET microservice solution, ASP.NET microservice example, ASP.NET API gateway, ASP.NET domain driven design, ASP.NET layered architecture, ASP.NET layering, ASP.NET clean architecture, ASP.NET authentication, ASP.NET authorization, ASP.NET identity, ASP.NET identity server, ASP.NET IdentityServer, ASP.NET payment module, ASP.NET best practices, ASP.NET design patterns, ASP.NET background jobs, ASP.NET exception handling, ASP.NET background workers, ASP.NET repository, ASP.NET repository pattern, ASP.NET unit of work, ASP.NET domain services, ASP.NET swagger, ASP.NET content management system, ASP.NET user management, ASP.NET role management, ASP.NET permission management + + + +## Microservice Example: eShopOnAbp + +[eShopOnAbp](https://www.eshoponabp.com/) is a microservice example built on top ABP. It is a sample net application, similar to the Microsoft's [eShopOnContainer](https://github.com/dotnet-architecture/eShopOnContainers) project. It is a reference microservice solution built with the ABP Framework and .NET, runs on Kubernetes with Helm configuration, includes API Gateways, Angular and ASP.NET Core MVC applications with PostgreSQL and MongoDB databases. For more information, check out https://github.com/abpframework/eShopOnAbp. + + + +## Conclusion + +In conclusion, ABP Framework is an open-source web development framework that offers many features and benefits for building modern and scalable web applications. Its modular and extensible architecture, implementation of Domain-Driven Design, and compatibility with various platforms make it a popular choice for developers. Whether you're building a web application, microservices, or modular monoliths, ABP Framework has everything you need to get started. + +![conclusion](images/conclusion.png) + +Whether you're building a dot net website, a web app infrastructure, or a webpage framework, open source web application frameworks are a cost-effective and flexible option for web development. If you are looking for an open source web application builder which contains project framework template with web application development tools (open source), [ABP Framework](https://abp.io/) is the right choice. \ No newline at end of file diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index 47e60700bc..206c2c58a4 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -440,16 +440,16 @@ Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unle ## Advanced Features -### IServiceCollection.OnRegistred Event +### IServiceCollection.OnRegistered Event -You may want to perform an action for every service registered to the dependency injection. In the `PreConfigureServices` method of your module, register a callback using the `OnRegistred` method as shown below: +You may want to perform an action for every service registered to the dependency injection. In the `PreConfigureServices` method of your module, register a callback using the `OnRegistered` method as shown below: ````csharp public class AppModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(ctx => + context.Services.OnRegistered(ctx => { var type = ctx.ImplementationType; //... @@ -465,7 +465,7 @@ public class AppModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(ctx => + context.Services.OnRegistered(ctx => { if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true)) { @@ -478,7 +478,7 @@ public class AppModule : AbpModule This example simply checks if the service class has `MyLogAttribute` attribute and adds `MyLogInterceptor` to the interceptor list if so. -> Notice that `OnRegistred` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors. +> Notice that `OnRegistered` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors. ## 3rd-Party Providers diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 7dac3762aa..19cf8fa443 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -32,25 +32,6 @@ Configure(options => > Multi-Tenancy is disabled in the ABP Framework by default. However, it is **enabled by default** when you create a new solution using the [startup template](Startup-Templates/Application.md). `MultiTenancyConsts` class in the solution has a constant to control it in a single place. -### AbpMultiTenancyOptions: Handle inactive and non-existent tenants. - -The `MultiTenancyMiddlewareErrorPageBuilder` of `AbpMultiTenancyOptions` is used to handle inactive and non-existent tenants. - -It will respond to an error page by default, you can change it if you want, eg: only output the error log and continue ASP NET Core's request pipeline. - -```csharp -Configure(options => -{ - options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => - { - // Handle the exception. - - // Return true to stop the pipeline, false to continue. - return true; - }; -}); -``` - ### Database Architecture ABP Framework supports all the following approaches to store the tenant data in the database; @@ -273,6 +254,23 @@ class SomeComponent { > However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then. +The `MultiTenancyMiddlewareErrorPageBuilder` is used to handle inactive and non-existent tenants. + +It will respond to an error page by default, you can change it if you want, eg: only output the error log and continue ASP NET Core's request pipeline. + +```csharp +Configure(options => +{ + options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => + { + // Handle the exception. + + // Return true to stop the pipeline, false to continue. + return true; + }; +}); +``` + ##### Domain/Subdomain Tenant Resolver In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure the `AbpTenantResolveOptions` to add the domain tenant resolver. diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md index 6178ebbb8e..e71beccd6f 100644 --- a/docs/en/Tutorials/Part-10.md +++ b/docs/en/Tutorials/Part-10.md @@ -361,7 +361,7 @@ public class BookAppService : GetListPolicyName = BookStorePermissions.Books.Default; CreatePolicyName = BookStorePermissions.Books.Create; UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Create; + DeletePolicyName = BookStorePermissions.Books.Delete; } public override async Task GetAsync(Guid id) diff --git a/docs/en/UI/Angular/Card-Component.md b/docs/en/UI/Angular/Card-Component.md new file mode 100644 index 0000000000..861171c39e --- /dev/null +++ b/docs/en/UI/Angular/Card-Component.md @@ -0,0 +1,206 @@ +# Card Component + +The ABP Card Component is a wrapper component for the Bootstrap card class. +It supports all the features that Bootstrap card component provides. + +ABP Card Component has three main components, `CardHeader`, `CardBody` and `CardFooter`. These components have their own class and style inputs + +|Component |Selector |Input Properties | +|--------- |-----------------|------------------------------------| +|CardHeader|`abp-card-header`| `cardHeaderClass`,`cardHeaderStyle`| +|CardBody |`abp-card-body` | `cardBodyClass`,`cardBodyStyle` | +|CardFooter|`abp-card-footer`| `cardFooterClass`,`cardFooterStyle`| + +In addition to these components, the Card component provides directives like `CardHeader`,`CardTitle`,`CardSubtitle`,`CardImgTop`. + +|Directive |Selector | +|-------------|-------------------------------------------------------------| +|CardHeader |`abp-card-header`,`[abp-card-header]`,`[abpCardHeader]` | +|CardTitle |`abp-card-title`,`[abp-card-title]`,`[abpCardTitle]` | +|CardSubtitle |`abp-card-subtitle`,`[abp-card-subtitle]`,`[abpCardSubtitle]`| +|CardImgTop |`abp-card-img-top`,`[abp-card-img-top]`,`[abpCardImgTop]` | + + +# Usage + +ABP Card Component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, you don't need to import it again. If not, first import it as shown below: + +```ts +// my-feature.module.ts + +import { ThemeSharedModule } from '@abp/ng.theme.shared'; +import { CardDemoComponent } from './chart-demo.component'; + +@NgModule({ + imports: [ + ThemeSharedModule , + // ... + ], + declarations: [CardDemoComponent], + // ... +}) +export class MyFeatureModule {} + +``` + +Then, the `abp-card` component can be used. See the examples below: + +## CardBody + +```ts +// card-demo.component.ts + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-card-demo', + template: ` + + This is some text within a card body + + `, +}) +export class CardDemoComponent { } +``` +See the card body result below: + +![abp-card-body](./images/abp-card-body.png) + +## Titles, Text and Links + +```ts + +//card-demo.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-card-demo', + template: ` + + +
Card Title
+
Card subtitle
+

Some quick example text to build on the card title and make up the bulk of the card's content.

+ Card link + Another link +
+
+ `, +}) +export class CardDemoComponent { } +``` +See the card title, text and link result below: + +![abp-card-title-text-link](./images/abp-card-title-text-link.png) + +## Images + +```ts + +//card-demo.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-card-demo', + template: ` + + ... + +

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+ `, +}) +export class CardDemoComponent { } +``` +See the card image result below: + +![abp-card-image-top](./images/abp-card-image.png) + +## List Groups + +```ts + +//card-demo.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-card-demo', + template: ` + +
    +
  • An item
  • +
  • A second item
  • +
  • A third item
  • +
+
+ `, +}) +export class CardDemoComponent { } +``` +See the group list result below: + +![abp-card-list-group](./images/abp-card-list-group.png) + +## Kitchen Sink + +```ts + +//card-demo.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-card-demo', + template: ` + + ... + +
Card title
+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
    +
  • An item
  • +
  • A second item
  • +
  • A third item
  • +
+ + Card link + Another link + +
+ `, +}) +export class CardDemoComponent { } +``` +See kitchen sink result below: + +![abp-card-kitchen-sink](./images/abp-card-kitchen-sink.png) + +## Header and Footer + +```ts + +//card-demo.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-card-demo', + template: ` + + Featured + +
Special title treatment
+

With supporting text below as a natural lead-in to additional content.

+ Go somewhere +
+ + 2 days ago + +
+ `, +}) +export class CardDemoComponent { } +``` +See the header and footer result below: + +![abp-card-header-footer](./images/abp-card-header-footer.png) diff --git a/docs/en/UI/Angular/Modifying-the-Menu.md b/docs/en/UI/Angular/Modifying-the-Menu.md index b0a3b760fa..c59ce0d650 100644 --- a/docs/en/UI/Angular/Modifying-the-Menu.md +++ b/docs/en/UI/Angular/Modifying-the-Menu.md @@ -88,14 +88,65 @@ function configureRoutes(routes: RoutesService) { } ``` +We can also define a group for navigation elements. It's an optional property + - **Note:** It'll also include groups that were defined at the modules + +```js +// route.provider.ts +import { RoutesService } from '@abp/ng.core'; + +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + //etc.. + group: 'ModuleName::GroupName' + }, + { + path: '/your-path/child', + name: 'Your child navigation', + parentName: 'Your navigation', + requiredPolicy: 'permission key here', + order: 1, + }, + ]); + }; +} +``` + +To get the route items as grouped we can use the `groupedVisible` (or Observable one `groupedVisible$`) getter methods + - It returns `RouteGroup[]` if there is any group in the route tree, otherwise it returns `undefined` + +```js +import { ABP, RoutesService, RouteGroup } from "@abp/ng.core"; +import { Component } from "@angular/core"; + +@Component(/* component metadata */) +export class AppComponent { + visible: RouteGroup[] | undefined = this.routes.groupedVisible; + //Or + visible$:Observable[] | undefined> = this.routes.groupedVisible$; + + constructor(private routes: RoutesService) {} +} +``` + ...and then in app.module.ts... + - The `groupedVisible` method will return the `Others` group for ungrouped items, the default key is `AbpUi::OthersGroup`, we can change this `key` via the `OTHERS_GROUP` injection token ```js import { NgModule } from '@angular/core'; +import { OTHERS_GROUP } from '@abp/ng.core'; import { APP_ROUTE_PROVIDER } from './route.provider'; @NgModule({ - providers: [APP_ROUTE_PROVIDER], + providers: [ + APP_ROUTE_PROVIDER, + { + provide: OTHERS_GROUP, + useValue: 'ModuleName::MyOthersGroupKey', + }, + ], // imports, declarations, and bootstrap }) export class AppModule {} @@ -109,8 +160,9 @@ Here is what every property works as: - `requiredPolicy` is the permission key to access the page. See the [Permission Management document](./Permission-Management.md) - `order` is the order of the navigation element. "Administration" has an order of `100`, so keep that in mind when ordering top level menu items. - `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label. -- `layout` defines in which layout the route will be loaded. (default: `eLayoutType.empty`) +- `layout` defines in which layout the route is loaded. (default: `eLayoutType.empty`) - `invisible` makes the item invisible in the menu. (default: `false`) +- `group` is an optional property that is used to group together related routes in an application. (type: `string`, default: `AbpUi::OthersGroup`) ### Via `routes` Property in `AppRoutingModule` diff --git a/docs/en/UI/Angular/Quick-Start.md b/docs/en/UI/Angular/Quick-Start.md index 768b3b7b67..dd26dd3efe 100644 --- a/docs/en/UI/Angular/Quick-Start.md +++ b/docs/en/UI/Angular/Quick-Start.md @@ -179,7 +179,7 @@ This command will download and start a simple static server, a browser window at Of course, you need your application to run on an optimized web server and become available to everyone. This is quite straight-forward: -1. Create a new static web server instance. You can use a service like [Azure App Service](https://azure.microsoft.com/tr-tr/services/app-service/web/), [Firebase](https://firebase.google.com/docs/hosting), [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), or even [GitHub Pages](https://angular.io/guide/deployment#deploy-to-github-pages). Another option is maintaining own web server with [NGINX](https://www.nginx.com/), [IIS](https://www.iis.net/), [Apache HTTP Server](https://httpd.apache.org/), or equivalent. +1. Create a new static web server instance. You can use a service like [Azure App Service](https://azure.microsoft.com/en-us/services/app-service/web/), [Firebase](https://firebase.google.com/docs/hosting), [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), or even [GitHub Pages](https://angular.io/guide/deployment#deploy-to-github-pages). Another option is maintaining own web server with [NGINX](https://www.nginx.com/), [IIS](https://www.iis.net/), [Apache HTTP Server](https://httpd.apache.org/), or equivalent. 2. Copy the files from `dist/MyProjectName` [1](#f-dist-folder-name) to a publicly served destination on the server via CLI of the service provider, SSH, or FTP (whichever is available). This step would be defined as a job if you have a CI/CD flow. 3. [Configure the server](https://angular.io/guide/deployment#server-configuration) to redirect all requests to the _index.html_ file. Some services do that automatically. Others require you [to add a file to the bundle via assets](https://angular.io/guide/workspace-config#assets-configuration) which describes the server how to do the redirections. Occasionally, you may need to do manual configuration. diff --git a/docs/en/UI/Angular/images/abp-card-body.png b/docs/en/UI/Angular/images/abp-card-body.png new file mode 100644 index 0000000000..d5d7d07d29 Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-body.png differ diff --git a/docs/en/UI/Angular/images/abp-card-component.png b/docs/en/UI/Angular/images/abp-card-component.png new file mode 100644 index 0000000000..726c617772 Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-component.png differ diff --git a/docs/en/UI/Angular/images/abp-card-header-footer.png b/docs/en/UI/Angular/images/abp-card-header-footer.png new file mode 100644 index 0000000000..72f0d7be45 Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-header-footer.png differ diff --git a/docs/en/UI/Angular/images/abp-card-header.png b/docs/en/UI/Angular/images/abp-card-header.png new file mode 100644 index 0000000000..a13ed5b3fe Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-header.png differ diff --git a/docs/en/UI/Angular/images/abp-card-image.png b/docs/en/UI/Angular/images/abp-card-image.png new file mode 100644 index 0000000000..c4136d524b Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-image.png differ diff --git a/docs/en/UI/Angular/images/abp-card-kitchen-sink.png b/docs/en/UI/Angular/images/abp-card-kitchen-sink.png new file mode 100644 index 0000000000..908e062ff0 Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-kitchen-sink.png differ diff --git a/docs/en/UI/Angular/images/abp-card-list-group.png b/docs/en/UI/Angular/images/abp-card-list-group.png new file mode 100644 index 0000000000..aa7b746647 Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-list-group.png differ diff --git a/docs/en/UI/Angular/images/abp-card-title-text-link.png b/docs/en/UI/Angular/images/abp-card-title-text-link.png new file mode 100644 index 0000000000..af7d009929 Binary files /dev/null and b/docs/en/UI/Angular/images/abp-card-title-text-link.png differ diff --git a/docs/en/UI/AspNetCore/Navigation-Menu.md b/docs/en/UI/AspNetCore/Navigation-Menu.md index f0776f47ec..60621f9906 100644 --- a/docs/en/UI/AspNetCore/Navigation-Menu.md +++ b/docs/en/UI/AspNetCore/Navigation-Menu.md @@ -104,6 +104,7 @@ There are more options of a menu item (the constructor of the `ApplicationMenuIt * `target` (`string`): Target of the menu item. Can be `null` (default), "\_*blank*", "\_*self*", "\_*parent*", "\_*top*" or a frame name for web applications. * `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute. * `cssClass` (`string`): Additional string classes for the menu item. +* `groupName` (`string`): Can be used to group menu items. ### Authorization @@ -179,6 +180,58 @@ userMenu.Icon = "fa fa-users"; > `context.Menu` gives you ability to access to all the menu items those have been added by the previous menu contributors. +### Menu Groups + +You can define groups and associate menu items with a group. + +Example: + +```csharp +using System.Threading.Tasks; +using MyProject.Localization; +using Volo.Abp.UI.Navigation; + +namespace MyProject.Web.Menus +{ + public class MyProjectMenuContributor : IMenuContributor + { + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + await ConfigureMainMenuAsync(context); + } + } + + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + var l = context.GetLocalizer(); + + context.Menu.AddGroup( + new ApplicationMenuGroup( + name: "Main", + displayName: l["Main"] + ) + ) + context.Menu.AddItem( + new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"], groupName: "Main") + .AddItem(new ApplicationMenuItem( + name: "MyProject.Crm.Customers", + displayName: l["Menu:Customers"], + url: "/crm/customers") + ).AddItem(new ApplicationMenuItem( + name: "MyProject.Crm.Orders", + displayName: l["Menu:Orders"], + url: "/crm/orders") + ) + ); + } + } +} +``` + +> The UI theme will decide whether to render the groups or not, and if it decides to render, the way it's rendered is up to the theme. Only the LeptonX theme implements the menu group. + ## Standard Menus A menu is a **named** component. An application may contain more than one menus with different, unique names. There are two pre-defined standard menus: @@ -233,4 +286,3 @@ namespace MyProject.Web.Pages } } ``` - diff --git a/docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md b/docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md index 8f664a80a5..54e9712d30 100644 --- a/docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md +++ b/docs/en/UI/AspNetCore/Tag-Helpers/Form-elements.md @@ -10,7 +10,7 @@ See the [form elements demo page](https://bootstrap-taghelpers.abp.io/Components ## abp-input -`abp-input` tag creates a Bootstrap form input for a given c# property. It uses [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/tr-tr/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-input-tag-helper) in background, so every data annotation attribute of `input` tag helper of Asp.Net Core is also valid for `abp-input`. +`abp-input` tag creates a Bootstrap form input for a given c# property. It uses [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-input-tag-helper) in background, so every data annotation attribute of `input` tag helper of Asp.Net Core is also valid for `abp-input`. Usage: @@ -58,49 +58,49 @@ Model: ### Attributes -You can set some of the attributes on your c# property, or directly on html tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes. +You can set some of the attributes on your c# property, or directly on HTML tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes. #### Property Attributes - `[TextArea()]`: Converts the input into a text area. -* `[Placeholder()]`: Sets placeholder for input. You can use a localization key directly. -* `[InputInfoText()]`: Sets a small info text for input. You can use a localization key directly. -* `[FormControlSize()]`: Sets size of form-control wrapper element. Available values are +* `[Placeholder()]`: Sets the description are of the input. You can use a localization key directly. +* `[InputInfoText()]`: Sets text for the input. You can directly use a localization key. +* `[FormControlSize()]`: Sets the size of the form-control wrapper element. Available values are - `AbpFormControlSize.Default` - `AbpFormControlSize.Small` - `AbpFormControlSize.Medium` - `AbpFormControlSize.Large` -* `[DisabledInput]` : Input is disabled. -* `[ReadOnlyInput]`: Input is read-only. +* `[DisabledInput]` : Sets the input as disabled. +* `[ReadOnlyInput]`: Sets the input as read-only. #### Tag Attributes -* `info`: Sets a small info text for input. You can use a localization key directly. -* `auto-focus`: If true, browser auto focuses on the element. -* `size`: Sets size of form-control wrapper element. Available values are +* `info`: Sets text for the input. You can directly use a localization key. +* `auto-focus`: It lets the browser set focus to the element when its value is true. +* `size`: Sets the size of the form-control wrapper element. Available values are - `AbpFormControlSize.Default` - `AbpFormControlSize.Small` - `AbpFormControlSize.Medium` - `AbpFormControlSize.Large` -* `disabled`: Input is disabled. -* `readonly`: Input is read-only. -* `label`: Sets the label for input. -* `display-required-symbol`: Adds the required symbol (*) to label if input is required. Default `True`. +* `disabled`: Sets the input as disabled. +* `readonly`: Sets the input as read-only. +* `label`: Sets the label of input. +* `required-symbol`: Adds the required symbol `(*)` to the label when the input is required. The default value is `True`. -`asp-format`, `name` and `value` attributes of [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-input-tag-helper) are also valid for `abp-input` tag helper. +`asp-format`, `name` and `value` attributes of [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-input-tag-helper) are also valid for `abp-input` tag helper. ### Label & Localization -You can set label of your input in different ways: +You can set the label of the input in several ways: -- You can use `Label` attribute and directly set the label. But it doesn't auto localize your localization key. So use it as `label="@L["{LocalizationKey}"].Value"`. -- You can set it using `[Display(name="{LocalizationKey}")]` attribute of Asp.Net Core. +- You can use the `Label` attribute to set the label directly. This property does not automatically localize the text. To localize the label, use `label="@L["{LocalizationKey}"].Value"`. +- You can set it using `[Display(name="{LocalizationKey}")]` attribute of ASP.NET Core. - You can just let **abp** find the localization key for the property. It will try to find "DisplayName:{PropertyName}" or "{PropertyName}" localization keys, if `label` or `[DisplayName]` attributes are not set. ## abp-select -`abp-select` tag creates a Bootstrap form select for a given c# property. It uses [Asp.Net Core Select Tag Helper](https://docs.microsoft.com/tr-tr/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-select-tag-helper) in background, so every data annotation attribute of `select` tag helper of Asp.Net Core is also valid for `abp-select`. +`abp-select` tag creates a Bootstrap form select for a given c# property. It uses [ASP.NET Core Select Tag Helper](https://docs.microsoft.com/tr-tr/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-select-tag-helper) in background, so every data annotation attribute of `select` tag helper of ASP.NET Core is also valid for `abp-select`. `abp-select` tag needs a list of `Microsoft.AspNetCore.Mvc.Rendering.SelectListItem ` to work. It can be provided by `asp-items` attriube on the tag or `[SelectItems()]` attribute on c# property. (if you are using [abp-dynamic-form](Dynamic-forms.md), c# attribute is the only way.) @@ -170,14 +170,14 @@ Model: ### Attributes -You can set some of the attributes on your c# property, or directly on html tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes. +You can set some of the attributes on your c# property, or directly on HTML tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes. #### Property Attributes * `[SelectItems()]`: Sets the select data. Parameter should be the name of the data list. (see example above) -- `[InputInfoText()]`: Sets a small info text for input. You can use a localization key directly. -- `[FormControlSize()]`: Sets size of form-control wrapper element. Available values are +- `[InputInfoText()]`: Sets text for the input. You can directly use a localization key. +- `[FormControlSize()]`: Sets the size of the form-control wrapper element. Available values are - `AbpFormControlSize.Default` - `AbpFormControlSize.Small` - `AbpFormControlSize.Medium` @@ -186,21 +186,21 @@ You can set some of the attributes on your c# property, or directly on html tag. #### Tag Attributes - `asp-items`: Sets the select data. This Should be a list of SelectListItem. -- `info`: Sets a small info text for input. You can use a localization key directly. -- `size`: Sets size of form-control wrapper element. Available values are +- `info`: Sets text for the input. You can directly use a localization key. +- `size`: Sets the size of the form-control wrapper element. Available values are - `AbpFormControlSize.Default` - `AbpFormControlSize.Small` - `AbpFormControlSize.Medium` - `AbpFormControlSize.Large` -- `label`: Sets the label for input. -- `display-required-symbol`: Adds the required symbol (*) to label if input is required. Default `True`. +- `label`: Sets the label of input. +- `required-symbol`: Adds the required symbol `(*)` to the label when the input is required. The default value is `True`. ### Label & Localization -You can set label of your input in different ways: +You can set the label of the input in several ways: - You can use `Label` attribute and directly set the label. But it doesn't auto localize your localization key. So use it as `label="@L["{LocalizationKey}"].Value".` -- You can set it using `[Display(name="{LocalizationKey}")]` attribute of Asp.Net Core. +- You can set it using `[Display(name="{LocalizationKey}")]` attribute of ASP.NET Core. - You can just let **abp** find the localization key for the property. It will try to find "DisplayName:{PropertyName}" or "{PropertyName}" localization keys. Localizations of combobox values are set by `abp-select` for **Enum** property. It searches for "{EnumTypeName}.{EnumPropertyName}" or "{EnumPropertyName}" localization keys. For instance, in the example above, it will use "CarType.StationWagon" or "StationWagon" keys for localization when it localizes combobox values. @@ -251,7 +251,7 @@ Model: ### Attributes -You can set some of the attributes on your c# property, or directly on html tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes. +You can set some of the attributes on your c# property, or directly on HTML tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes. #### Property Attributes @@ -261,3 +261,172 @@ You can set some of the attributes on your c# property, or directly on html tag. - `asp-items`: Sets the select data. This Should be a list of SelectListItem. - `Inline`: If true, radio buttons will be in single line, next to each other. If false, they will be under each other. + +## abp-date-picker & abp-date-range-picker + +`abp-date-picker` and `abp-date-range-picker` tags creates a Bootstrap form date picker for a given c# property. `abp-date-picker` is for single date selection, `abp-date-range-picker` is for date range selection. It uses [datepicker](https://www.daterangepicker.com/) jQuery plugin. + +Usage: + +````xml + + + +```` + +Model: + +````csharp + public class FormElementsModel : PageModel + { + public SampleModel MyModel { get; set; } + + public DynamicForm DynamicFormExample { get; set; } + + public void OnGet() + { + MyModel = new SampleModel(); + + DynamicFormExample = new DynamicForm(); + } + + public class SampleModel + { + public DateTime MyDate { get; set; } + + public DateTime MyDateRangeStart { get; set; } + + public DateTime MyDateRangeEnd { get; set; } + } + + public class DynamicForm + { + [DateRangePicker("MyPicker",true)] + public DateTime StartDate { get; set; } + + [DateRangePicker("MyPicker",false)] + [DatePickerOptions(nameof(DatePickerOptions))] + public DateTime EndDate { get; set; } + + public DateTime DateTime { get; set; } + + public DynamicForm() + { + StartDate = DateTime.Now; + EndDate = DateTime.Now; + DateTime = DateTime.Now; + } + } + + public AbpDatePickerOptions DatePickerOptions { get; set; } + } +```` + +### Attributes + +You can set some of the attributes on your c# property, or directly on HTML tag. If you are going to use this property in a [abp-dynamic-form](Dynamic-forms.md), then you can only set these properties via property attributes. + +#### Property Attributes + +* `[Placeholder()]`: Sets the description are of the input. You can use a localization key directly. +* `[InputInfoText()]`: Sets text for the input. You can directly use a localization key. +* `[FormControlSize()]`: Sets the size of the form-control wrapper element. Available values are + - `AbpFormControlSize.Default` + - `AbpFormControlSize.Small` + - `AbpFormControlSize.Medium` + - `AbpFormControlSize.Large` +* `[DisabledInput]` : Sets the input as disabled. +* `[ReadOnlyInput]`: Sets the input as read-only. +- `[DatePickerOptions()]`: Sets the predefined options of the date picker. Parameter should be the name of the options property (see example above). See the available [datepicker options](https://www.daterangepicker.com/#options). You can use a localization key directly. + +##### abp-date-picker +`[DatePicker]` : Sets the input as datepicker. Especially for string properties. + +##### abp-date-range-picker +`[DateRangePicker()]` : Sets the picker id for the date range picker. You can set the property as a start date by setting IsStart=true or leave it as default/false to set it as an end date. + +#### Tag Attributes + +* `info`: Sets text for the input. You can directly use a localization key. +* `auto-focus`: It lets the browser set focus to the element when its value is true. +* `size`: Sets the size of the form-control wrapper element. Available values are + - `AbpFormControlSize.Default` + - `AbpFormControlSize.Small` + - `AbpFormControlSize.Medium` + - `AbpFormControlSize.Large` +* `disabled`: Sets the input as disabled. +* `readonly`: Sets the input as read-only. +* `label`: Sets the label of input. +* `required-symbol`: Adds the required symbol `(*)` to the label when the input is required. The default value is `True`. +* `open-button`: A button to open the datepicker will be added when its `True`. The default value is `True`. +* `clear-button`: A button to clear the datepicker will be added when its `True`. The default value is `True`. +* `single-open-and-clear-button`: Shows the open and clear buttons in a single button when it's `True`. The default value is `True`. +* `is-utc`: Converts the date to UTC when its `True`. The default value is `False`. +* `is-iso`: Converts the date to ISO format when its `True`. The default value is `False`. +* `date-format`: Sets the date format of the input. The default format is the user's culture date format. You need to provide a JavaScript date format convention. Eg: `YYYY-MM-DDTHH:MM:SSZ`. +* `date-separator`: Sets a character to separate start and end dates. The default value is `-` +* Other non-mapped attributes will be automatically added to the input element as is. See the available [datepicker options](https://www.daterangepicker.com/#options). Eg: `data-start-date="2020-01-01"` + +##### abp-date-picker + +* `asp-date`: Sets the date value. This should be a `DateTime`, `DateTime?`, `DateTimeOffset`, `DateTimeOffset?` or `string` value. + +##### abp-date-range-picker + +* `asp-for-start`: Sets the start date value. This should be a `DateTime`, `DateTime?`, `DateTimeOffset`, `DateTimeOffset?` or `string` value. +* `asp-for-end`: Sets the end date value. This should be a `DateTime`, `DateTime?`, `DateTimeOffset`, `DateTimeOffset?` or `string` value. + +### Label & Localization + +You can set the label of the input in several ways: + +- You can use the `Label` attribute to set the label directly. This property does not automatically localize the text. To localize the label, use `label="@L["{LocalizationKey}"].Value"`. +- You can set it using `[Display(name="{LocalizationKey}")]` attribute ASP.NET Core. +- You can just let **abp** find the localization key for the property. If the `label` or `[DisplayName]` attributes are not set, it tries to find the localization by convention. For example `DisplayName:{YourPropertyName}` or `{YourPropertyName}`. If your property name is FullName, it will search for `DisplayName:FullName` or `{FullName}`. + +### JavaScript Usage + +````javascript +var newPicker = abp.libs.bootstrapDateRangePicker.createDateRangePicker( + { + label: "New Picker", + } +); +newPicker.insertAfter($('body')); +```` + +````javascript +var newPicker = abp.libs.bootstrapDateRangePicker.createSinglePicker( + { + label: "New Picker", + } +); +newPicker.insertAfter($('body')); +```` + +#### Options + +* `label`: Sets the label of the input. +* `placeholder`: Sets the placeholder of the input. +* `value`: Sets the value of the input. +* `name`: Sets the name of the input. +* `id`: Sets the id of the input. +* `required`: Sets the input as required. +* `disabled`: Sets the input as disabled. +* `readonly`: Sets the input as read-only. +* `size`: Sets the size of the form-control wrapper element. Available values are + - `AbpFormControlSize.Default` + - `AbpFormControlSize.Small` + - `AbpFormControlSize.Medium` + - `AbpFormControlSize.Large` +* `openButton`: A button to open the datepicker will be added when its `True`. The default value is `True`. +* `clearButton`: A button to clear the datepicker will be added when its `True`. The default value is `True`. +* `singleOpenAndClearButton`: Shows the open and clear buttons in a single button when it's `True`. The default value is `True`. +* `isUtc`: Converts the date to UTC when its `True`. The default value is `False`. +* `isIso`: Converts the date to ISO format when its `True`. The default value is `False`. +* `dateFormat`: Sets the date format of the input. The default format is the user's culture date format. You need to provide a JavaScript date format convention. Eg: `YYYY-MM-DDTHH:MM:SSZ`. +* `dateSeparator`: Sets a character to separate start and end dates. The default value is `-`. +* `startDateName`: Sets the name of the hidden start date input. +* `endDateName`: Sets the name of the hidden end date input. +* `dateName`: Sets the name of the hidden date input. +* Other [datepicker options](https://www.daterangepicker.com/#options). Eg: `startDate: "2020-01-01"`. \ No newline at end of file diff --git a/docs/en/UI/Blazor/Basic-Theme.md b/docs/en/UI/Blazor/Basic-Theme.md index 4060f097ba..e4124d9a2c 100644 --- a/docs/en/UI/Blazor/Basic-Theme.md +++ b/docs/en/UI/Blazor/Basic-Theme.md @@ -85,6 +85,27 @@ You can simply override the styles in the Global Styles file of your application See the [Customization / Overriding Components](Customization-Overriding-Components.md) to learn how you can replace components, customize and extend the user interface. +### Overriding the Menu Item +Basic theme supports overriding a single menu item with a custom component. You can create a custom component and call `UseComponent` extension method of Basic Theme in the **MenuContributor**. + +```csharp +using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Navigation; + +//... + +context.Menu.Items.Add( + new ApplicationMenuItem("Custom.1", "My Custom Menu", "#") + .UseComponent(typeof(MyMenuItemComponent))); +``` + +```html + +``` + ### Copy & Customize You can run the following [ABP CLI](../../CLI.md) command in **Blazor{{if UI == "Blazor"}}WebAssembly{{else}} Server{{end}}** project directory to copy the source code to your solution: diff --git a/docs/en/UI/Blazor/Navigation-Menu.md b/docs/en/UI/Blazor/Navigation-Menu.md index 7f09a65dbb..0abac5fc68 100644 --- a/docs/en/UI/Blazor/Navigation-Menu.md +++ b/docs/en/UI/Blazor/Navigation-Menu.md @@ -104,6 +104,7 @@ There are more options of a menu item (the constructor of the `ApplicationMenuIt * `target` (`string`): Target of the menu item. Can be `null` (default), "\_*blank*", "\_*self*", "\_*parent*", "\_*top*" or a frame name for web applications. * `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute. * `cssClass` (`string`): Additional string classes for the menu item. +* `groupName` (`string`): Can be used to group menu items. ### Authorization @@ -160,6 +161,58 @@ userMenu.Icon = "fa fa-users"; > `context.Menu` gives you ability to access to all the menu items those have been added by the previous menu contributors. +### Menu Groups + +You can define groups and associate menu items with a group. + +Example: + +```csharp +using System.Threading.Tasks; +using MyProject.Localization; +using Volo.Abp.UI.Navigation; + +namespace MyProject.Web.Menus +{ + public class MyProjectMenuContributor : IMenuContributor + { + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + await ConfigureMainMenuAsync(context); + } + } + + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + var l = context.GetLocalizer(); + + context.Menu.AddGroup( + new ApplicationMenuGroup( + name: "Main", + displayName: l["Main"] + ) + ) + context.Menu.AddItem( + new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"], groupName: "Main") + .AddItem(new ApplicationMenuItem( + name: "MyProject.Crm.Customers", + displayName: l["Menu:Customers"], + url: "/crm/customers") + ).AddItem(new ApplicationMenuItem( + name: "MyProject.Crm.Orders", + displayName: l["Menu:Orders"], + url: "/crm/orders") + ) + ); + } + } +} +``` + +> The UI theme will decide whether to render the groups or not, and if it decides to render, the way it's rendered is up to the theme. Only the LeptonX theme implements the menu group. + ## Standard Menus A menu is a **named** component. An application may contain more than one menus with different, unique names. There are two pre-defined standard menus: diff --git a/docs/en/UI/Blazor/Overall.md b/docs/en/UI/Blazor/Overall.md index 835bf4eab6..f38c0c257b 100644 --- a/docs/en/UI/Blazor/Overall.md +++ b/docs/en/UI/Blazor/Overall.md @@ -88,7 +88,7 @@ There are a set of standard libraries that comes pre-installed and supported by * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. -* [Flag Icon](https://github.com/lipis/flag-icon-css) as a library to show flags of countries. +* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries. These libraries are selected as the base libraries and available to the applications and modules. diff --git a/docs/en/UI/Blazor/Theming.md b/docs/en/UI/Blazor/Theming.md index d8cec07cb3..2496df9b20 100644 --- a/docs/en/UI/Blazor/Theming.md +++ b/docs/en/UI/Blazor/Theming.md @@ -48,7 +48,7 @@ All the themes must depend on the [Volo.Abp.AspNetCore.Components.Server.Theming * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. -* [Flag Icon](https://github.com/lipis/flag-icon-css) as a library to show flags of countries. +* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries. These libraries are selected as the base libraries and available to the applications and modules. diff --git a/docs/zh-Hans/Background-Jobs-Hangfire.md b/docs/zh-Hans/Background-Jobs-Hangfire.md index fb899758cc..d3fb341bab 100644 --- a/docs/zh-Hans/Background-Jobs-Hangfire.md +++ b/docs/zh-Hans/Background-Jobs-Hangfire.md @@ -66,6 +66,8 @@ public class YourModule : AbpModule } ```` +> 你必须为Hangfire配置一个存储 + ### 指定队列 你可以使用 [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) 来指定队列. diff --git a/docs/zh-Hans/Background-Workers-Hangfire.md b/docs/zh-Hans/Background-Workers-Hangfire.md index fde7b35973..bdd546eba8 100644 --- a/docs/zh-Hans/Background-Workers-Hangfire.md +++ b/docs/zh-Hans/Background-Workers-Hangfire.md @@ -41,6 +41,36 @@ public class YourModule : AbpModule > Hangfire后台工作者集成提供了 `HangfirePeriodicBackgroundWorkerAdapter` 来适配 `PeriodicBackgroundWorkerBase` 和 `AsyncPeriodicBackgroundWorkerBase` 派生类. 所以你依然可以按照[后台工作者文档](Background-Workers.md)来定义后台作业. +## 配置 + +你可以安装任何Hangfire存储. 最常用的是SQL Server(参阅[Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer)NuGet包). + +当你安装NuGet包后,你需要为你的项目配置Hangfire. + +1.首先, 我们需要更改 `Module` 类 (例如: `HttpApiHostModule`) 的 `ConfigureServices` 方法去配置Hangfire存储和连接字符串: + +````csharp + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + //... other configarations. + + ConfigureHangfire(context, configuration); + } + + private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) + { + context.Services.AddHangfire(config => + { + config.UseSqlServerStorage(configuration.GetConnectionString("Default")); + }); + } +```` + +> 你必须为Hangfire配置一个存储 + ## 创建后台工作者 `HangfireBackgroundWorkerBase` 是创建一个后台工作者简单的方法. diff --git a/docs/zh-Hans/Dependency-Injection.md b/docs/zh-Hans/Dependency-Injection.md index 2264a12930..2c22c123ab 100644 --- a/docs/zh-Hans/Dependency-Injection.md +++ b/docs/zh-Hans/Dependency-Injection.md @@ -270,16 +270,16 @@ using (var scope = _serviceProvider.CreateScope()) ## 高级特性 -### IServiceCollection.OnRegistred 事件 +### IServiceCollection.OnRegistered 事件 -你可能想在注册到依赖注入的每个服务上执行一个操作, 在你的模块的 `PreConfigureServices` 方法中, 使用 `OnRegistred` 方法注册一个回调(callback) , 如下所示: +你可能想在注册到依赖注入的每个服务上执行一个操作, 在你的模块的 `PreConfigureServices` 方法中, 使用 `OnRegistered` 方法注册一个回调(callback) , 如下所示: ````csharp public class AppModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(ctx => + context.Services.OnRegistered(ctx => { var type = ctx.ImplementationType; //... @@ -295,7 +295,7 @@ public class AppModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(ctx => + context.Services.OnRegistered(ctx => { if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true)) { @@ -308,7 +308,7 @@ public class AppModule : AbpModule 这个示例判断一个服务类是否具有 `MyLogAttribute` 特性, 如果有的话就添加一个 `MyLogInterceptor` 到拦截器集合中. -> 注意, 如果服务类公开了多于一个服务或接口, `OnRegistred` 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 `Interceptors.TryAdd` 方法而不是 `Interceptors.Add` 方法. 请参阅动态代理(dynamic proxying)/拦截器 [文档](Dynamic-Proxying-Interceptors.md). +> 注意, 如果服务类公开了多于一个服务或接口, `OnRegistered` 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 `Interceptors.TryAdd` 方法而不是 `Interceptors.Add` 方法. 请参阅动态代理(dynamic proxying)/拦截器 [文档](Dynamic-Proxying-Interceptors.md). ## 第三方提供程序 diff --git a/docs/zh-Hans/Tutorials/Part-10.md b/docs/zh-Hans/Tutorials/Part-10.md index 170e3106a1..da5c23c78f 100644 --- a/docs/zh-Hans/Tutorials/Part-10.md +++ b/docs/zh-Hans/Tutorials/Part-10.md @@ -367,7 +367,7 @@ namespace Acme.BookStore.Books GetListPolicyName = BookStorePermissions.Books.Default; CreatePolicyName = BookStorePermissions.Books.Create; UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Create; + DeletePolicyName = BookStorePermissions.Books.Delete; } public override async Task GetAsync(Guid id) diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs new file mode 100644 index 0000000000..61c064b376 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs @@ -0,0 +1,56 @@ +using System; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Authentication.Cookies; + +public static class CookieAuthenticationOptionsExtensions +{ + /// + /// Introspect access token on validating the principal. + /// + /// + /// + /// + public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc") + { + var originalHandler = options.Events.OnValidatePrincipal; + options.Events.OnValidatePrincipal = async principalContext => + { + originalHandler?.Invoke(principalContext); + + if (principalContext.Principal != null && principalContext.Principal.Identity != null && principalContext.Principal.Identity.IsAuthenticated) + { + var accessToken = principalContext.Properties.GetTokenValue("access_token"); + if (!accessToken.IsNullOrWhiteSpace()) + { + var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); + if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null) + { + openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted); + } + + var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest + { + Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect", + ClientId = openIdConnectOptions.ClientId, + ClientSecret = openIdConnectOptions.ClientSecret, + Token = accessToken + }); + + if (response.IsActive) + { + return; + } + } + + principalContext.RejectPrincipal(); + await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); + } + }; + + return options; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj index 09c743bc2a..297b2a8576 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj @@ -12,11 +12,13 @@
- - - - - + + + + + + +
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs index 9a6a40a17a..aa48ad633b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs @@ -10,14 +10,22 @@ public class TableColumn { public string Title { get; set; } public string Data { get; set; } + + public string PropertyName { get; set; } + [CanBeNull] public string DisplayFormat { get; set; } + public IFormatProvider DisplayFormatProvider { get; set; } = CultureInfo.CurrentCulture; + [CanBeNull] public Type Component { get; set; } + public List Actions { get; set; } + [CanBeNull] public Func ValueConverter { get; set; } + public bool Sortable { get; set; } public TableColumn() diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs new file mode 100644 index 0000000000..40ac508030 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Configuration; + +[Dependency(ReplaceServices = true)] +public class BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService : + ICurrentApplicationConfigurationCacheResetService, + ITransientDependency +{ + private readonly WebAssemblyCachedApplicationConfigurationClient _webAssemblyCachedApplicationConfigurationClient; + + public BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService(WebAssemblyCachedApplicationConfigurationClient webAssemblyCachedApplicationConfigurationClient) + { + _webAssemblyCachedApplicationConfigurationClient = webAssemblyCachedApplicationConfigurationClient; + } + + public async Task ResetAsync() + { + await _webAssemblyCachedApplicationConfigurationClient.InitializeAsync(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs index 38f55ca04e..775b19beb1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; @@ -144,6 +145,10 @@ public class AbpDynamicFormTagHelperService : AbpTagHelperService(); + abpDateInputTagHelper.AspFor = model; + abpDateInputTagHelper.ViewContext = TagHelper.ViewContext; + abpDateInputTagHelper.DisplayRequiredSymbol = TagHelper.RequiredSymbols ?? true; + return abpDateInputTagHelper; + } + + private AbpTagHelper GetAbpDateRangeInputTagHelper(TagHelperContext context, TagHelperOutput output, ModelExpression model) + { + var modelAttribute = model.ModelExplorer.GetAttribute(); + + var pickerId = modelAttribute.PickerId; + + var abpDateRangeInputTagHelper = _serviceProvider.GetRequiredService(); + abpDateRangeInputTagHelper.PickerId = pickerId; + abpDateRangeInputTagHelper.ViewContext = TagHelper.ViewContext; + + if (modelAttribute.IsStart) + { + abpDateRangeInputTagHelper.AspForStart = model; + + var otherModelExists = TryToGetOtherDateModel(model, pickerId, out var otherModel); + if (otherModelExists && otherModel.GetAttribute().IsEnd) + { + abpDateRangeInputTagHelper.AspForEnd = ModelExplorerToModelExpressionConverter(otherModel); + } + } + + return abpDateRangeInputTagHelper; + } + + private bool TryToGetOtherDateModel(ModelExpression model, string pickerId, out ModelExplorer otherModel) + { + otherModel = TagHelper.Model.ModelExplorer.Properties.SingleOrDefault(x => x != model.ModelExplorer && x.GetAttribute()?.PickerId == pickerId); + return otherModel != null; + } + + private static bool IsDateRangeGroup(ModelExplorer modelModelExplorer) + { + return modelModelExplorer.GetAttribute() != null; + } + protected virtual void RemoveFormGroupItemsNotInModel(TagHelperContext context, TagHelperOutput output, List items) { var models = GetModels(context, output); @@ -294,6 +357,25 @@ public class AbpDynamicFormTagHelperService : AbpTagHelperService() != null || + model.ModelExplorer.GetAttribute() != null) + { + return true; + } + + if (model.Metadata.ModelType == typeof(DateTime) || + model.Metadata.ModelType == typeof(DateTime?) || + model.Metadata.ModelType == typeof(DateTimeOffset) || + model.Metadata.ModelType == typeof(DateTimeOffset?)) + { + return true; + } + + return false; + } + protected virtual bool IsEnum(ModelExplorer explorer) { return explorer.Metadata.IsEnum; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index 0a3d1e1c08..a3cecfa471 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -294,8 +294,10 @@ public class AbpInputTagHelperService : AbpTagHelperService { return ""; } + + var isHaveRequiredAttribute = context.AllAttributes.Any(a => a.Name == "required"); - return TagHelper.AspFor.ModelExplorer.GetAttribute() != null ? " * " : ""; + return TagHelper.AspFor.ModelExplorer.GetAttribute() != null || isHaveRequiredAttribute ? " * " : ""; } protected virtual string GetInfoAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs index 58c7b1b45d..e6bac77fa3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs @@ -37,6 +37,8 @@ public class AbpSelectTagHelper : AbpTagHelper output.Attributes.Add("data-autocomplete-selected-item-value", TagHelper.AutocompleteSelectedItemValue); output.Attributes.Add("data-autocomplete-allow-clear", TagHelper.AllowClear); output.Attributes.Add("data-autocomplete-placeholder", TagHelper.Placeholder); + output.Attributes.Add("data-autocomplete-parent-selector", TagHelper.AutocompleteParentSelector); } } @@ -196,8 +197,10 @@ public class AbpSelectTagHelperService : AbpTagHelperService { return ""; } + + var isHaveRequiredAttribute = context.AllAttributes.Any(a => a.Name == "required"); - return TagHelper.AspFor.ModelExplorer.GetAttribute() != null ? " * " : ""; + return TagHelper.AspFor.ModelExplorer.GetAttribute() != null || isHaveRequiredAttribute ? " * " : ""; } protected virtual void AddInfoTextId(TagHelperOutput inputTagHelperOutput) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelper.cs new file mode 100644 index 0000000000..10923f25af --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelper.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public abstract class + AbpDatePickerBaseTagHelper : AbpTagHelper>, IAbpDatePickerOptions + where TTagHelper : AbpDatePickerBaseTagHelper + +{ + private readonly IAbpDatePickerOptions _abpDatePickerOptionsImplementation; + + public string Label { get; set; } + + public string LabelTooltip { get; set; } + + public string LabelTooltipIcon { get; set; } = "bi-info-circle"; + + public string LabelTooltipPlacement { get; set; } = "right"; + + public bool LabelTooltipHtml { get; set; } = false; + + [HtmlAttributeName("info")] + public string InfoText { get; set; } + + [HtmlAttributeName("disabled")] + public bool IsDisabled { get; set; } = false; + + [HtmlAttributeName("readonly")] + public bool? IsReadonly { get; set; } = false; + + public bool AutoFocus { get; set; } + + public AbpFormControlSize Size { get; set; } = AbpFormControlSize.Default; + + [HtmlAttributeName("required-symbol")] + public bool DisplayRequiredSymbol { get; set; } = true; + + public string Name { get; set; } + + public string Value { get; set; } + + public bool SuppressLabel { get; set; } + + protected AbpDatePickerBaseTagHelper(AbpDatePickerBaseTagHelperService service) : base(service) + { + _abpDatePickerOptionsImplementation = new AbpDatePickerOptions(); + } + + public string PickerId { + get => _abpDatePickerOptionsImplementation.PickerId; + set => _abpDatePickerOptionsImplementation.PickerId = value; + } + + public DateTime? MinDate { + get => _abpDatePickerOptionsImplementation.MinDate; + set => _abpDatePickerOptionsImplementation.MinDate = value; + } + + public DateTime? MaxDate { + get => _abpDatePickerOptionsImplementation.MaxDate; + set => _abpDatePickerOptionsImplementation.MaxDate = value; + } + + public object MaxSpan { + get => _abpDatePickerOptionsImplementation.MaxSpan; + set => _abpDatePickerOptionsImplementation.MaxSpan = value; + } + + public bool? ShowDropdowns { + get => _abpDatePickerOptionsImplementation.ShowDropdowns; + set => _abpDatePickerOptionsImplementation.ShowDropdowns = value; + } + + public int? MinYear { + get => _abpDatePickerOptionsImplementation.MinYear; + set => _abpDatePickerOptionsImplementation.MinYear = value; + } + + public int? MaxYear { + get => _abpDatePickerOptionsImplementation.MaxYear; + set => _abpDatePickerOptionsImplementation.MaxYear = value; + } + + public AbpDatePickerWeekNumbers WeekNumbers { + get => _abpDatePickerOptionsImplementation.WeekNumbers; + set => _abpDatePickerOptionsImplementation.WeekNumbers = value; + } + + public bool? TimePicker { + get => _abpDatePickerOptionsImplementation.TimePicker; + set => _abpDatePickerOptionsImplementation.TimePicker = value; + } + + public int? TimePickerIncrement { + get => _abpDatePickerOptionsImplementation.TimePickerIncrement; + set => _abpDatePickerOptionsImplementation.TimePickerIncrement = value; + } + + public bool? TimePicker24Hour { + get => _abpDatePickerOptionsImplementation.TimePicker24Hour; + set => _abpDatePickerOptionsImplementation.TimePicker24Hour = value; + } + + public bool? TimePickerSeconds { + get => _abpDatePickerOptionsImplementation.TimePickerSeconds; + set => _abpDatePickerOptionsImplementation.TimePickerSeconds = value; + } + + public List Ranges { + get => _abpDatePickerOptionsImplementation.Ranges; + set => _abpDatePickerOptionsImplementation.Ranges = value; + } + + public bool? ShowCustomRangeLabel { + get => _abpDatePickerOptionsImplementation.ShowCustomRangeLabel; + set => _abpDatePickerOptionsImplementation.ShowCustomRangeLabel = value; + } + + public bool? AlwaysShowCalendars { + get => _abpDatePickerOptionsImplementation.AlwaysShowCalendars; + set => _abpDatePickerOptionsImplementation.AlwaysShowCalendars = value; + } + + public AbpDatePickerOpens Opens { + get => _abpDatePickerOptionsImplementation.Opens; + set => _abpDatePickerOptionsImplementation.Opens = value; + } + + public AbpDatePickerDrops Drops { + get => _abpDatePickerOptionsImplementation.Drops; + set => _abpDatePickerOptionsImplementation.Drops = value; + } + + public string ButtonClasses { + get => _abpDatePickerOptionsImplementation.ButtonClasses; + set => _abpDatePickerOptionsImplementation.ButtonClasses = value; + } + + public string TodayButtonClasses { + get => _abpDatePickerOptionsImplementation.TodayButtonClasses; + set => _abpDatePickerOptionsImplementation.TodayButtonClasses = value; + } + + public string ApplyButtonClasses { + get => _abpDatePickerOptionsImplementation.ApplyButtonClasses; + set => _abpDatePickerOptionsImplementation.ApplyButtonClasses = value; + } + + public string ClearButtonClasses { + get => _abpDatePickerOptionsImplementation.ClearButtonClasses; + set => _abpDatePickerOptionsImplementation.ClearButtonClasses = value; + } + + public object Locale { + get => _abpDatePickerOptionsImplementation.Locale; + set => _abpDatePickerOptionsImplementation.Locale = value; + } + + public bool? AutoApply { + get => _abpDatePickerOptionsImplementation.AutoApply; + set => _abpDatePickerOptionsImplementation.AutoApply = value; + } + + public bool? LinkedCalendars { + get => _abpDatePickerOptionsImplementation.LinkedCalendars; + set => _abpDatePickerOptionsImplementation.LinkedCalendars = value; + } + + public bool? AutoUpdateInput { + get => _abpDatePickerOptionsImplementation.AutoUpdateInput; + set => _abpDatePickerOptionsImplementation.AutoUpdateInput = value; + } + + public string ParentEl { + get => _abpDatePickerOptionsImplementation.ParentEl; + set => _abpDatePickerOptionsImplementation.ParentEl = value; + } + + public string DateFormat { + get => _abpDatePickerOptionsImplementation.DateFormat; + set => _abpDatePickerOptionsImplementation.DateFormat = value; + } + + public bool OpenButton { + get => _abpDatePickerOptionsImplementation.OpenButton; + set => _abpDatePickerOptionsImplementation.OpenButton = value; + } + + public bool ClearButton { + get => _abpDatePickerOptionsImplementation.ClearButton; + set => _abpDatePickerOptionsImplementation.ClearButton = value; + } + + public bool SingleOpenAndClearButton { + get => _abpDatePickerOptionsImplementation.SingleOpenAndClearButton; + set => _abpDatePickerOptionsImplementation.SingleOpenAndClearButton = value; + } + + public bool? IsUtc { + get => _abpDatePickerOptionsImplementation.IsUtc; + set => _abpDatePickerOptionsImplementation.IsUtc = value; + } + + public bool? IsIso { + get => _abpDatePickerOptionsImplementation.IsIso; + set => _abpDatePickerOptionsImplementation.IsIso = value; + } + + public object Options { + get => _abpDatePickerOptionsImplementation.Options; + set => _abpDatePickerOptionsImplementation.Options = value; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs new file mode 100644 index 0000000000..82273097cf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs @@ -0,0 +1,712 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; +using Volo.Abp.Json; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelperService + where TTagHelper : AbpDatePickerBaseTagHelper +{ + protected readonly Dictionary> SupportedInputTypes = new() { + {typeof(string), o => DateTime.Parse((string)o).ToString("O")}, + {typeof(DateTime), o => ((DateTime) o).ToString("O")}, + {typeof(DateTime?), o => ((DateTime?) o)?.ToString("O")}, + {typeof(DateTimeOffset), o => ((DateTimeOffset) o).ToString("O")}, + {typeof(DateTimeOffset?), o => ((DateTimeOffset?) o)?.ToString("O")} + }; + + protected readonly IJsonSerializer JsonSerializer; + protected readonly IHtmlGenerator Generator; + protected readonly HtmlEncoder Encoder; + protected readonly IServiceProvider ServiceProvider; + protected readonly IAbpTagHelperLocalizer TagHelperLocalizer; + protected virtual string TagName { get; set; } = "abp-date-picker"; + protected IStringLocalizer L { get; } + protected InputTagHelper InputTagHelper { get; set; } + protected abstract TagHelperOutput TagHelperOutput { get; set; } + + protected AbpDatePickerBaseTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, + HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, + IAbpTagHelperLocalizer tagHelperLocalizer) + { + JsonSerializer = jsonSerializer; + Generator = generator; + Encoder = encoder; + ServiceProvider = serviceProvider; + L = l; + TagHelperLocalizer = tagHelperLocalizer; + + InputTagHelper = new InputTagHelper(Generator) { InputTypeName = "text" }; + } + + protected virtual T GetAttribute() where T : Attribute + { + return GetAttributeAndModelExpression(out _); + } + + protected abstract T GetAttributeAndModelExpression(out ModelExpression modelExpression) where T : Attribute; + + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + TagHelperOutput = new TagHelperOutput("input", GetInputAttributes(context, output), (_, _) => Task.FromResult(new DefaultTagHelperContent())); + + InputTagHelper.ViewContext = TagHelper.ViewContext; + + if (!TagHelper.Name.IsNullOrEmpty()) + { + InputTagHelper.Name = TagHelper.Name; + } + + if (!TagHelper.Value.IsNullOrEmpty()) + { + InputTagHelper.Value = TagHelper.Value; + } + + AddDisabledAttribute(TagHelperOutput); + AddAutoFocusAttribute(TagHelperOutput); + AddFormControls(context, output, TagHelperOutput); + AddReadOnlyAttribute(TagHelperOutput); + AddPlaceholderAttribute(TagHelperOutput); + AddInfoTextId(TagHelperOutput); + + // Open and close button + var openButtonContent = TagHelper.OpenButton + ? await ProcessButtonAndGetContentAsync(context, output, "calendar", "open") + : ""; + var clearButtonContent = TagHelper.ClearButton + ? await ProcessButtonAndGetContentAsync(context, output, "times", "clear", visible:!TagHelper.SingleOpenAndClearButton) + : ""; + + var labelContent = await GetLabelAsHtmlAsync(context, output, TagHelperOutput); + var infoContent = GetInfoAsHtml(context, output, TagHelperOutput); + var validationContent = await GetValidationAsHtmlAsync(context, output); + + var inputGroup = new TagHelperOutput("div", + new TagHelperAttributeList(new[] { new TagHelperAttribute("class", "input-group") }), + (_, _) => Task.FromResult(new DefaultTagHelperContent())); + inputGroup.Content.AppendHtml( + TagHelperOutput.Render(Encoder) + openButtonContent + clearButtonContent + ); + + var abpDatePickerTag = new TagHelperOutput(TagName, GetBaseTagAttributes(context, output, TagHelper), + (_, _) => Task.FromResult(new DefaultTagHelperContent())); + abpDatePickerTag.Content.AppendHtml(inputGroup.Render(Encoder)); + abpDatePickerTag.Content.AppendHtml(validationContent); + abpDatePickerTag.Content.AppendHtml(GetExtraInputHtml(context, output)); + + var innerHtml = labelContent + abpDatePickerTag.Render(Encoder) + infoContent; + + var order = GetOrder(); + + AddGroupToFormGroupContents( + context, + GetPropertyName(), + SurroundInnerHtmlAndGet(context, output, innerHtml), + order + ); + + + output.TagMode = TagMode.StartTagAndEndTag; + output.TagName = "div"; + LeaveOnlyGroupAttributes(context, output); + output.Attributes.AddClass("mb-3"); + + output.Content.AppendHtml(innerHtml); + } + + protected virtual void AddReadOnlyAttribute(TagHelperOutput inputTagHelperOutput) + { + if (inputTagHelperOutput.Attributes.ContainsName("readonly") == false && + (TagHelper.IsReadonly != false || GetAttribute() != null)) + { + inputTagHelperOutput.Attributes.Add("readonly", ""); + } + } + + protected virtual TagHelperAttributeList GetInputAttributes(TagHelperContext context, TagHelperOutput output) + { + var groupPrefix = "group-"; + + var tagHelperAttributes = output.Attributes.Where(a => !a.Name.StartsWith(groupPrefix)).ToList(); + + var attrList = new TagHelperAttributeList(); + + foreach (var tagHelperAttribute in tagHelperAttributes) + { + attrList.Add(tagHelperAttribute); + } + + attrList.Add("type", "text"); + + if (attrList.ContainsName("value")) + { + attrList.Remove(attrList.First(a => a.Name == "value")); + } + + if (!TagHelper.Name.IsNullOrEmpty() && !attrList.ContainsName("name")) + { + attrList.Add("name", TagHelper.Name); + } + + if (!attrList.ContainsName("autocomplete")) + { + attrList.Add("autocomplete", "off"); + } + + return attrList; + } + protected virtual void LeaveOnlyGroupAttributes(TagHelperContext context, TagHelperOutput output) + { + var groupPrefix = "group-"; + var tagHelperAttributes = output.Attributes.Where(a => a.Name.StartsWith(groupPrefix)).ToList(); + + output.Attributes.Clear(); + + foreach (var tagHelperAttribute in tagHelperAttributes) + { + var nameWithoutPrefix = tagHelperAttribute.Name.Substring(groupPrefix.Length); + var newAttribute = new TagHelperAttribute(nameWithoutPrefix, tagHelperAttribute.Value); + output.Attributes.Add(newAttribute); + } + } + + protected virtual string SurroundInnerHtmlAndGet(TagHelperContext context, TagHelperOutput output, string innerHtml) + { + return "
" + + Environment.NewLine + innerHtml + Environment.NewLine + + "
"; + } + + protected abstract string GetPropertyName(); + + protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, + int order) + { + var list = context.GetValue>(FormGroupContents) ?? new List(); + + if (!list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\""))) + { + list.Add(new FormGroupItem { HtmlContent = html, Order = order, PropertyName = propertyName }); + } + } + + protected abstract int GetOrder(); + protected abstract void AddBaseTagAttributes(TagHelperAttributeList attributes); + + protected virtual string GetExtraInputHtml(TagHelperContext context, TagHelperOutput output) + { + return string.Empty; + } + + protected TagHelperAttributeList ConvertDatePickerOptionsToAttributeList(IAbpDatePickerOptions options) + { + var attrList = new TagHelperAttributeList(); + + if(options == null) + { + return attrList; + } + + if (options.Locale != null) + { + attrList.Add("data-locale", JsonSerializer.Serialize(options.Locale)); + } + + if (options.MinDate != null) + { + attrList.Add("data-min-date", options.MinDate); + } + + if (options.MaxDate != null) + { + attrList.Add("data-max-date", options.MaxDate); + } + + if (options.MaxSpan != null) + { + attrList.Add("data-max-span", JsonSerializer.Serialize(options.MaxSpan)); + } + + if (options.ShowDropdowns == false) + { + attrList.Add("data-show-dropdowns", options.ShowDropdowns.ToString().ToLowerInvariant()); + } + + if (options.MinYear != null) + { + attrList.Add("data-min-year", options.MinYear); + } + + if (options.MaxYear != null) + { + attrList.Add("data-max-year", options.MaxYear); + } + + switch (options.WeekNumbers) + { + case AbpDatePickerWeekNumbers.Normal: + attrList.Add("data-show-week-numbers", "true"); + break; + case AbpDatePickerWeekNumbers.Iso: + attrList.Add("data-show-i-s-o-week-numbers", "true"); + break; + } + + if (options.TimePicker != null) + { + attrList.Add("data-time-picker", options.TimePicker.ToString().ToLowerInvariant()); + } + + if (options.TimePickerIncrement != null) + { + attrList.Add("data-time-picker-increment", options.TimePickerIncrement); + } + + if (options.TimePicker24Hour != null) + { + attrList.Add("data-time-picker24-hour", options.TimePicker24Hour.ToString().ToLowerInvariant()); + } + + if (options.TimePickerSeconds != null) + { + attrList.Add("data-time-picker-seconds", options.TimePickerSeconds.ToString().ToLowerInvariant()); + } + + if (options.Opens != AbpDatePickerOpens.Center) + { + attrList.Add("data-opens", options.Opens.ToString().ToLowerInvariant()); + } + + if (options.Drops != AbpDatePickerDrops.Down) + { + attrList.Add("data-drops", options.Drops.ToString().ToLowerInvariant()); + } + + if (!options.ButtonClasses.IsNullOrEmpty()) + { + attrList.Add("data-button-classes", options.ButtonClasses); + } + + if (!options.ApplyButtonClasses.IsNullOrEmpty()) + { + attrList.Add("data-apply-button-classes", options.ApplyButtonClasses); + } + + if (!options.ClearButtonClasses.IsNullOrEmpty()) + { + attrList.Add("data-clear-button-classes", options.ClearButtonClasses); + } + + if (!options.TodayButtonClasses.IsNullOrEmpty()) + { + attrList.Add("data-today-button-classes", options.TodayButtonClasses); + } + + if (options.AutoApply != null) + { + attrList.Add("data-auto-apply", options.AutoApply.ToString().ToLowerInvariant()); + } + + if (options.LinkedCalendars != null) + { + attrList.Add("data-linked-calendars", options.LinkedCalendars.ToString().ToLowerInvariant()); + } + + if (options.AutoUpdateInput != null) + { + attrList.Add("data-auto-update-input", options.AutoUpdateInput.ToString().ToLowerInvariant()); + } + + if (!options.ParentEl.IsNullOrEmpty()) + { + attrList.Add("data-parent-el", options.ParentEl); + } + + if (!options.DateFormat.IsNullOrEmpty()) + { + attrList.Add("data-date-format", options.DateFormat); + } + + if(options.Ranges != null && options.Ranges.Any()) + { + var ranges = options.Ranges.ToDictionary(r => r.Label, r => r.Dates); + + attrList.Add("data-ranges", JsonSerializer.Serialize(ranges)); + } + + if(options.AlwaysShowCalendars != null) + { + attrList.Add("data-always-show-calendars", options.AlwaysShowCalendars.ToString().ToLowerInvariant()); + } + + if(options.ShowCustomRangeLabel == false) + { + attrList.Add("data-show-custom-range-label", options.ShowCustomRangeLabel.ToString().ToLowerInvariant()); + } + + if(options.Options != null) + { + attrList.Add("data-options", JsonSerializer.Serialize(options.Options)); + } + + if (options.IsUtc != null) + { + attrList.Add("data-is-utc", options.IsUtc.ToString().ToLowerInvariant()); + } + + if (options.IsIso != null) + { + attrList.Add("data-is-iso", options.IsIso.ToString().ToLowerInvariant()); + } + + if (!options.PickerId.IsNullOrWhiteSpace()) + { + attrList.Add("id", options.PickerId); + } + + if(!options.SingleOpenAndClearButton) + { + attrList.Add("data-single-open-and-clear-button", options.SingleOpenAndClearButton.ToString().ToLowerInvariant()); + } + + return attrList; + } + + protected TagHelperAttributeList GetBaseTagAttributes(TagHelperContext context, TagHelperOutput output, IAbpDatePickerOptions options) + { + var groupPrefix = "group-"; + + var tagHelperAttributes = output.Attributes.Where(a => !a.Name.StartsWith(groupPrefix)).ToList(); + + var attrList = new TagHelperAttributeList(); + + foreach (var tagHelperAttribute in tagHelperAttributes) + { + attrList.Add(tagHelperAttribute); + } + + if (attrList.ContainsName("type")) + { + attrList.Remove(attrList.First(a => a.Name == "type")); + } + + if (attrList.ContainsName("name")) + { + attrList.Remove(attrList.First(a => a.Name == "name")); + } + + if (attrList.ContainsName("id")) + { + attrList.Remove(attrList.First(a => a.Name == "id")); + } + + if (attrList.ContainsName("value")) + { + attrList.Remove(attrList.First(a => a.Name == "value")); + } + + foreach (var attr in ConvertDatePickerOptionsToAttributeList(options)) + { + attrList.Add(attr); + } + + var optionsAttribute = GetAttributeAndModelExpression(out var modelExpression); + if (optionsAttribute != null) + { + foreach (var attr in ConvertDatePickerOptionsToAttributeList(optionsAttribute.GetDatePickerOptions(modelExpression.ModelExplorer))) + { + attrList.Add(attr); + } + } + + AddBaseTagAttributes(attrList); + + return attrList; + } + + protected virtual bool IsOutputHidden(TagHelperOutput inputTag) + { + return inputTag.Attributes.Any(a => + a.Name.ToLowerInvariant() == "type" && a.Value?.ToString()?.ToLowerInvariant() == "hidden"); + } + + protected virtual string GetInfoAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag) + { + if (IsOutputHidden(inputTag)) + { + return string.Empty; + } + + string text; + ModelExplorer modelExplorer = null; + + if (!string.IsNullOrEmpty(TagHelper.InfoText)) + { + text = TagHelper.InfoText; + } + else + { + var infoAttribute = GetAttributeAndModelExpression(out var modelExpression); + if (infoAttribute != null) + { + modelExplorer = modelExpression.ModelExplorer; + text = infoAttribute.Text; + } + else + { + return string.Empty; + } + } + + var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); + modelExplorer ??= GetModelExpression().ModelExplorer; + var localizedText = TagHelperLocalizer.GetLocalizedText(text, modelExplorer); + + var div = new TagBuilder("div"); + div.Attributes.Add("id", idAttr?.Value + "InfoText"); + div.AddCssClass("form-text"); + div.InnerHtml.Append(localizedText); + + inputTag.Attributes.Add("aria-describedby", idAttr?.Value + "InfoText"); + + return div.ToHtmlString(); + } + + protected virtual async Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, + TagHelperOutput inputTag) + { + if (IsOutputHidden(inputTag) || TagHelper.SuppressLabel) + { + return string.Empty; + } + if (string.IsNullOrEmpty(TagHelper.Label)) + { + return await GetLabelAsHtmlUsingTagHelperAsync(context, output) + GetRequiredSymbol(context, output); + } + + var label = new TagBuilder("label"); + label.Attributes.Add("for", GetIdAttributeValue(inputTag)); + label.InnerHtml.AppendHtml(TagHelper.Label); + + label.AddCssClass("form-label"); + + if (!TagHelper.LabelTooltip.IsNullOrEmpty()) + { + label.Attributes.Add("data-bs-toggle", "tooltip"); + label.Attributes.Add("data-bs-placement", TagHelper.LabelTooltipPlacement); + if (TagHelper.LabelTooltipHtml) + { + label.Attributes.Add("data-bs-html", "true"); + } + + label.Attributes.Add("title", TagHelper.LabelTooltip); + label.InnerHtml.AppendHtml($" "); + } + + return label.ToHtmlString(); + } + + protected virtual string GetIdAttributeValue(TagHelperOutput inputTag) + { + var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); + + return idAttr != null ? idAttr.Value.ToString() : string.Empty; + } + + protected virtual string GetRequiredSymbol(TagHelperContext context, TagHelperOutput output) + { + if (!TagHelper.DisplayRequiredSymbol) + { + return ""; + } + + var isHaveRequiredAttribute = context.AllAttributes.Any(a => a.Name == "required"); + + return GetAttribute() != null || isHaveRequiredAttribute ? " * " : ""; + } + + protected abstract ModelExpression GetModelExpression(); + + protected virtual async Task GetLabelAsHtmlUsingTagHelperAsync(TagHelperContext context, + TagHelperOutput output) + { + var modelExpression = GetModelExpression(); + if (modelExpression == null) + { + return string.Empty; + } + var labelTagHelper = new LabelTagHelper(Generator) { + ViewContext = TagHelper.ViewContext, + For = modelExpression + }; + + var attributeList = new TagHelperAttributeList(); + + attributeList.AddClass("form-label"); + + if (!TagHelper.LabelTooltip.IsNullOrEmpty()) + { + attributeList.Add("data-bs-toggle", "tooltip"); + attributeList.Add("data-bs-placement", TagHelper.LabelTooltipPlacement); + if (TagHelper.LabelTooltipHtml) + { + attributeList.Add("data-bs-html", "true"); + } + + attributeList.Add("title", TagHelper.LabelTooltip); + } + + var innerOutput = + await labelTagHelper.ProcessAndGetOutputAsync(attributeList, context, "label", TagMode.StartTagAndEndTag); + if (!TagHelper.LabelTooltip.IsNullOrEmpty()) + { + innerOutput.Content.AppendHtml($" "); + } + + return innerOutput.Render(Encoder); + } + + protected virtual async Task ProcessButtonAndGetContentAsync(TagHelperContext context, + TagHelperOutput output, string icon, string type, bool visible = true) + { + var abpButtonTagHelper = ServiceProvider.GetRequiredService(); + var attributes = + new TagHelperAttributeList { new("type", "button"), new("tabindex", "-1"), new("data-type", type) }; + abpButtonTagHelper.ButtonType = AbpButtonType.Outline_Secondary; + abpButtonTagHelper.Icon = icon; + + abpButtonTagHelper.Disabled = TagHelper.IsDisabled; + + if (!visible) + { + attributes.AddClass("d-none"); + } + + return await abpButtonTagHelper.RenderAsync(attributes, context, Encoder, "button", TagMode.StartTagAndEndTag); + } + + protected virtual void AddInfoTextId(TagHelperOutput inputTagHelperOutput) + { + if (GetAttribute() == null) + { + return; + } + + var idAttr = inputTagHelperOutput.Attributes.FirstOrDefault(a => a.Name == "id"); + + if (idAttr == null) + { + return; + } + + inputTagHelperOutput.Attributes.Add("aria-describedby", GetInfoText()); + } + + public virtual string GetInfoText() + { + var infoAttribute = GetAttributeAndModelExpression(out var modelExpression); + + if (infoAttribute != null) + { + return TagHelperLocalizer.GetLocalizedText(infoAttribute.Text, modelExpression.ModelExplorer); + } + + return string.Empty; + } + + protected virtual void AddPlaceholderAttribute(TagHelperOutput inputTagHelperOutput) + { + if (inputTagHelperOutput.Attributes.ContainsName("placeholder")) + { + return; + } + + var attribute = GetAttributeAndModelExpression(out var modelExpression); + + if (attribute != null) + { + var placeholderLocalized = + TagHelperLocalizer.GetLocalizedText(attribute.Value, modelExpression.ModelExplorer); + + inputTagHelperOutput.Attributes.Add("placeholder", placeholderLocalized); + } + } + + protected virtual void AddFormControls(TagHelperContext context, TagHelperOutput output, + TagHelperOutput inputTagHelperOutput) + { + inputTagHelperOutput.Attributes.AddClass("form-control"); + var size = GetSize(context, output); + if (!size.IsNullOrEmpty()) + { + inputTagHelperOutput.Attributes.AddClass(size); + } + } + + protected virtual void AddAutoFocusAttribute(TagHelperOutput inputTagHelperOutput) + { + if (TagHelper.AutoFocus && !inputTagHelperOutput.Attributes.ContainsName("data-auto-focus")) + { + inputTagHelperOutput.Attributes.Add("data-auto-focus", "true"); + } + } + + protected virtual void AddDisabledAttribute(TagHelperOutput inputTagHelperOutput) + { + if (inputTagHelperOutput.Attributes.ContainsName("disabled") == false && + (TagHelper.IsDisabled || GetAttribute() != null)) + { + inputTagHelperOutput.Attributes.Add("disabled", ""); + } + } + + + protected virtual string GetSize(TagHelperContext context, TagHelperOutput output) + { + // TODO: Test this method + var attribute = GetAttribute(); + + if (attribute != null) + { + TagHelper.Size = attribute.Size; + } + + return TagHelper.Size switch { + AbpFormControlSize.Small => "form-control-sm", + AbpFormControlSize.Medium => "form-control-md", + AbpFormControlSize.Large => "form-control-lg", + _ => "" + }; + } + + protected abstract Task GetValidationAsHtmlAsync(TagHelperContext context, TagHelperOutput output); + + protected virtual async Task GetValidationAsHtmlByInputAsync(TagHelperContext context, + TagHelperOutput output, + [NotNull]InputTagHelper inputTag) + { + var validationMessageTagHelper = + new ValidationMessageTagHelper(Generator) { For = inputTag.For, ViewContext = TagHelper.ViewContext }; + + var attributeList = new TagHelperAttributeList { { "class", "text-danger col-auto" } }; + + return await validationMessageTagHelper.RenderAsync(attributeList, context, Encoder, "span", + TagMode.StartTagAndEndTag); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerDrops.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerDrops.cs new file mode 100644 index 0000000000..c61cbfec78 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerDrops.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public enum AbpDatePickerDrops : byte +{ + Down = 1, + Up = 2, + Auto = 3 +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerOpens.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerOpens.cs new file mode 100644 index 0000000000..a74bf229ef --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerOpens.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public enum AbpDatePickerOpens : byte +{ + Left = 1, + Right = 2, + Center = 3, +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerOptions.cs new file mode 100644 index 0000000000..15b4e2003d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerOptions.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public class AbpDatePickerOptions : IAbpDatePickerOptions +{ + public string PickerId { get; set; } + public DateTime? MinDate { get; set; } + public DateTime? MaxDate { get; set; } + public object MaxSpan { get; set; } + public bool? ShowDropdowns { get; set; } + public int? MinYear { get; set; } + public int? MaxYear { get; set; } + public AbpDatePickerWeekNumbers WeekNumbers { get; set; } = AbpDatePickerWeekNumbers.None; + public bool? TimePicker { get; set; } + public int? TimePickerIncrement { get; set; } + public bool? TimePicker24Hour { get; set; } + public bool? TimePickerSeconds { get; set; } + public List Ranges { get; set; } + public bool? ShowCustomRangeLabel { get; set; } + public bool? AlwaysShowCalendars { get; set; } + public AbpDatePickerOpens Opens { get; set; } = AbpDatePickerOpens.Center; + public AbpDatePickerDrops Drops { get; set; } = AbpDatePickerDrops.Down; + public string ButtonClasses { get; set; } + public string TodayButtonClasses { get; set; } + public string ApplyButtonClasses { get; set; } + public string ClearButtonClasses { get; set; } + public object Locale { get; set; } + public bool? AutoApply { get; set; } + public bool? LinkedCalendars { get; set; } + public bool? AutoUpdateInput { get; set; } + public string ParentEl { get; set; } + public string DateFormat { get; set; } + public bool OpenButton { get; set; } = true; + public bool ClearButton { get; set; } = true; + public bool SingleOpenAndClearButton { get; set; } = true; + public bool? IsUtc { get; set; } + public bool? IsIso { get; set; } + public object Options { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerRange.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerRange.cs new file mode 100644 index 0000000000..eea3d2ad31 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerRange.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public class AbpDatePickerRange +{ + private readonly List _dates = new List(); + public string Label { get; set; } + public IReadOnlyList Dates => _dates; + + public AbpDatePickerRange() + { + + } + public AbpDatePickerRange(string label, DateTime start, DateTime end) + { + Label = label; + AddDate(start); + AddDate(end); + } + + public AbpDatePickerRange(string label, DateTime date) + { + Label = label; + AddDate(date); + } + + public AbpDatePickerRange(string label, DateTimeOffset start, DateTimeOffset end) + { + Label = label; + AddDate(start); + AddDate(end); + } + + public AbpDatePickerRange(string label, DateTimeOffset date) + { + Label = label; + AddDate(date); + } + + public AbpDatePickerRange(string label, string start, string end) + { + Label = label; + AddDate(start); + AddDate(end); + } + + public AbpDatePickerRange(string label, string date) + { + Label = label; + AddDate(date); + } + + public void AddDate(string date) + { + _dates.Add(DateTime.Parse(date).ToString("O")); + } + + public void AddDate(DateTime date) + { + _dates.Add(date.ToString("O")); + } + + public void AddDate(DateTimeOffset date) + { + _dates.Add(date.ToString("O")); + } + + public void AddDate(DateTime? date) + { + if (date.HasValue) + { + _dates.Add(date.Value.ToString("O")); + } + } + + public void AddDate(DateTimeOffset? date) + { + if (date.HasValue) + { + _dates.Add(date.Value.ToString("O")); + } + } + + public void AddDate(string date, string format) + { + _dates.Add(DateTime.ParseExact(date, format, null).ToString("O")); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelper.cs new file mode 100644 index 0000000000..8426b0071b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelper.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +[HtmlTargetElement("abp-date-picker", TagStructure = TagStructure.NormalOrSelfClosing)] +public class AbpDatePickerTagHelper : AbpDatePickerBaseTagHelper +{ + [CanBeNull] + public ModelExpression AspFor { get; set; } + + public AbpDatePickerTagHelper(AbpDatePickerTagHelperService service) : base(service) + { + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs new file mode 100644 index 0000000000..e5eae85c53 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs @@ -0,0 +1,88 @@ +using System; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; +using Volo.Abp.Json; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService +{ + public AbpDatePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, IAbpTagHelperLocalizer tagHelperLocalizer) : base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer) + { + + } + + protected override TagHelperOutput TagHelperOutput { get; set; } + + [CanBeNull] + protected virtual InputTagHelper DateTagHelper { get; set; } + + [CanBeNull] + protected virtual TagHelperOutput DateTagHelperOutput { get; set; } + protected override string GetPropertyName() + { + return TagHelper.AspFor?.Name ?? string.Empty; + } + + protected override T GetAttributeAndModelExpression(out ModelExpression modelExpression) + { + modelExpression = TagHelper.AspFor; + return modelExpression?.ModelExplorer.GetAttribute(); + } + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (TagHelper.AspFor != null) + { + DateTagHelper = new InputTagHelper(Generator) + { + InputTypeName = "hidden", + ViewContext = TagHelper.ViewContext, + For = TagHelper.AspFor, + }; + + var attributes = new TagHelperAttributeList { { "data-date", "true" }, { "type", "hidden" } }; + DateTagHelperOutput = await DateTagHelper.ProcessAndGetOutputAsync(attributes, context, "input"); + } + + await base.ProcessAsync(context, output); + } + + + protected override int GetOrder() + { + return TagHelper.AspFor?.Metadata.Order ?? 0; + } + + protected override void AddBaseTagAttributes(TagHelperAttributeList attributes) + { + if (TagHelper.AspFor != null && + TagHelper.AspFor.Model != null && + SupportedInputTypes.TryGetValue(TagHelper.AspFor.Metadata.ModelType, out var convertFunc)) + { + attributes.Add("data-date", convertFunc(TagHelper.AspFor.Model)); + } + } + + protected override ModelExpression GetModelExpression() + { + return TagHelper.AspFor; + } + + protected async override Task GetValidationAsHtmlAsync(TagHelperContext context, TagHelperOutput output) + { + return DateTagHelper != null ? await GetValidationAsHtmlByInputAsync(context, output, DateTagHelper) : string.Empty; + } + + protected override string GetExtraInputHtml(TagHelperContext context, TagHelperOutput output) + { + return DateTagHelperOutput?.Render(Encoder); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerWeekNumbers.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerWeekNumbers.cs new file mode 100644 index 0000000000..b542fcec4d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerWeekNumbers.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public enum AbpDatePickerWeekNumbers : byte +{ + None = 0, + Normal = 1, + Iso = 2 +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelper.cs new file mode 100644 index 0000000000..bfd1e354f6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelper.cs @@ -0,0 +1,20 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +[HtmlTargetElement("abp-date-range-picker", TagStructure = TagStructure.NormalOrSelfClosing)] +public class AbpDateRangePickerTagHelper : AbpDatePickerBaseTagHelper +{ + [CanBeNull] + public ModelExpression AspForStart { get; set; } + + [CanBeNull] + public ModelExpression AspForEnd { get; set; } + + public AbpDateRangePickerTagHelper(AbpDateRangePickerTagHelperService tagHelperService) : + base(tagHelperService) + { + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs new file mode 100644 index 0000000000..45a083b789 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs @@ -0,0 +1,132 @@ +using System; +using System.Linq; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; +using Volo.Abp.Json; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperService +{ + public AbpDateRangePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, + HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, + IAbpTagHelperLocalizer tagHelperLocalizer) : + base(jsonSerializer, generator, encoder, serviceProvider, l, + tagHelperLocalizer) + { + } + + protected override string TagName { get; set; } = "abp-date-range-picker"; + + protected override T GetAttributeAndModelExpression(out ModelExpression modelExpression) + { + modelExpression = new[] { TagHelper.AspForStart, TagHelper.AspForEnd }.FirstOrDefault(x => x?.ModelExplorer?.GetAttribute() != null); + return modelExpression?.ModelExplorer.GetAttribute(); + } + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (TagHelper.AspForStart != null) + { + var startDateAttributes = new TagHelperAttributeList { { "data-start-date", "true" }, { "type", "hidden" } }; + StartDateTagHelper = new InputTagHelper(Generator) + { + ViewContext = TagHelper.ViewContext, + For = TagHelper.AspForStart, + InputTypeName = "hidden" + }; + + StartDateTagHelperOutput = await StartDateTagHelper.ProcessAndGetOutputAsync(startDateAttributes, context, "input"); + } + + if (TagHelper.AspForEnd != null) + { + var endDateAttributes = new TagHelperAttributeList { { "data-end-date", "true" }, { "type", "hidden" } }; + EndDateTagHelper = new InputTagHelper(Generator) + { + ViewContext = TagHelper.ViewContext, + For = TagHelper.AspForEnd, + InputTypeName = "hidden" + }; + + EndDateTagHelperOutput = await EndDateTagHelper.ProcessAndGetOutputAsync(endDateAttributes, context, "input"); + } + + await base.ProcessAsync(context, output); + } + + protected override TagHelperOutput TagHelperOutput { get; set; } + + [CanBeNull] + protected virtual InputTagHelper StartDateTagHelper { get; set; } + + [CanBeNull] + protected virtual TagHelperOutput StartDateTagHelperOutput { get; set; } + + [CanBeNull] + protected virtual InputTagHelper EndDateTagHelper { get; set; } + + [CanBeNull] + protected virtual TagHelperOutput EndDateTagHelperOutput { get; set; } + + protected override string GetPropertyName() + { + return TagHelper.AspForStart?.Name ?? string.Empty; + } + + protected override int GetOrder() + { + return TagHelper.Order; + } + + protected override void AddBaseTagAttributes(TagHelperAttributeList attributes) + { + if (TagHelper.AspForStart != null && + TagHelper.AspForStart.Model != null && + SupportedInputTypes.TryGetValue(TagHelper.AspForStart.Metadata.ModelType, out var convertFuncStart)) + { + attributes.Add("data-start-date", convertFuncStart(TagHelper.AspForStart.Model)); + } + + if (TagHelper.AspForEnd != null && + TagHelper.AspForEnd.Model != null && + SupportedInputTypes.TryGetValue(TagHelper.AspForEnd.Metadata.ModelType, out var convertFuncEnd)) + { + attributes.Add("data-end-date", convertFuncEnd(TagHelper.AspForEnd.Model)); + } + } + + protected override string GetExtraInputHtml(TagHelperContext context, TagHelperOutput output) + { + return StartDateTagHelperOutput?.Render(Encoder) + EndDateTagHelperOutput?.Render(Encoder); + } + + protected override ModelExpression GetModelExpression() + { + return TagHelper.AspForStart; + } + + protected async override Task GetValidationAsHtmlAsync(TagHelperContext context, TagHelperOutput output) + { + var validationHtml = string.Empty; + + if (StartDateTagHelper != null) + { + validationHtml += await GetValidationAsHtmlByInputAsync(context, output, StartDateTagHelper); + } + + if (EndDateTagHelper != null) + { + validationHtml += await GetValidationAsHtmlByInputAsync(context, output, EndDateTagHelper); + } + + return validationHtml; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DatePickerAttribute.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DatePickerAttribute.cs new file mode 100644 index 0000000000..840e21241e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DatePickerAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +[AttributeUsage(AttributeTargets.Property)] +public class DatePickerAttribute : Attribute +{ +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DatePickerOptionsAttribute.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DatePickerOptionsAttribute.cs new file mode 100644 index 0000000000..9cff72aa23 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DatePickerOptionsAttribute.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +[AttributeUsage(AttributeTargets.Property)] +public class DatePickerOptionsAttribute : Attribute +{ + public string DatePickerOptionsPropertyName { get; set; } + + public DatePickerOptionsAttribute(string datePickerOptionsPropertyName) + { + DatePickerOptionsPropertyName = datePickerOptionsPropertyName; + } + + public IAbpDatePickerOptions GetDatePickerOptions(ModelExplorer explorer) + { + var properties = explorer.Container.Properties.Where(p => p.Metadata.PropertyName != null && p.Metadata.PropertyName.Equals(DatePickerOptionsPropertyName)).ToList(); + + while (properties.Count == 0) + { + explorer = explorer.Container; + if(explorer.Container == null) + { + return null; + } + + properties = explorer.Container.Properties.Where(p => p.Metadata.PropertyName != null && p.Metadata.PropertyName.Equals(DatePickerOptionsPropertyName)).ToList(); + } + + return properties.FirstOrDefault(p=> p.Model is IAbpDatePickerOptions)?.Model as IAbpDatePickerOptions; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DateRangePickerAttribute.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DateRangePickerAttribute.cs new file mode 100644 index 0000000000..04412ed4fc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/DateRangePickerAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +[AttributeUsage(AttributeTargets.Property)] +public class DateRangePickerAttribute : Attribute +{ + public string PickerId { get; set; } + + public bool IsStart { get; set; } + + public bool IsEnd => !IsStart; + + public DateRangePickerAttribute(string pickerId, bool isStart = false) + { + PickerId = pickerId; + IsStart = isStart; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/IAbpDatePickerOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/IAbpDatePickerOptions.cs new file mode 100644 index 0000000000..118c409da9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/IAbpDatePickerOptions.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; + +public interface IAbpDatePickerOptions +{ + public string PickerId { get; set; } + + /// + /// Min date allowed + /// + DateTime? MinDate { get; set; } + + /// + /// Max date allowed + /// + DateTime? MaxDate { get; set; } + + /// + /// The maximum span between the selected start and end dates. Can have any property you can add to a moment object (i.e. days, months) + /// + object MaxSpan { get; set; } + + /// + /// Show year and month select boxes above calendars to jump to a specific month and year. + /// + bool? ShowDropdowns { get; set; } + + /// + /// The minimum year shown in the dropdowns when showDropdowns is set to true. + /// + int? MinYear { get; set; } + + /// + /// The maximum year shown in the dropdowns when showDropdowns is set to true. + /// + int? MaxYear { get; set; } + + /// + /// Show week numbers at the start of each week on the calendars. + /// + AbpDatePickerWeekNumbers WeekNumbers { get; set; } + + /// + /// Adds select boxes to choose times in addition to dates. + /// + bool? TimePicker { get; set; } + + /// + /// Increment of the minutes selection list for times (i.e. 30 to allow only selection of times ending in 0 or 30). + /// + int? TimePickerIncrement { get; set; } + + /// + /// Use 24-hour instead of 12-hour times, removing the AM/PM selection. + /// + bool? TimePicker24Hour { get; set; } + + /// + /// Show seconds in the timePicker. + /// + bool? TimePickerSeconds { get; set; } + + /// + /// Set predefined date ranges the user can select from. Each key is the label for the range, and its value an array with two dates representing the bounds of the range. + /// + List Ranges { get; set; } + + /// + /// Displays "Custom Range" at the end of the list of predefined ranges, when the ranges option is used. This option will be highlighted whenever the current date range selection does not match one of the predefined ranges. Clicking it will display the calendars to select a new range. + /// + bool? ShowCustomRangeLabel { get; set; } + + /// + /// Normally, if you use the ranges option to specify pre-defined date ranges, calendars for choosing a custom date range are not shown until the user clicks "Custom Range". When this option is set to true, the calendars for choosing a custom date range are always shown instead. + /// + bool? AlwaysShowCalendars { get; set; } + + /// + /// Whether the picker appears aligned to the left, to the right, or centered under the HTML element it's attached to. + /// + AbpDatePickerOpens Opens { get; set; } + + /// + /// Whether the picker appears below (default) or above the HTML element it's attached to. + /// + AbpDatePickerDrops Drops { get; set; } + + /// + /// CSS class names that will be added to both the today, clear, and apply buttons. + /// + [CanBeNull] + string ButtonClasses { get; set; } + + /// + /// CSS class names that will be added only to the today button. + /// + [CanBeNull] + string TodayButtonClasses { get; set; } + + /// + /// CSS class names that will be added only to the apply button. + /// + [CanBeNull] + string ApplyButtonClasses { get; set; } + + /// + /// CSS class names that will be added only to the clear button. + /// + [CanBeNull] + string ClearButtonClasses { get; set; } + + /// + /// Allows you to provide localized strings for buttons and labels, customize the date format, and change the first day of week for the calendars. + /// + [CanBeNull] + object Locale { get; set; } + + /// + /// Hide the apply button, and automatically apply a new date range as soon as two dates are clicked. + /// + bool? AutoApply { get; set; } + + /// + /// When enabled, the two calendars displayed will always be for two sequential months (i.e. January and February), and both will be advanced when clicking the left or right arrows above the calendars. When disabled, the two calendars can be individually advanced and display any month/year. + /// + bool? LinkedCalendars { get; set; } + + /// + /// Indicates whether the date range picker should automatically update the value of the input element it's attached to at initialization and when the selected dates change. + /// + bool? AutoUpdateInput { get; set; } + + /// + /// jQuery selector of the parent element that the date range picker will be added to, if not provided this will be 'body' + /// + [CanBeNull] + string ParentEl { get; set; } + + [CanBeNull] + string DateFormat { get; set; } + + bool OpenButton { get; set; } + + bool ClearButton { get; set; } + + bool SingleOpenAndClearButton { get; set; } + + bool? IsUtc { get; set; } + + bool? IsIso { get; set; } + + /// + /// Other non-mapped attributes will be automatically added to the input element as is. + /// + [CanBeNull] + object Options { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json index 0547630188..70668c8dc9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json @@ -4,7 +4,7 @@ "GivenTenantIsNotExist": "Annettua vuokralaista ei ole olemassa: {0}", "GivenTenantIsNotAvailable": "Annettua vuokralaista ei ole saatavilla: {0}", "Tenant": "Vuokralainen", - "Switch": "vaihtaa", + "Switch": "Vaihda", "Name": "Nimi", "SwitchTenantHint": "Jätä nimikenttä tyhjäksi, jos haluat vaihtaa isäntäpuolelle.", "SwitchTenant": "Vaihda vuokralainen", diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs index 3181d8e1a2..9e5754f393 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs @@ -7,6 +7,13 @@ public class FlagIconCssStyleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) { - context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icons.min.css"); + if (context.FileProvider.GetFileInfo("/libs/flag-icons/css/flag-icons.min.css").Exists) + { + context.Files.AddIfNotContains("/libs/flag-icons/css/flag-icons.min.css"); + } + else if (context.FileProvider.GetFileInfo("/libs/flag-icon-css/css/flag-icons.min.css").Exists) + { + context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icons.min.css"); + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs index 4818158a97..b18767050b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs @@ -1,16 +1,28 @@ using System.Collections.Generic; +using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Core; using Volo.Abp.Modularity; +using Volo.Abp.Localization; namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; [DependsOn(typeof(CoreScriptContributor))] public class Select2ScriptContributor : BundleContributor { + public const string PackageName = "select2"; public override void ConfigureBundle(BundleConfigurationContext context) { - //TODO: Add select2.full.min.js or localize! + //TODO: Add select2.full.min.js context.Files.AddIfNotContains("/libs/select2/js/select2.min.js"); } + public override void ConfigureDynamicResources(BundleConfigurationContext context) + { + var fileName = context.LazyServiceProvider.LazyGetRequiredService>().Value.GetCurrentUICultureLanguageFilesMap(PackageName); + var filePath = $"/libs/select2/js/i18n/{fileName}.js"; + if (context.FileProvider.GetFileInfo(filePath).Exists) + { + context.Files.AddIfNotContains(filePath); + } + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs index 70780d642c..e38995a685 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs @@ -14,12 +14,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers; public class ErrorController : AbpController { - private readonly IExceptionToErrorInfoConverter _errorInfoConverter; - private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; - private readonly IStringLocalizer _localizer; - private readonly AbpErrorPageOptions _abpErrorPageOptions; - private readonly IExceptionNotifier _exceptionNotifier; - private readonly AbpExceptionHandlingOptions _exceptionHandlingOptions; + protected readonly IExceptionToErrorInfoConverter ErrorInfoConverter; + protected readonly IHttpExceptionStatusCodeFinder StatusCodeFinder; + protected readonly IStringLocalizer Localizer; + protected readonly AbpErrorPageOptions AbpErrorPageOptions; + protected readonly IExceptionNotifier ExceptionNotifier; + protected readonly AbpExceptionHandlingOptions ExceptionHandlingOptions; public ErrorController( IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, @@ -29,33 +29,33 @@ public class ErrorController : AbpController IExceptionNotifier exceptionNotifier, IOptions exceptionHandlingOptions) { - _errorInfoConverter = exceptionToErrorInfoConverter; - _statusCodeFinder = httpExceptionStatusCodeFinder; - _localizer = localizer; - _exceptionNotifier = exceptionNotifier; - _exceptionHandlingOptions = exceptionHandlingOptions.Value; - _abpErrorPageOptions = abpErrorPageOptions.Value; + ErrorInfoConverter = exceptionToErrorInfoConverter; + StatusCodeFinder = httpExceptionStatusCodeFinder; + Localizer = localizer; + ExceptionNotifier = exceptionNotifier; + ExceptionHandlingOptions = exceptionHandlingOptions.Value; + AbpErrorPageOptions = abpErrorPageOptions.Value; } - public async Task Index(int httpStatusCode) + public virtual async Task Index(int httpStatusCode) { var exHandlerFeature = HttpContext.Features.Get(); var exception = exHandlerFeature != null ? exHandlerFeature.Error - : new Exception(_localizer["UnhandledException"]); + : new Exception(Localizer["UnhandledException"]); - await _exceptionNotifier.NotifyAsync(new ExceptionNotificationContext(exception)); + await ExceptionNotifier.NotifyAsync(new ExceptionNotificationContext(exception)); - var errorInfo = _errorInfoConverter.Convert(exception, options => + var errorInfo = ErrorInfoConverter.Convert(exception, options => { - options.SendExceptionsDetailsToClients = _exceptionHandlingOptions.SendExceptionsDetailsToClients; - options.SendStackTraceToClients = _exceptionHandlingOptions.SendStackTraceToClients; + options.SendExceptionsDetailsToClients = ExceptionHandlingOptions.SendExceptionsDetailsToClients; + options.SendStackTraceToClients = ExceptionHandlingOptions.SendStackTraceToClients; }); if (httpStatusCode == 0) { - httpStatusCode = (int)_statusCodeFinder.GetStatusCode(HttpContext, exception); + httpStatusCode = (int)StatusCodeFinder.GetStatusCode(HttpContext, exception); } HttpContext.Response.StatusCode = httpStatusCode; @@ -69,9 +69,9 @@ public class ErrorController : AbpController }); } - private string GetErrorPageUrl(int statusCode) + protected virtual string GetErrorPageUrl(int statusCode) { - var page = _abpErrorPageOptions.ErrorViewUrls.GetOrDefault(statusCode.ToString()); + var page = AbpErrorPageOptions.ErrorViewUrls.GetOrDefault(statusCode.ToString()); if (string.IsNullOrWhiteSpace(page)) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js index 7f469e9179..945df5d0c6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js @@ -108,6 +108,7 @@ $select.select2({ ajax: { url: url, + delay: 250, dataType: "json", data: function (params) { let query = {}; @@ -135,6 +136,7 @@ width: '100%', dropdownParent: parentSelector ? $(parentSelector) : $('body'), allowClear: allowClear, + language: abp.localization.currentCulture.cultureName, placeholder: { id: '-1', text: placeholder @@ -191,6 +193,567 @@ }); } + abp.libs.bootstrapDateRangePicker = { + packageName: "bootstrap-daterangepicker", + + createDateRangePicker: function (options) { + options = options || {}; + options.singleDatePicker = false; + return this.createDatePicker(options); + }, + createSinglePicker: function (options) { + options = options || {}; + options.singleDatePicker = true; + return this.createDatePicker(options); + }, + createDatePicker: function (options) { + var $container = $('
') + var label = $('') + if (options.label) { + label.text(options.label) + } + + $container.append(label) + var $datePicker = options.singleDatePicker ? $('') : $(''); + $container.append($datePicker) + + var $inputGroup = $('
'); + var $dateInput = $(''); + + if (options.placeholder) { + $dateInput.attr('placeholder', options.placeholder) + } + + if (options.value) { + $dateInput.val(options.value) + } + + if (options.name) { + $dateInput.attr('name', options.name) + } + + if (options.id) { + $dateInput.attr('id', options.id) + } + + if (options.required) { + $dateInput.attr('required', true) + } + + if (options.disabled) { + $dateInput.attr('disabled', true) + } + + if (options.readonly) { + $dateInput.attr('readonly', true) + } + + if(options.placeholder) { + $dateInput.attr('placeholder', options.placeholder) + } + + if (options.size) { + switch (options.size) { + case 'Small': + $dateInput.addClass('form-control-sm') + break; + case 'Medium': + $dateInput.addClass('form-control-md') + break; + case 'Large': + $dateInput.addClass('form-control-lg') + break; + default: + break; + } + } + + $inputGroup.append($dateInput); + + if (options.openButton !== false) { + var $openButton = $(''); + $inputGroup.append($openButton); + if(options.disabled) { + $openButton.attr('disabled', 'disabled') + } + } + + if (options.clearButton !== false) { + var $clearButton = $(''); + $inputGroup.append($clearButton); + if(options.disabled) { + $clearButton.attr('disabled', 'disabled') + } + } + + $datePicker.append($inputGroup); + + if (options.startDateName) { + var $hiddenStartDateElement = $(''); + $datePicker.append($hiddenStartDateElement); + } + + if (options.endDateName) { + var $hiddenEndDateElement = $(''); + $datePicker.append($hiddenEndDateElement); + } + + if (options.dateName) { + var $hiddenDateElement = $(''); + $datePicker.append($hiddenDateElement); + } + + $datePicker.data('options', options); + abp.dom.initializers.initializeDateRangePickers($datePicker); + $container[0].datePicker = $datePicker[0]; + return $container; + } + }; + + + abp.dom.initializers.initializeDateRangePickers = function ($rootElement) { + function setOptions (options, $datePickerElement, singleDatePicker) { + + options.singleDatePicker = singleDatePicker; + + var defaultOptions = { + showDropdowns: true, + opens: "center", + drops: "down", + autoApply: true, + autoUpdateInput: false, + showTodayButton: true, + showClearButton: true, + minYear: parseInt(moment().subtract(100, 'year').locale('en').format('YYYY')), + maxYear: parseInt(moment().add(100, 'year').locale('en').format('YYYY')), + locale: { + direction: abp.localization.currentCulture.isRightToLeft ? 'rtl' : 'ltr', + todayLabel: abp.localization.localize('Today', 'AbpUi'), + clearLabel: abp.localization.localize('Clear', 'AbpUi'), + applyLabel: abp.localization.localize('Apply', 'AbpUi'), + }, + singleOpenAndClearButton: true + }; + var locale = defaultOptions.locale; + $.extend(options, defaultOptions); + $.extend(options, $datePickerElement.data()); + if ($.isEmptyObject(options.locale)) { + options.locale = locale; + } else { + locale = options.locale; + } + + $.extend(options, $datePickerElement.data("options")); + if ($.isEmptyObject(options.locale)) { + options.locale = locale; + } + + if (options.timePicker && options.timePicker24Hour === undefined) { + options.timePicker24Hour = moment.localeData().longDateFormat('LT').indexOf('A') < 1; + } + + if (options.dateFormat) { + options.locale = options.locale || {}; + options.locale.format = options.dateFormat; + } + + if (options.separator) { + options.locale = options.locale || {}; + options.locale.separator = options.separator; + } + + if (options.ranges) { + $.each(options.ranges, function (key, value) { + let start = value[0]; + let end; + if (value.length > 1) { + end = value[1]; + } else { + end = value[0]; + } + options.ranges[key] = [getMoment(start, options), getMoment(end, options)]; + }); + } + + if (typeof options.template !== 'string' && !(options.template instanceof $)) + options.template = + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + ' ' + + '
' + + '
'; + } + + function replaceOpenButton (hasDate,singleOpenAndClearButton, $openButton, $clearButton) { + var hiddenClass = 'd-none'; + + if (singleOpenAndClearButton) { + if (hasDate) { + $openButton.addClass(hiddenClass); + $clearButton.removeClass(hiddenClass); + $clearButton.insertAfter($openButton); + } else { + $openButton.removeClass(hiddenClass); + $clearButton.addClass(hiddenClass); + $openButton.insertAfter($clearButton); + } + } + } + + function getMoment (date, options, dateFormat) { + var isUtc = options.isUtc; + dateFormat = dateFormat || options.dateFormat; + if (!date) { + return isUtc ? moment.utc() : moment(); + } + + if (isUtc) { + return moment.utc(date, dateFormat); + } else { + return moment(date, dateFormat); + } + } + + function getStartDate(options, startDate){ + startDate = startDate ? startDate : options.startDate; + startDate = startDate ? getMoment(startDate, options) : null; + if (options.singleDatePicker && !startDate) { + startDate = options.date ? getMoment(options.date, options) : null; + } + + if(startDate && startDate.isValid()){ + return startDate; + } + + return null; + } + + function getEndDate(options, endDate){ + if (options.singleDatePicker) { + return null; + } + endDate = endDate ? endDate : options.endDate; + endDate = endDate ? getMoment(endDate, options) : null; + + if(endDate && endDate.isValid()){ + return endDate; + } + + return null; + } + + function getTodayButton(options, $input){ + if (options.showTodayButton) { + var $todayBtn = $(''); + if(options.todayButtonClasses){ + $todayBtn.addClass(options.todayButtonClasses); + }else{ + $todayBtn.addClass('btn-default'); + } + + $todayBtn.on('click', function () { + var today = getMoment(undefined, options); + $input.data('daterangepicker').setStartDate(today); + $input.data('daterangepicker').setEndDate(today); + $input.data('daterangepicker').clickApply(); + }); + + return $todayBtn; + } + + return null; + } + + function getClearButton(options, $input, $dateRangePicker){ + if (options.showClearButton) { + var $clearBtn = $(''); + if(options.clearButtonClasses){ + $clearBtn.addClass(options.clearButtonClasses); + }else{ + $clearBtn.addClass('btn-default'); + } + $clearBtn.on('click', function () { + $input.val('').trigger('change'); + $dateRangePicker.hide(); + }); + + return $clearBtn; + } + return null; + } + + function addExtraButtons(options, $dateRangePicker, $input) { + var extraButtons = [getTodayButton(options, $input), getClearButton(options, $input, $dateRangePicker)]; + + if ($dateRangePicker.container.hasClass('auto-apply')) { + var $div = $('
'); + $div.css('display', 'block'); + $.each(extraButtons, function (index, value) { + $div.prepend(value); + }); + + $div.insertAfter($dateRangePicker.container.find('.drp-buttons')); + } else { + $.each(extraButtons, function (index, value) { + $dateRangePicker.container.find('.drp-buttons').prepend(value); + }); + } + } + + function addOpenButtonClick($openButton, $dateRangePicker){ + if(!$openButton){ + return; + } + $dateRangePicker.outsideClick = function (e) { + var target = $(e.target); + // if the page is clicked anywhere except within the daterangerpicker/button itself then call this.hide() + if ( + // ie modal dialog fix + e.type == "focusin" || + target.closest(this.element).length || + target.closest(this.container).length || + target.closest('.calendar-table').length || + target.closest($openButton).length + ) { + return; + } + + this.hide(); + this.element.trigger('outsideClick.daterangepicker', this); + }; + + $openButton.on('click', function () { + $dateRangePicker.toggle(); + }); + } + + function extendDateFormat (format,options) { + if (options.timePicker) { + if (options.timePicker24Hour) { + if (options.timePickerSeconds) { + format += " HH:mm:ss"; + } else { + format += " HH:mm"; + } + } else { + if (options.timePickerSeconds) { + format += ' ' + " hh:mm:ss A" + } else { + format += " hh:mm A"; + } + } + } + + return format; + } + + function fillInput($input, startDate, endDate, options) { + if (options.singleDatePicker) { + if (startDate) { + $input.val(startDate.format(options.dateFormat)); + } + } else { + if (startDate && endDate) { + $input.val(startDate.format(options.dateFormat) + options.separator + endDate.format(options.dateFormat)); + } + } + } + + $rootElement + .findWithSelf('abp-date-picker,abp-date-range-picker') + .each(function () { + var $this = $(this); + var $input = $this.find('.input-group input[type="text"]') + if ($input.data('daterangepicker')) { + return; + } + + var $openButton = $this.find('button[data-type="open"]') + var $clearButton = $this.find('button[data-type="clear"]') + var singleDatePicker = $this.is('abp-date-picker') + var options = {}; + + setOptions(options, $this, singleDatePicker); + + var isIso = options.isIso; + var dateFormat = options.dateFormat; + var separator = options.separator; + var defaultDateFormat = extendDateFormat("YYYY-MM-DD", options); + + var singleOpenAndClearButton = options.singleOpenAndClearButton && $clearButton.length > 0 && $openButton.length > 0; + + var startDate = getStartDate(options); + + var endDate = getEndDate(options); + if (startDate) { + options.startDate = startDate; + } + if (endDate) { + options.endDate = endDate; + } + + $input.daterangepicker(options); + + var $dateRangePicker = $input.data('daterangepicker'); + + addExtraButtons(options, $dateRangePicker, $input); + + addOpenButtonClick($openButton, $dateRangePicker); + + if (!dateFormat) { + dateFormat = extendDateFormat(moment.localeData().longDateFormat('L'), options); + options.dateFormat = dateFormat; + } + + if (!separator) { + separator = $dateRangePicker.locale.separator; + options.separator = separator; + } + + if(options.autoUpdateInput){ + fillInput($input, startDate, endDate, options); + } + + $input.on('apply.daterangepicker', function (ev, picker) { + if (singleDatePicker) { + $(this).val(picker.startDate.format(dateFormat)); + } else { + $(this).val(picker.startDate.format(dateFormat) + separator + picker.endDate.format(dateFormat)); + } + + $(this).trigger('change'); + }); + + $input.on('cancel.daterangepicker', function (ev, picker) { + $(this).val(''); + $(this).trigger('change'); + }); + + $input.on('show.daterangepicker', function (ev, picker) { + var momentStartDate = getMoment(startDate, options); + var momentEndDate = getMoment(endDate, options); + if (momentStartDate.isValid()) { + picker.setStartDate(momentStartDate); + } + if (momentEndDate.isValid()) { + picker.setEndDate(momentEndDate); + } + }); + + $clearButton.on('click', function () { + $input.val(''); + $input.trigger('change'); + }); + + var firstTrigger = true; + $input.on('change', function () { + if ($(this).val() !== '') { + replaceOpenButton(true, singleOpenAndClearButton, $openButton, $clearButton); + } else { + replaceOpenButton(false, singleOpenAndClearButton, $openButton, $clearButton); + } + var dates = $(this).val().split(separator); + if (dates.length === 2) { + startDate = formatDate(getStartDate(options, dates[0])); + endDate = formatDate(getEndDate(options, dates[1])); + } else { + if (dates[0]) { + startDate = formatDate(getStartDate(options, dates[0])); + } + else { + if(!firstTrigger){ + startDate = null; + } + } + + if(!firstTrigger){ + endDate = null; + } + } + + if (!startDate) { + replaceOpenButton(false, singleOpenAndClearButton, $openButton, $clearButton); + $(this).val(''); + } + + if (!singleDatePicker) { + var input1 = $this.find("input[data-start-date]") + input1.val(startDate); + var input2 = $this.find("input[data-end-date]") + input2.val(endDate); + } else { + var input = $this.find("input[data-date]") + input.val(startDate); + } + + if (singleDatePicker) { + $this.data('date', startDate); + $input.data('date', startDate); + } else { + $this.data('startDate', startDate); + $this.data('endDate', endDate); + $input.data('startDate', startDate); + $input.data('endDate', endDate); + } + + firstTrigger = false; + }); + + function formatDate(date) { + if (date) { + if (isIso) { + return date.locale('en').format(); + } + return date.locale('en').format(defaultDateFormat) + } + + return null; + } + + function getFormattedStartDate() { + if (startDate) { + return getMoment(startDate, options).format(dateFormat); + } + + return null; + } + + function getFormattedEndDate() { + if (endDate) { + return getMoment(endDate, options).format(dateFormat); + } + + return null; + } + + if (singleDatePicker) { + $this[0].getFormattedDate = getFormattedStartDate; + $input[0].getFormattedDate = getFormattedStartDate; + $dateRangePicker.getFormattedDate = getFormattedStartDate; + } else { + $dateRangePicker.getFormattedStartDate = getFormattedStartDate; + $dateRangePicker.getFormattedEndDate = getFormattedEndDate; + + $this[0].getFormattedStartDate = getFormattedStartDate; + $this[0].getFormattedEndDate = getFormattedEndDate; + + $input[0].getFormattedStartDate = getFormattedStartDate; + $input[0].getFormattedEndDate = getFormattedEndDate; + } + + $input.trigger('change'); + }); + } + abp.dom.onNodeAdded(function (args) { abp.dom.initializers.initializeToolTips(args.$el.findWithSelf('[data-toggle="tooltip"]')); abp.dom.initializers.initializePopovers(args.$el.findWithSelf('[data-toggle="popover"]')); @@ -215,6 +778,7 @@ abp.dom.initializers.initializePopovers($('[data-toggle="popover"]')); abp.dom.initializers.initializeTimeAgos($('.timeago')); abp.dom.initializers.initializeDatepickers($(document)); + abp.dom.initializers.initializeDateRangePickers($(document)); abp.dom.initializers.initializeForms($('form')); abp.dom.initializers.initializeAutocompleteSelects($('.auto-complete-select')); $('[data-auto-focus="true"]').first().findWithSelf('input,select').focus(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs index 54d766ec08..23b3d08602 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs @@ -39,7 +39,7 @@ public class AbpAspNetCoreMvcUiWidgetsModule : AbpModule { var widgetTypes = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (WidgetAttribute.IsWidget(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index ec3690ca8e..0a12d0332a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -43,6 +43,7 @@ using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.UI; using Volo.Abp.UI.Navigation; +using Volo.Abp.Validation.Localization; namespace Volo.Abp.AspNetCore.Mvc; @@ -174,10 +175,17 @@ public class AbpAspNetCoreMvcModule : AbpModule context.Services.Replace(ServiceDescriptor.Singleton()); context.Services.AddSingleton(); - Configure(mvcOptions => - { - mvcOptions.AddAbp(context.Services); - }); + context.Services.AddOptions() + .Configure((mvcOptions, serviceProvider) => + { + mvcOptions.AddAbp(context.Services); + + // serviceProvider is root service provider. + var stringLocalizer = serviceProvider.GetRequiredService>(); + mvcOptions.ModelBindingMessageProvider.SetValueIsInvalidAccessor(_ => stringLocalizer["The value '{0}' is invalid."]); + mvcOptions.ModelBindingMessageProvider.SetNonPropertyValueMustBeANumberAccessor(() => stringLocalizer["The field must be a number."]); + mvcOptions.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(value => stringLocalizer["The field {0} must be a number.", value]); + }); Configure(options => { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs index 9bffcabb41..10f7e28811 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs @@ -8,16 +8,16 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring; [Route("api/abp/api-definition")] public class AbpApiDefinitionController : AbpController, IRemoteService { - private readonly IApiDescriptionModelProvider _modelProvider; + protected readonly IApiDescriptionModelProvider ModelProvider; public AbpApiDefinitionController(IApiDescriptionModelProvider modelProvider) { - _modelProvider = modelProvider; + ModelProvider = modelProvider; } [HttpGet] - public ApplicationApiDescriptionModel Get(ApplicationApiDescriptionModelRequestDto model) + public virtual ApplicationApiDescriptionModel Get(ApplicationApiDescriptionModelRequestDto model) { - return _modelProvider.CreateApiModel(model); + return ModelProvider.CreateApiModel(model); } } 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 427aaf65a6..1836360f06 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 @@ -9,22 +9,22 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; [Route("api/abp/application-configuration")] public class AbpApplicationConfigurationController : AbpControllerBase, IAbpApplicationConfigurationAppService { - private readonly IAbpApplicationConfigurationAppService _applicationConfigurationAppService; - private readonly IAbpAntiForgeryManager _antiForgeryManager; + protected readonly IAbpApplicationConfigurationAppService ApplicationConfigurationAppService; + protected readonly IAbpAntiForgeryManager AntiForgeryManager; public AbpApplicationConfigurationController( IAbpApplicationConfigurationAppService applicationConfigurationAppService, IAbpAntiForgeryManager antiForgeryManager) { - _applicationConfigurationAppService = applicationConfigurationAppService; - _antiForgeryManager = antiForgeryManager; + ApplicationConfigurationAppService = applicationConfigurationAppService; + AntiForgeryManager = antiForgeryManager; } [HttpGet] public virtual async Task GetAsync( ApplicationConfigurationRequestOptions options) { - _antiForgeryManager.SetCookie(); - return await _applicationConfigurationAppService.GetAsync(options); + AntiForgeryManager.SetCookie(); + 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 7fafee261c..de38e89104 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,11 +17,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; [ApiExplorerSettings(IgnoreApi = true)] public class AbpApplicationConfigurationScriptController : AbpController { - private readonly AbpApplicationConfigurationAppService _configurationAppService; - private readonly IJsonSerializer _jsonSerializer; - private readonly AbpAspNetCoreMvcOptions _options; - private readonly IJavascriptMinifier _javascriptMinifier; - private readonly IAbpAntiForgeryManager _antiForgeryManager; + protected readonly AbpApplicationConfigurationAppService ConfigurationAppService; + protected readonly IJsonSerializer JsonSerializer; + protected readonly AbpAspNetCoreMvcOptions Options; + protected readonly IJavascriptMinifier JavascriptMinifier; + protected readonly IAbpAntiForgeryManager AntiForgeryManager; public AbpApplicationConfigurationScriptController( AbpApplicationConfigurationAppService configurationAppService, @@ -30,42 +30,42 @@ public class AbpApplicationConfigurationScriptController : AbpController IJavascriptMinifier javascriptMinifier, IAbpAntiForgeryManager antiForgeryManager) { - _configurationAppService = configurationAppService; - _jsonSerializer = jsonSerializer; - _options = options.Value; - _javascriptMinifier = javascriptMinifier; - _antiForgeryManager = antiForgeryManager; + ConfigurationAppService = configurationAppService; + JsonSerializer = jsonSerializer; + Options = options.Value; + JavascriptMinifier = javascriptMinifier; + AntiForgeryManager = antiForgeryManager; } [HttpGet] [Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)] - public async Task Get() + public virtual async Task Get() { var script = CreateAbpExtendScript( - await _configurationAppService.GetAsync( + await ConfigurationAppService.GetAsync( new ApplicationConfigurationRequestOptions { IncludeLocalizationResources = false } ) ); - _antiForgeryManager.SetCookie(); + AntiForgeryManager.SetCookie(); return Content( - _options.MinifyGeneratedScript == true - ? _javascriptMinifier.Minify(script) + Options.MinifyGeneratedScript == true + ? JavascriptMinifier.Minify(script) : script, MimeTypes.Application.Javascript ); } - private string CreateAbpExtendScript(ApplicationConfigurationDto config) + protected virtual string CreateAbpExtendScript(ApplicationConfigurationDto config) { var script = new StringBuilder(); script.AppendLine("(function(){"); script.AppendLine(); - script.AppendLine($"$.extend(true, abp, {_jsonSerializer.Serialize(config, indented: true)})"); + script.AppendLine($"$.extend(true, abp, {JsonSerializer.Serialize(config, indented: true)})"); script.AppendLine(); script.AppendLine("abp.event.trigger('abp.configurationInitialized');"); script.AppendLine(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs index fdaf0e8ff1..05a3f52c72 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs @@ -24,6 +24,7 @@ public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency { if (!ShouldHandleException(context)) { + LogException(context, out _); return; } @@ -57,25 +58,7 @@ public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency { //TODO: Trigger an AbpExceptionHandled event or something like that. - var exceptionHandlingOptions = context.GetRequiredService>().Value; - var exceptionToErrorInfoConverter = context.GetRequiredService(); - var remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, options => - { - options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients; - options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients; - }); - - var logLevel = context.Exception.GetLogLevel(); - - var remoteServiceErrorInfoBuilder = new StringBuilder(); - remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); - remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService().Serialize(remoteServiceErrorInfo, indented: true)); - - var logger = context.GetService>(NullLogger.Instance); - - logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); - - logger.LogException(context.Exception, logLevel); + LogException(context, out var remoteServiceErrorInfo); await context.GetRequiredService().NotifyAsync(new ExceptionNotificationContext(context.Exception)); @@ -96,4 +79,24 @@ public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency context.Exception = null; //Handled! } + + protected virtual void LogException(ExceptionContext context, out RemoteServiceErrorInfo remoteServiceErrorInfo) + { + var exceptionHandlingOptions = context.GetRequiredService>().Value; + var exceptionToErrorInfoConverter = context.GetRequiredService(); + remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, options => + { + options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients; + options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients; + }); + + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService().Serialize(remoteServiceErrorInfo, indented: true)); + + var logger = context.GetService>(NullLogger.Instance); + var logLevel = context.Exception.GetLogLevel(); + logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + logger.LogException(context.Exception, logLevel); + } } 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 index d8da85f871..c3da22706e 100644 --- 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 @@ -39,7 +39,7 @@ public class AbpApplicationLocalizationScriptController : AbpController [HttpGet] [Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)] - public async Task GetAsync(ApplicationLocalizationRequestDto input) + public virtual async Task GetAsync(ApplicationLocalizationRequestDto input) { var script = CreateScript( await LocalizationAppService.GetAsync(input) @@ -53,7 +53,7 @@ public class AbpApplicationLocalizationScriptController : AbpController ); } - private string CreateScript(ApplicationLocalizationDto localizationDto) + protected virtual string CreateScript(ApplicationLocalizationDto localizationDto) { var script = new StringBuilder(); @@ -66,4 +66,4 @@ public class AbpApplicationLocalizationScriptController : AbpController return script.ToString(); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs index c15d9b5bb8..ef7196fb0c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs @@ -14,30 +14,30 @@ namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting; [ApiExplorerSettings(IgnoreApi = true)] public class AbpServiceProxyScriptController : AbpController { - private readonly IProxyScriptManager _proxyScriptManager; - private readonly AbpAspNetCoreMvcOptions _options; - private readonly IJavascriptMinifier _javascriptMinifier; + protected readonly IProxyScriptManager ProxyScriptManager; + protected readonly AbpAspNetCoreMvcOptions Options; + protected readonly IJavascriptMinifier JavascriptMinifier; public AbpServiceProxyScriptController(IProxyScriptManager proxyScriptManager, IOptions options, IJavascriptMinifier javascriptMinifier) { - _proxyScriptManager = proxyScriptManager; - _options = options.Value; - _javascriptMinifier = javascriptMinifier; + ProxyScriptManager = proxyScriptManager; + Options = options.Value; + JavascriptMinifier = javascriptMinifier; } [HttpGet] [Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)] - public ActionResult GetAll(ServiceProxyGenerationModel model) + public virtual ActionResult GetAll(ServiceProxyGenerationModel model) { model.Normalize(); - var script = _proxyScriptManager.GetScript(model.CreateOptions()); + var script = ProxyScriptManager.GetScript(model.CreateOptions()); return Content( - _options.MinifyGeneratedScript == true - ? _javascriptMinifier.Minify(script) + Options.MinifyGeneratedScript == true + ? JavascriptMinifier.Minify(script) : script, MimeTypes.Application.Javascript ); diff --git a/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs b/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs index 903e60a16f..13400a7ff9 100644 --- a/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs @@ -91,7 +91,7 @@ public class AbpAspNetCoreSignalRModule : AbpModule { var hubTypes = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (IsHubClass(context) && !IsDisabledForAutoMap(context)) { diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs index b6527f10c8..7c6fdacc11 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs @@ -24,7 +24,7 @@ public class AbpAuditingModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(AuditingInterceptorRegistrar.RegisterIfNeeded); + context.Services.OnRegistered(AuditingInterceptorRegistrar.RegisterIfNeeded); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs index 939814cba9..623733e8e5 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq.Expressions; +using System.Threading; using System.Threading.Tasks; namespace Volo.Abp.Auditing; @@ -76,7 +77,8 @@ public class AbpAuditingOptions IgnoredTypes = new List { typeof(Stream), - typeof(Expression) + typeof(Expression), + typeof(CancellationToken) }; EntityHistorySelectors = new EntityHistorySelectorList(); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs index 9a1a4c52b4..cb244599c2 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs @@ -22,7 +22,7 @@ public class AbpAuthorizationModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(AuthorizationInterceptorRegistrar.RegisterIfNeeded); + context.Services.OnRegistered(AuthorizationInterceptorRegistrar.RegisterIfNeeded); AutoAddDefinitionProviders(context.Services); } @@ -64,7 +64,7 @@ public class AbpAuthorizationModule : AbpModule { var definitionProviders = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (typeof(IPermissionDefinitionProvider).IsAssignableFrom(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/fi.json b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/fi.json index 16700a0891..bbd590ca45 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/fi.json +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/fi.json @@ -1,10 +1,10 @@ { "culture": "fi", "texts": { - "Volo.Authorization:010001": "Käyttöoikeuden varmistus epäonnistui! Annettua politiikkaa ei ole myönnetty.", + "Volo.Authorization:010001": "Käyttöoikeuden varmistus epäonnistui! Annettua käytäntöä ei ole myönnetty.", "Volo.Authorization:010002": "Käyttöoikeuden varmistus epäonnistui! Annettua käytäntöä ei ole myönnetty: {PolicyName}", - "Volo.Authorization:010003": "Käyttöoikeuden varmistus epäonnistui! Annettua käytäntöä ei ole myönnetty tietylle resurssille: {ResourceName}", - "Volo.Authorization:010004": "Käyttöoikeuden varmistus epäonnistui! Annettua vaatimusta ei ole annettu tietylle resurssille: {ResourceName}", - "Volo.Authorization:010005": "Käyttöoikeuden varmistus epäonnistui! Annettuja vaatimuksia ei ole annettu tietylle resurssille: {ResourceName}" + "Volo.Authorization:010003": "Käyttöoikeuden varmistus epäonnistui! Annettua käytäntöä ei ole myönnetty resurssille: {ResourceName}", + "Volo.Authorization:010004": "Käyttöoikeuden varmistus epäonnistui! Annettua vaatimusta ei ole annettu resurssille: {ResourceName}", + "Volo.Authorization:010005": "Käyttöoikeuden varmistus epäonnistui! Annettuja vaatimuksia ei ole annettu resurssille: {ResourceName}" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj index d8d2b83e5a..e03023a72a 100644 --- a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj +++ b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs index 99d4d9e4df..8d3dc6caf5 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs @@ -21,7 +21,7 @@ public class AbpBackgroundJobsAbstractionsModule : AbpModule { var jobTypes = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IBackgroundJob<>)) || ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IAsyncBackgroundJob<>))) diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AsyncBackgroundJob.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AsyncBackgroundJob.cs index 4cf298f712..aa6cad070a 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AsyncBackgroundJob.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AsyncBackgroundJob.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -6,7 +7,7 @@ namespace Volo.Abp.BackgroundJobs; public abstract class AsyncBackgroundJob : IAsyncBackgroundJob { - //TODO: Add UOW, Localization and other useful properties..? + //TODO: Add UOW, Localization, CancellationTokenProvider and other useful properties..? public ILogger> Logger { get; set; } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJob.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJob.cs index 57beeb77b5..b1770942fe 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJob.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJob.cs @@ -1,3 +1,4 @@ +using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -5,7 +6,7 @@ namespace Volo.Abp.BackgroundJobs; public abstract class BackgroundJob : IBackgroundJob { - //TODO: Add UOW, Localization and other useful properties..? + //TODO: Add UOW, Localization, CancellationTokenProvider and other useful properties..? public ILogger> Logger { get; set; } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs index 7a6f54fbd7..7798a8d9a6 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; using Volo.Abp.ExceptionHandling; using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; namespace Volo.Abp.BackgroundJobs; @@ -46,13 +47,19 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc { using(CurrentTenant.Change(GetJobArgsTenantId(context.JobArgs))) { - if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob.ExecuteAsync)) - { - await ((Task)jobExecuteMethod.Invoke(job, new[] { context.JobArgs })); - } - else + var cancellationTokenProvider = + context.ServiceProvider.GetRequiredService(); + + using (cancellationTokenProvider.Use(context.CancellationToken)) { - jobExecuteMethod.Invoke(job, new[] { context.JobArgs }); + if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob.ExecuteAsync)) + { + await ((Task)jobExecuteMethod.Invoke(job, new[] { context.JobArgs })); + } + else + { + jobExecuteMethod.Invoke(job, new[] { context.JobArgs }); + } } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAsyncBackgroundJob.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAsyncBackgroundJob.cs index dc39b1bd0f..022067d3ed 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAsyncBackgroundJob.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAsyncBackgroundJob.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Volo.Abp.BackgroundJobs; diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJob.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJob.cs index 140db1e72a..8c8c17ca93 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJob.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJob.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.BackgroundJobs; +using System.Threading; + +namespace Volo.Abp.BackgroundJobs; /// /// Defines interface of a background job. diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs index a5de60d298..ae7e098070 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BackgroundJobs; @@ -11,10 +12,17 @@ public class JobExecutionContext : IServiceProviderAccessor public object JobArgs { get; } - public JobExecutionContext(IServiceProvider serviceProvider, Type jobType, object jobArgs) + public CancellationToken CancellationToken { get; } + + public JobExecutionContext( + IServiceProvider serviceProvider, + Type jobType, + object jobArgs, + CancellationToken cancellationToken = default) { ServiceProvider = serviceProvider; JobType = jobType; JobArgs = jobArgs; + CancellationToken = cancellationToken; } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs index 2583ada9fc..a94426adad 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs @@ -12,22 +12,22 @@ namespace Volo.Abp.BackgroundJobs.Hangfire; public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDependency { protected AbpBackgroundJobOptions Options { get; } - + public HangfireBackgroundJobManager(IOptions options) { Options = options.Value; } - + public virtual Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) { return Task.FromResult(delay.HasValue ? BackgroundJob.Schedule>( - adapter => adapter.ExecuteAsync(GetQueueName(typeof(TArgs)),args), + adapter => adapter.ExecuteAsync(GetQueueName(typeof(TArgs)), args, default), delay.Value ) : BackgroundJob.Enqueue>( - adapter => adapter.ExecuteAsync(GetQueueName(typeof(TArgs)) ,args) + adapter => adapter.ExecuteAsync(GetQueueName(typeof(TArgs)), args, default) )); } diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs index 7ba0cd7db4..58d2863618 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Hangfire; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -21,8 +22,8 @@ public class HangfireJobExecutionAdapter Options = options.Value; } - [Queue("{0}")] - public async Task ExecuteAsync(string queue, TArgs args) + [Queue("{0}")] + public async Task ExecuteAsync(string queue, TArgs args, CancellationToken cancellationToken = default) { if (!Options.IsJobExecutionEnabled) { @@ -38,7 +39,7 @@ public class HangfireJobExecutionAdapter using (var scope = ServiceScopeFactory.CreateScope()) { var jobType = Options.GetJob(typeof(TArgs)).JobType; - var context = new JobExecutionContext(scope.ServiceProvider, jobType, args); + var context = new JobExecutionContext(scope.ServiceProvider, jobType, args, cancellationToken: cancellationToken); await JobExecuter.ExecuteAsync(context); } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs index 3127cc5910..588e844153 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs @@ -40,7 +40,7 @@ public class QuartzJobExecutionAdapter : IJob { var args = JsonSerializer.Deserialize(context.JobDetail.JobDataMap.GetString(nameof(TArgs))); var jobType = Options.GetJob(typeof(TArgs)).JobType; - var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args); + var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args, cancellationToken: context.CancellationToken); try { await JobExecuter.ExecuteAsync(jobContext); diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs index 8de0a5ff6e..4aeaf59885 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs @@ -68,7 +68,8 @@ public class BackgroundJobWorker : AsyncPeriodicBackgroundWorkerBase, IBackgroun var context = new JobExecutionContext( workerContext.ServiceProvider, jobConfiguration.JobType, - jobArgs); + jobArgs, + workerContext.CancellationToken); try { diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 1e3cf13549..3bcc507e39 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -309,7 +309,7 @@ public abstract class AbpCrudPageBase< { CurrentSorting = e.Columns .Where(c => c.SortDirection != SortDirection.Default) - .Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : "")) + .Select(c => c.SortField + (c.SortDirection == SortDirection.Descending ? " DESC" : "")) .JoinAsString(","); CurrentPage = e.Page; @@ -608,7 +608,8 @@ public abstract class AbpCrudPageBase< yield return new TableColumn { Title = lookupPropertyDefinition.GetLocalizedDisplayName(StringLocalizerFactory), - Data = $"ExtraProperties[{propertyInfo.Name}]" + Data = $"ExtraProperties[{propertyInfo.Name}]", + PropertyName = propertyInfo.Name }; } else @@ -616,7 +617,8 @@ public abstract class AbpCrudPageBase< var column = new TableColumn { Title = propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory), - Data = $"ExtraProperties[{propertyInfo.Name}]" + Data = $"ExtraProperties[{propertyInfo.Name}]", + PropertyName = propertyInfo.Name }; if (propertyInfo.IsDate() || propertyInfo.IsDateTime()) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor index c674ec3612..88a0a1df4a 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor @@ -34,15 +34,16 @@ { if (column.Actions.Any()) { + var entityActionType = column.Actions.Count == 1 ? ActionType.Button : ActionType.Dropdown; - + @foreach (var action in column.Actions) { if (action.ConfirmationMessage != null) { + @{ var entity = context as IHasExtraProperties; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs index cc198e877b..ec2074434a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs @@ -58,7 +58,7 @@ public class AuthService : IAuthService, ITransientDependency { if (!response.IsSuccessStatusCode) { - Logger.LogError("Remote server returns '{response.StatusCode}'"); + Logger.LogError($"Remote server returns '{response.StatusCode}'"); return null; } @@ -127,6 +127,26 @@ public class AuthService : IAuthService, ITransientDependency } } + public async Task CheckMultipleOrganizationsAsync(string username) + { + var url = $"{CliUrls.WwwAbpIo}api/license/check-multiple-organizations?username={username}"; + + var client = CliHttpClientFactory.CreateClient(); + + using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger)) + { + if (!response.IsSuccessStatusCode) + { + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); + } + + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); + + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent); + } + } + private async Task LogoutAsync(string accessToken) { try diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/IAuthService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/IAuthService.cs index ad2916ca0b..f91fd0dc00 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/IAuthService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/IAuthService.cs @@ -9,4 +9,6 @@ public interface IAuthService Task LoginAsync(string userName, string password, string organizationName = null); Task LogoutAsync(); + + Task CheckMultipleOrganizationsAsync(string username); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/LoginInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/LoginInfo.cs index aacb26e5f4..3b14c96330 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/LoginInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/LoginInfo.cs @@ -1,8 +1,11 @@ - +using System; + namespace Volo.Abp.Cli.Auth; public class LoginInfo { + public Guid? Id { get; set; } + public string Name { get; set; } public string Surname { get; set; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs index a3b6d4a411..0d5b3b31e5 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs @@ -2,13 +2,11 @@ using Microsoft.Extensions.Logging.Abstractions; using System; using System.Text; -using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Auth; -using Volo.Abp.Cli.Http; using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; @@ -26,17 +24,13 @@ public class LoginCommand : IConsoleCommand, ITransientDependency public ICancellationTokenProvider CancellationTokenProvider { get; } public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } - private readonly CliHttpClientFactory _cliHttpClientFactory; - public LoginCommand(AuthService authService, ICancellationTokenProvider cancellationTokenProvider, - IRemoteServiceExceptionHandler remoteServiceExceptionHandler, - CliHttpClientFactory cliHttpClientFactory) + IRemoteServiceExceptionHandler remoteServiceExceptionHandler) { AuthService = authService; CancellationTokenProvider = cancellationTokenProvider; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; - _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } @@ -111,7 +105,7 @@ public class LoginCommand : IConsoleCommand, ITransientDependency private async Task HasMultipleOrganizationAndThisNotSpecified(CommandLineArgs commandLineArgs, string organization) { if (string.IsNullOrWhiteSpace(organization) && - await CheckMultipleOrganizationsAsync(commandLineArgs.Target)) + await AuthService.CheckMultipleOrganizationsAsync(commandLineArgs.Target)) { Logger.LogError($"You have multiple organizations, please specify your organization with `--organization` parameter."); return true; @@ -168,26 +162,6 @@ public class LoginCommand : IConsoleCommand, ITransientDependency return false; } - private async Task CheckMultipleOrganizationsAsync(string username) - { - var url = $"{CliUrls.WwwAbpIo}api/license/check-multiple-organizations?username={username}"; - - var client = _cliHttpClientFactory.CreateClient(); - - using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger)) - { - if (!response.IsSuccessStatusCode) - { - throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); - } - - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); - - var responseContent = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(responseContent); - } - } - public string GetUsageInfo() { var sb = new StringBuilder(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs index 9d17bf4c4f..d8f007dc2c 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System.Text; using System.Threading.Tasks; @@ -41,7 +40,7 @@ public class LoginInfoCommand : IConsoleCommand, ITransientDependency var sb = new StringBuilder(); sb.AppendLine(""); - sb.AppendLine($"Login info:"); + sb.AppendLine("Login info:"); sb.AppendLine($"Name: {loginInfo.Name}"); sb.AppendLine($"Surname: {loginInfo.Surname}"); sb.AppendLine($"Username: {loginInfo.Username}"); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SuiteAppSettingsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SuiteAppSettingsService.cs new file mode 100644 index 0000000000..d274234ded --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SuiteAppSettingsService.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands.Services; + +public class SuiteAppSettingsService : ITransientDependency +{ + private const int DefaultPort = 3000; + + public CmdHelper CmdHelper { get; } + + public SuiteAppSettingsService(CmdHelper cmdHelper) + { + CmdHelper = cmdHelper; + } + + public async Task GetSuitePortAsync() + { + return await GetSuitePortAsync(GetCurrentSuiteVersion()); + } + + public async Task GetSuitePortAsync(string version) + { + var filePath = GetFilePathOrNull(version); + + if (filePath == null) + { + return DefaultPort; + } + + var content = File.ReadAllText(filePath); + + var contentAsJson = JObject.Parse(content); + + var url = contentAsJson["AbpSuite"]?["ApplicationUrl"]?.ToString(); + + if (url == null) + { + return DefaultPort; + } + + return Convert.ToInt32(url.Split(":").Last()); + } + + private string GetFilePathOrNull(string version) + { + if (version == null) + { + return null; + } + + var path = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".dotnet", + "tools", + ".store", + "volo.abp.suite", + version, + "volo.abp.suite", + version, + "tools", + "net7.0", + "any", + "appsettings.json" + ); + + if (!File.Exists(path)) + { + return null; + } + + return path; + } + + private string GetCurrentSuiteVersion() + { + var dotnetToolList = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g", out int exitCode); + + var suiteLine = dotnetToolList.Split(Environment.NewLine) + .FirstOrDefault(l => l.ToLower().StartsWith("volo.abp.suite ")); + + if (string.IsNullOrEmpty(suiteLine)) + { + return null; + } + + return suiteLine.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1]; + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs index 83c21d4773..b35efb6f36 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs @@ -35,23 +35,26 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency private readonly PackageVersionCheckerService _packageVersionCheckerService; private readonly AuthService _authService; private readonly CliHttpClientFactory _cliHttpClientFactory; + private readonly SuiteAppSettingsService _suiteAppSettingsService; private const string SuitePackageName = "Volo.Abp.Suite"; public ILogger Logger { get; set; } - private const string AbpSuiteHost = "http://localhost:3000"; + private int _abpSuitePort = 3000; public SuiteCommand( AbpNuGetIndexUrlService nuGetIndexUrlService, PackageVersionCheckerService packageVersionCheckerService, ICmdHelper cmdHelper, AuthService authService, - CliHttpClientFactory cliHttpClientFactory) + CliHttpClientFactory cliHttpClientFactory, + SuiteAppSettingsService suiteAppSettingsService) { CmdHelper = cmdHelper; _nuGetIndexUrlService = nuGetIndexUrlService; _packageVersionCheckerService = packageVersionCheckerService; _authService = authService; _cliHttpClientFactory = cliHttpClientFactory; + _suiteAppSettingsService = suiteAppSettingsService; Logger = NullLogger.Instance; } @@ -72,17 +75,20 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency commandLineArgs.Options.ContainsKey(Options.Preview.Long); var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); + var currentSuiteVersionAsString = GetCurrentSuiteVersion(); switch (operationType) { case "": case null: - await InstallSuiteIfNotInstalledAsync(); + await InstallSuiteIfNotInstalledAsync(currentSuiteVersionAsString); + _abpSuitePort = await _suiteAppSettingsService.GetSuitePortAsync(currentSuiteVersionAsString); RunSuite(); break; case "generate": - await InstallSuiteIfNotInstalledAsync(); + await InstallSuiteIfNotInstalledAsync(currentSuiteVersionAsString); + _abpSuitePort = await _suiteAppSettingsService.GetSuitePortAsync(currentSuiteVersionAsString); var suiteProcess = StartSuite(); System.Threading.Thread.Sleep(500); //wait for initialization of the app await GenerateCrudPageAsync(commandLineArgs); @@ -130,7 +136,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency } var IsSolutionBuiltResponse = await client.GetAsync( - $"{AbpSuiteHost}/api/abpSuite/solutions/{solutionId.ToString()}/is-built" + $"http://localhost:{_abpSuitePort}/api/abpSuite/solutions/{solutionId.ToString()}/is-built" ); var IsSolutionBuilt = Convert.ToBoolean(await IsSolutionBuiltResponse.Content.ReadAsStringAsync()); @@ -148,7 +154,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency ); var responseMessage = await client.PostAsync( - $"{AbpSuiteHost}/api/abpSuite/crudPageGenerator/{solutionId.ToString()}/save-and-generate-entity", + $"http://localhost:{_abpSuitePort}/api/abpSuite/crudPageGenerator/{solutionId.ToString()}/save-and-generate-entity", entityContent ); @@ -178,7 +184,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency } var responseMessage = await client.GetHttpResponseMessageWithRetryAsync( - "http://localhost:3000/api/abpSuite/solutions", + $"http://localhost:{_abpSuitePort}/api/abpSuite/solutions", _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)), Logger, timeIntervals.ToArray()); @@ -216,7 +222,7 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency ); var responseMessage = await client.PostAsync( - "http://localhost:3000/api/abpSuite/addSolution", + $"http://localhost:{_abpSuitePort}/api/abpSuite/addSolution", entityContent, _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)) ); @@ -234,11 +240,9 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency } } - private async Task InstallSuiteIfNotInstalledAsync() + private async Task InstallSuiteIfNotInstalledAsync(string currentSuiteVersion) { - var currentSuiteVersionAsString = GetCurrentSuiteVersion(); - - if (string.IsNullOrEmpty(currentSuiteVersionAsString)) + if (string.IsNullOrEmpty(currentSuiteVersion)) { await InstallSuiteAsync(); } @@ -430,6 +434,19 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency Logger.LogWarning("Couldn't check ABP Suite installed status: " + ex.Message); } + if (IsSuiteAlreadyRunning()) + { + Logger.LogInformation("Opening suite..."); + CmdHelper.Open($"http://localhost:{_abpSuitePort}"); + return; + } + + if (IsPortAlreadyInUse()) + { + Logger.LogError($"Port \"{_abpSuitePort}\" is already in use."); + return; + } + CmdHelper.RunCmd("abp-suite"); } @@ -453,23 +470,32 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency return null; } + if (IsPortAlreadyInUse()) + { + Logger.LogError($"Port \"{_abpSuitePort}\" is already in use."); + return null; + } + return CmdHelper.RunCmdAndGetProcess("abp-suite --no-browser"); } private bool IsSuiteAlreadyRunning() + { + return GetProcessesRelatedWithSuite().Any(); + } + + private bool IsPortAlreadyInUse() { var ipGP = IPGlobalProperties.GetIPGlobalProperties(); var endpoints = ipGP.GetActiveTcpListeners(); - return endpoints.Any(e => e.Port == 3000); + return endpoints.Any(e => e.Port == _abpSuitePort); } private void KillSuite() { try { - var suiteProcesses = (from p in Process.GetProcesses() - where p.ProcessName.ToLower().Contains("abp-suite") - select p); + var suiteProcesses = GetProcessesRelatedWithSuite(); foreach (var suiteProcess in suiteProcesses) { @@ -483,6 +509,13 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency } } + private IEnumerable GetProcessesRelatedWithSuite() + { + return (from p in Process.GetProcesses() + where p.ProcessName.ToLower().Contains("abp-suite") + select p); + } + public string GetUsageInfo() { var sb = new StringBuilder(); diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionRegistrationActionExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionRegistrationActionExtensions.cs index 2947526fa5..286cc05210 100644 --- a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionRegistrationActionExtensions.cs +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionRegistrationActionExtensions.cs @@ -5,9 +5,9 @@ namespace Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionRegistrationActionExtensions { - // OnRegistred + // OnRegistered - public static void OnRegistred(this IServiceCollection services, Action registrationAction) + public static void OnRegistered(this IServiceCollection services, Action registrationAction) { GetOrCreateRegistrationActionList(services).Add(registrationAction); } diff --git a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs index c7e1bef3b2..57f2c59d57 100644 --- a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs @@ -209,7 +209,22 @@ public static class AbpStringExtensions return str; } - return str.Substring(0, pos) + replace + str.Substring(pos + search.Length); + var searchLength = search.Length; + var replaceLength = replace.Length; + var newLength = str.Length - searchLength + replaceLength; + + Span buffer = newLength <= 1024 ? stackalloc char[newLength] : new char[newLength]; + + // Copy the part of the original string before the search term + str.AsSpan(0, pos).CopyTo(buffer); + + // Copy the replacement text + replace.AsSpan().CopyTo(buffer.Slice(pos)); + + // Copy the remainder of the original string + str.AsSpan(pos + searchLength).CopyTo(buffer.Slice(pos + replaceLength)); + + return buffer.ToString(); } /// diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs index 931dc8c07e..b573d7d958 100644 --- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs @@ -42,7 +42,7 @@ public class AbpDataModule : AbpModule { var contributors = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (typeof(IDataSeedContributor).IsAssignableFrom(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo.Abp.DistributedLocking.Abstractions.csproj b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo.Abp.DistributedLocking.Abstractions.csproj index 19c1b4f869..c75303ad2a 100644 --- a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo.Abp.DistributedLocking.Abstractions.csproj +++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo.Abp.DistributedLocking.Abstractions.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs index fdec4f1d3f..490f024d36 100644 --- a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs +++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs @@ -1,21 +1,27 @@ using System; -using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using AsyncKeyedLock; using Volo.Abp.DependencyInjection; namespace Volo.Abp.DistributedLocking; public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency { - private readonly ConcurrentDictionary _localSyncObjects = new(); + private readonly AsyncKeyedLocker _localSyncObjects = new(o => + { + o.PoolSize = 20; + o.PoolInitialFill = 1; + }); protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; } public LocalAbpDistributedLock(IDistributedLockKeyNormalizer distributedLockKeyNormalizer) { DistributedLockKeyNormalizer = distributedLockKeyNormalizer; } - + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public async Task TryAcquireAsync( string name, TimeSpan timeout = default, @@ -23,14 +29,13 @@ public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency { Check.NotNullOrWhiteSpace(name, nameof(name)); var key = DistributedLockKeyNormalizer.NormalizeKey(name); - - var semaphore = _localSyncObjects.GetOrAdd(key, _ => new SemaphoreSlim(1, 1)); - if (!await semaphore.WaitAsync(timeout, cancellationToken)) + var timeoutReleaser = await _localSyncObjects.LockAsync(key, timeout, cancellationToken); + if (!timeoutReleaser.EnteredSemaphore) { + timeoutReleaser.Dispose(); return null; } - - return new LocalAbpDistributedLockHandle(semaphore); + return new LocalAbpDistributedLockHandle(timeoutReleaser); } } diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLockHandle.cs b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLockHandle.cs index f4f6d640bc..d08451657e 100644 --- a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLockHandle.cs +++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLockHandle.cs @@ -1,20 +1,20 @@ -using System.Threading; +using System; using System.Threading.Tasks; namespace Volo.Abp.DistributedLocking; public class LocalAbpDistributedLockHandle : IAbpDistributedLockHandle { - private readonly SemaphoreSlim _semaphore; + private readonly IDisposable _disposable; - public LocalAbpDistributedLockHandle(SemaphoreSlim semaphore) + public LocalAbpDistributedLockHandle(IDisposable disposable) { - _semaphore = semaphore; + _disposable = disposable; } public ValueTask DisposeAsync() { - _semaphore.Release(); + _disposable.Dispose(); return default; } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs index 881a6437d9..9a70469df8 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.BackgroundJobs; using Volo.Abp.DependencyInjection; diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/fi.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/fi.json index ac197f1634..5560d3a598 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/fi.json +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/fi.json @@ -4,7 +4,7 @@ "DisplayName:Abp.Mailing.DefaultFromAddress": "Oletus osoitteesta", "DisplayName:Abp.Mailing.DefaultFromDisplayName": "Oletus näyttönimestä", "DisplayName:Abp.Mailing.Smtp.Host": "Isäntä", - "DisplayName:Abp.Mailing.Smtp.Port": "Satama", + "DisplayName:Abp.Mailing.Smtp.Port": "Portti", "DisplayName:Abp.Mailing.Smtp.UserName": "Käyttäjänimi", "DisplayName:Abp.Mailing.Smtp.Password": "Salasana", "DisplayName:Abp.Mailing.Smtp.Domain": "Verkkotunnus", diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index e9e1f2c618..96d7b4111e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -4,11 +4,13 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Storage; using Volo.Abp.Domain.Entities; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.DependencyInjection; @@ -73,6 +75,16 @@ public class EfCoreRepository : RepositoryBase, IE { return (await GetDbContextAsync()).Set(); } + + protected async Task GetDbConnectionAsync() + { + return (await GetDbContextAsync()).Database.GetDbConnection(); + } + + protected async Task GetDbTransactionAsync() + { + return (await GetDbContextAsync()).Database.CurrentTransaction?.GetDbTransaction(); + } protected virtual AbpEntityOptions AbpEntityOptions => _entityOptionsLazy.Value; diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index fe3d9f3032..21e663e5e1 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -47,7 +47,7 @@ public class AbpEventBusModule : AbpModule var localHandlers = new List(); var distributedHandlers = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(ILocalEventHandler<>))) { diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs index 7411c61fdb..cde88956b5 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs @@ -22,7 +22,7 @@ public class AbpFeaturesModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded); + context.Services.OnRegistered(FeatureInterceptorRegistrar.RegisterIfNeeded); AutoAddDefinitionProviders(context.Services); } @@ -57,7 +57,7 @@ public class AbpFeaturesModule : AbpModule { var definitionProviders = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (typeof(IFeatureDefinitionProvider).IsAssignableFrom(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs index 14cd0d405b..1a1e2ece3b 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs @@ -124,7 +124,7 @@ public class FeatureDefinition : ICanCreateChildFeature } /// - /// Sets a property in the dictionary. + /// Adds one or more providers to the list. /// This is a shortcut for nested calls on this object. /// public virtual FeatureDefinition WithProviders(params string[] providers) diff --git a/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/AbpGlobalFeaturesModule.cs b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/AbpGlobalFeaturesModule.cs index 8541d04306..3c8cd5708a 100644 --- a/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/AbpGlobalFeaturesModule.cs +++ b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/AbpGlobalFeaturesModule.cs @@ -17,7 +17,7 @@ public class AbpGlobalFeaturesModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(GlobalFeatureInterceptorRegistrar.RegisterIfNeeded); + context.Services.OnRegistered(GlobalFeatureInterceptorRegistrar.RegisterIfNeeded); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs b/framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs index d44a294799..49080939c2 100644 --- a/framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs +++ b/framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs @@ -6,6 +6,7 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; @@ -13,6 +14,7 @@ using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Client.StaticProxying; using Volo.Abp.Http.Modeling; using Volo.Abp.Reflection; @@ -24,14 +26,17 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention protected readonly IClientProxyApiDescriptionFinder ClientProxyApiDescriptionFinder; protected readonly List ControllerWithAttributeRoute; protected readonly List ActionWithAttributeRoute; + protected readonly AbpHttpClientStaticProxyingOptions StaticProxyingOptions; public AbpHttpClientProxyServiceConvention( IOptions options, IConventionalRouteBuilder conventionalRouteBuilder, - IClientProxyApiDescriptionFinder clientProxyApiDescriptionFinder) + IClientProxyApiDescriptionFinder clientProxyApiDescriptionFinder, + IOptions staticProxyingOptions) : base(options, conventionalRouteBuilder) { ClientProxyApiDescriptionFinder = clientProxyApiDescriptionFinder; + StaticProxyingOptions = staticProxyingOptions.Value; ControllerWithAttributeRoute = new List(); ActionWithAttributeRoute = new List(); } @@ -73,6 +78,27 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention } } + protected override void ConfigureParameters(ControllerModel controller) + { + foreach (var action in controller.Actions) + { + foreach (var prm in action.Parameters) + { + if (prm.BindingInfo != null) + { + continue; + } + + if (StaticProxyingOptions.BindingFromQueryTypes.Contains(prm.ParameterInfo.ParameterType)) + { + prm.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromQueryAttribute() }); + } + } + } + + base.ConfigureParameters(controller); + } + protected virtual bool ShouldBeRemove(ApplicationModel application, ControllerModel controllerModel) { return application.Controllers diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/StaticProxying/AbpHttpClientStaticProxyingOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/StaticProxying/AbpHttpClientStaticProxyingOptions.cs new file mode 100644 index 0000000000..f7c74303a9 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/StaticProxying/AbpHttpClientStaticProxyingOptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Http.Client.StaticProxying; + +public class AbpHttpClientStaticProxyingOptions +{ + public List BindingFromQueryTypes { get; } + + public AbpHttpClientStaticProxyingOptions() + { + BindingFromQueryTypes = new List(); + } +} diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs index 4dda1f5ca5..ab65909cf9 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs @@ -83,7 +83,7 @@ public class AbpNewtonsoftJsonSerializer : IJsonSerializer, ITransientDependency if (!camelCase) { - // Default contract resolver is AbpCamelCasePropertyNamesContractResolver} + //Default contract resolver is AbpCamelCasePropertyNamesContractResolver} settings.ContractResolver = new AbpDefaultContractResolver(RootServiceProvider.GetRequiredService()); } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index de89dfa506..6c009894e3 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -610,7 +610,7 @@ public class MongoDbRepository return Task.CompletedTask; } - private void TriggerEntityCreateEvents(TEntity entity) + protected virtual void TriggerEntityCreateEvents(TEntity entity) { EntityChangeEventHelper.PublishEntityCreatedEvent(entity); } diff --git a/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj b/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj index da83bc80f4..77da44eb47 100644 --- a/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj +++ b/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj @@ -1,20 +1,21 @@ - - - - - - - netstandard2.0 - Volo.Abp.MultiLingualObject - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - false - false - false - - - - - - - - + + + + + + + netstandard2.0 + Volo.Abp.MultiLingualObject + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + enable + + + + + + + diff --git a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs index 904f3abd32..4edbd984bd 100644 --- a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs +++ b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs @@ -5,16 +5,30 @@ namespace Volo.Abp.MultiLingualObjects; public interface IMultiLingualObjectManager { - Task GetTranslationAsync( + Task GetTranslationAsync( TMultiLingual multiLingual, - string culture = null, + string? culture = null, bool fallbackToParentCultures = true) where TMultiLingual : IMultiLingualObject where TTranslation : class, IObjectTranslation; - Task GetTranslationAsync( - ICollection translations, - string culture = null, + Task GetTranslationAsync( + IEnumerable translations, + string? culture = null, bool fallbackToParentCultures = true) + where TTranslation : class, IObjectTranslation; + + + Task> GetBulkTranslationsAsync( + IEnumerable> translationsCombined, + string? culture = null, + bool fallbackToParentCultures = true) + where TTranslation : class, IObjectTranslation; + + Task> GetBulkTranslationsAsync( + IEnumerable multiLinguals, + string? culture = null, + bool fallbackToParentCultures = true) + where TMultiLingual : IMultiLingualObject where TTranslation : class, IObjectTranslation; } diff --git a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs index afb4509917..908431e997 100644 --- a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs +++ b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs @@ -19,16 +19,16 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD { SettingProvider = settingProvider; } - public virtual async Task GetTranslationAsync( - ICollection translations, - string culture, + public virtual async Task GetTranslationAsync( + IEnumerable translations, + string? culture, bool fallbackToParentCultures) where TTranslation : class, IObjectTranslation { culture ??= CultureInfo.CurrentUICulture.Name; - if (translations.IsNullOrEmpty()) + if (translations == null || !translations.Any()) { return null; } @@ -65,9 +65,9 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD return translation; } - public virtual Task GetTranslationAsync( + public virtual Task GetTranslationAsync( TMultiLingual multiLingual, - string culture = null, + string? culture = null, bool fallbackToParentCultures = true) where TMultiLingual : IMultiLingualObject where TTranslation : class, IObjectTranslation @@ -75,13 +75,13 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD return GetTranslationAsync(multiLingual.Translations, culture: culture, fallbackToParentCultures: fallbackToParentCultures); } - protected virtual TTranslation GetTranslationBasedOnCulturalRecursive( - CultureInfo culture, ICollection translations, int currentDepth) + protected virtual TTranslation? GetTranslationBasedOnCulturalRecursive( + CultureInfo culture, IEnumerable translations, int currentDepth) where TTranslation : class, IObjectTranslation { if (culture == null || culture.Name.IsNullOrWhiteSpace() || - translations.IsNullOrEmpty() || + translations == null || !translations.Any() || currentDepth > MaxCultureFallbackDepth) { return null; @@ -89,5 +89,108 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD var translation = translations.FirstOrDefault(pt => pt.Language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)); return translation ?? GetTranslationBasedOnCulturalRecursive(culture.Parent, translations, currentDepth + 1); - } + } + + public virtual async Task> GetBulkTranslationsAsync(IEnumerable> translationsCombined, string? culture, bool fallbackToParentCultures) + where TTranslation : class, IObjectTranslation + { + culture ??= CultureInfo.CurrentUICulture.Name; + + if (translationsCombined == null || !translationsCombined.Any()) + { + return new(); + } + + var someHaveNoTranslations = false; + var res = new List(); + foreach (var translations in translationsCombined) + { + if (!translations.Any()) + { + //if the src has no translations, don't try to find a translation + res.Add(null); + continue; + } + var translation = translations.FirstOrDefault(pt => pt.Language == culture); + if (translation != null) + { + res.Add(translation); + } + else + { + if (fallbackToParentCultures) + { + translation = GetTranslationBasedOnCulturalRecursive( + CultureInfo.CurrentUICulture.Parent, + translations, + 0 + ); + + if (translation != null) + { + res.Add(translation); + } + else + { + res.Add(null); + someHaveNoTranslations = true; + } + } + else + { + res.Add(null); + someHaveNoTranslations = true; + } + } + } + + + if (someHaveNoTranslations) + { + var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); + + var index = 0; + foreach (var translations in translationsCombined) + { + if (!translations.Any()) + { + //don't try to find a translation + } + else + { + var translation = res[index]; + if (translation != null) + { + continue; + } + translation = translations.FirstOrDefault(pt => pt.Language == defaultLanguage); + if (translation != null) + { + res[index] = translation; + } + else + { + res[index] = translations.FirstOrDefault(); + } + } + index++; + } + } + return res; + } + + public virtual async Task> GetBulkTranslationsAsync(IEnumerable multiLinguals, string? culture, bool fallbackToParentCultures) + where TMultiLingual : IMultiLingualObject + where TTranslation : class, IObjectTranslation + { + var resInitial = await GetBulkTranslationsAsync(multiLinguals.Select(x => x.Translations), culture, fallbackToParentCultures); + var index = 0; + var res = new List<(TMultiLingual entity, TTranslation? translation)>(); + foreach (var item in multiLinguals) + { + var t = resInitial[index++]; + res.Add((item, t)); + } + return res; + } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs index 75ca252ca7..921aca8d7e 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs @@ -63,7 +63,7 @@ public class AbpSecurityModule : AbpModule { var contributorTypes = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (typeof(IAbpClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs index ebaaef54e1..eb05fbb949 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs @@ -36,7 +36,7 @@ public class AbpSettingsModule : AbpModule { var definitionProviders = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (typeof(ISettingDefinitionProvider).IsAssignableFrom(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleController.cs b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleController.cs index 4d061d0195..dffb4b8a3a 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleController.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleController.cs @@ -12,16 +12,16 @@ namespace Volo.Abp.Swashbuckle; [ApiExplorerSettings(IgnoreApi = true)] public class AbpSwashbuckleController : AbpController { - private readonly IAbpAntiForgeryManager _antiForgeryManager; + protected readonly IAbpAntiForgeryManager AntiForgeryManager; public AbpSwashbuckleController(IAbpAntiForgeryManager antiForgeryManager) { - _antiForgeryManager = antiForgeryManager; + AntiForgeryManager = antiForgeryManager; } [HttpGet] - public void SetCsrfCookie() + public virtual void SetCsrfCookie() { - _antiForgeryManager.SetCookie(); + AntiForgeryManager.SetCookie(); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingCoreModule.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingCoreModule.cs index 74f0589355..cce61484b3 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingCoreModule.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingCoreModule.cs @@ -23,7 +23,7 @@ public class AbpTextTemplatingCoreModule : AbpModule var definitionProviders = new List(); var contentContributors = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/fi.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/fi.json index f7301d9c31..0efd4fee49 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/fi.json +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/fi.json @@ -2,6 +2,6 @@ "culture": "fi", "texts": { "DisplayName:Abp.Timing.Timezone": "Aikavyöhyke", - "Description:Abp.Timing.Timezone": "Levityksen aikavyöhyke" + "Description:Abp.Timing.Timezone": "Sovelluksen aikavyöhyke" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenu.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenu.cs index 1a69de7f2c..9f48aebf97 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenu.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenu.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using JetBrains.Annotations; using Volo.Abp.Data; @@ -5,7 +6,7 @@ using Volo.Abp.UI.Navigation; namespace Volo.Abp.UI.Navigation; -public class ApplicationMenu : IHasMenuItems +public class ApplicationMenu : IHasMenuItems, IHasMenuGroups { /// /// Unique name of the menu in the application. @@ -31,6 +32,10 @@ public class ApplicationMenu : IHasMenuItems [NotNull] public ApplicationMenuItemList Items { get; } + /// + [NotNull] + public ApplicationMenuGroupList Groups { get; } + /// /// Can be used to store a custom object related to this menu. /// @@ -47,6 +52,7 @@ public class ApplicationMenu : IHasMenuItems DisplayName = displayName ?? Name; Items = new ApplicationMenuItemList(); + Groups = new ApplicationMenuGroupList(); } /// @@ -60,6 +66,17 @@ public class ApplicationMenu : IHasMenuItems return this; } + /// + /// Adds a to . + /// + /// to be added + /// This object + public ApplicationMenu AddGroup([NotNull] ApplicationMenuGroup group) + { + Groups.Add(group); + return this; + } + /// /// Adds a custom data item to with given key & value. /// diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuExtensions.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuExtensions.cs index eec549cf9c..624abde425 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuExtensions.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuExtensions.cs @@ -64,4 +64,54 @@ public static class ApplicationMenuExtensions return menuWithItems; } + + [NotNull] + public static ApplicationMenuGroup GetMenuGroup( + [NotNull] this IHasMenuGroups menuWithGroups, + string groupName) + { + var menuGroup = menuWithGroups.GetMenuGroupOrNull(groupName); + if (menuGroup == null) + { + throw new AbpException($"Could not find a group item with given name: {groupName}"); + } + + return menuGroup; + } + + [CanBeNull] + public static ApplicationMenuGroup GetMenuGroupOrNull( + [NotNull] this IHasMenuGroups menuWithGroups, + string menuGroupName) + { + Check.NotNull(menuWithGroups, nameof(menuWithGroups)); + + return menuWithGroups.Groups.FirstOrDefault(group => group.Name == menuGroupName); + } + + public static bool TryRemoveMenuGroup( + [NotNull] this IHasMenuGroups menuWithGroups, + string menuGroupName) + { + Check.NotNull(menuWithGroups, nameof(menuWithGroups)); + + return menuWithGroups.Groups.RemoveAll(group => group.Name == menuGroupName) > 0; + } + + [NotNull] + public static IHasMenuGroups SetMenuGroupOrder( + [NotNull] this IHasMenuGroups menuWithGroups, + string menuGroupName, + int order) + { + Check.NotNull(menuWithGroups, nameof(menuWithGroups)); + + var menuGroup = menuWithGroups.GetMenuGroupOrNull(menuGroupName); + if (menuGroup != null) + { + menuGroup.Order = order; + } + + return menuWithGroups; + } } diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroup.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroup.cs new file mode 100644 index 0000000000..469c0ad355 --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroup.cs @@ -0,0 +1,67 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.UI.Navigation; + +public class ApplicationMenuGroup +{ + private string _displayName; + + /// + /// Default value of a group item. + /// + public const int DefaultOrder = 1000; + + /// + /// Unique name of the group in the application. + /// + [NotNull] + public string Name { get; } + + /// + /// Display name of the group. + /// + [NotNull] + public string DisplayName { + get { return _displayName; } + set { + Check.NotNullOrWhiteSpace(value, nameof(value)); + _displayName = value; + } + } + + /// + /// Can be used to render the element with a specific Id for DOM selections. + /// + public string ElementId { get; set; } + + /// + /// The Display order of the group. + /// Default value: 1000. + /// + public int Order { get; set; } + + public ApplicationMenuGroup( + [NotNull] string name, + [NotNull] string displayName, + string elementId = null, + int order = DefaultOrder) + { + Check.NotNullOrWhiteSpace(name, nameof(name)); + Check.NotNullOrWhiteSpace(displayName, nameof(displayName)); + + Name = name; + DisplayName = displayName; + ElementId = elementId; + Order = order; + } + + private string GetDefaultElementId() + { + return "MenuGroup_" + Name; + } + + public override string ToString() + { + return $"[ApplicationMenuGroup] Name = {Name}"; + } +} diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroupList.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroupList.cs new file mode 100644 index 0000000000..f280f5d4f9 --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroupList.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.UI.Navigation; + +public class ApplicationMenuGroupList: List +{ + public ApplicationMenuGroupList() + { + + } + + public ApplicationMenuGroupList(int capacity) + : base(capacity) + { + + } + + public ApplicationMenuGroupList(IEnumerable collection) + : base(collection) + { + + } + + public void Normalize() + { + Order(); + } + + private void Order() + { + var orderedItems = this.OrderBy(item => item.Order).ToArray(); + Clear(); + AddRange(orderedItems); + } +} diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs index 32241ba7ac..dc1614e3e5 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs @@ -92,6 +92,11 @@ public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers public string CssClass { get; set; } + /// + /// Can be used to group menu items. + /// + public string GroupName { get; set; } + public ApplicationMenuItem( [NotNull] string name, [NotNull] string displayName, @@ -101,6 +106,7 @@ public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers>(); Items = new ApplicationMenuItemList(); diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IHasMenuGroups.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IHasMenuGroups.cs new file mode 100644 index 0000000000..fbbe7a63a4 --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IHasMenuGroups.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.UI.Navigation; + +public interface IHasMenuGroups +{ + /// + /// Menu groups. + /// + ApplicationMenuGroupList Groups { get; } +} diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs index aa933c6e09..349aa8e5a5 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs @@ -96,6 +96,7 @@ public class MenuManager : IMenuManager, ITransientDependency } NormalizeMenu(menu); + NormalizeMenuGroup(menu); return menu; } @@ -159,4 +160,23 @@ public class MenuManager : IMenuManager, ITransientDependency menuWithItems.Items.Normalize(); } + + protected virtual void NormalizeMenuGroup(ApplicationMenu applicationMenu) + { + foreach (var menuGroup in applicationMenu.Items.Where(x => !x.GroupName.IsNullOrWhiteSpace()).GroupBy(x => x.GroupName)) + { + var group = applicationMenu.GetMenuGroupOrNull(menuGroup.First().GroupName); + if (group != null) + { + continue; + } + + foreach (var menuItem in menuGroup) + { + menuItem.GroupName = null; + } + } + + applicationMenu.Groups.Normalize(); + } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json index 4717b1fe9b..bcbab76211 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json @@ -48,6 +48,7 @@ "Search": "بحث", "ItemWillBeDeletedMessageWithFormat": "سيتم حذف {0}!", "ItemWillBeDeletedMessage": "سوف يتم حذف هذا البند!", - "ManageYourAccount": "إدارة حسابك" + "ManageYourAccount": "إدارة حسابك", + "OthersGroup": "آخرون" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json index 1fe7ea6a23..5dc41952a6 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json @@ -48,6 +48,7 @@ "Search": "Vyhledávání", "ItemWillBeDeletedMessageWithFormat": "{0} bude smazáno!", "ItemWillBeDeletedMessage": "Tato položka bude smazána!", - "ManageYourAccount": "Spravujte svůj účet" + "ManageYourAccount": "Spravujte svůj účet", + "OthersGroup": "Jiný" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json index edb4f7563a..dc99f75586 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json @@ -48,6 +48,7 @@ "Search": "Suche", "ItemWillBeDeletedMessageWithFormat": "{0} wird gelöscht!", "ItemWillBeDeletedMessage": "Dieses Element wird gelöscht!", - "ManageYourAccount": "Verwalten Sie Ihr Benutzerkonto" + "ManageYourAccount": "Verwalten Sie Ihr Benutzerkonto", + "OthersGroup":"Andere" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json index 6bba8ee140..24a7642eb0 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json @@ -48,6 +48,7 @@ "Search": "Αναζήτηση", "ItemWillBeDeletedMessageWithFormat": "Το {0} θα διαγραφεί", "ItemWillBeDeletedMessage": "Αυτό το στοιχείο θα διαγραφεί!", - "ManageYourAccount": "Διαχείριση Λογαριασμού" + "ManageYourAccount": "Διαχείριση Λογαριασμού", + "OthersGroup":"άλλος" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json index a26c5ddf9e..4b88af7d26 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json @@ -48,6 +48,7 @@ "Search": "Search", "ItemWillBeDeletedMessageWithFormat": "{0} will be deleted!", "ItemWillBeDeletedMessage": "This item will be deleted!", - "ManageYourAccount": "Manage your account" + "ManageYourAccount": "Manage your account", + "OthersGroup": "Other" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json index 2a22849731..b97ca73d1d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json @@ -48,6 +48,9 @@ "Search": "Search", "ItemWillBeDeletedMessageWithFormat": "{0} will be deleted!", "ItemWillBeDeletedMessage": "This item will be deleted!", - "ManageYourAccount": "Manage your account" + "ManageYourAccount": "Manage your account", + "OthersGroup": "Other", + "Today": "Today", + "Apply": "Apply" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json index f2b40a785b..c28e8049bc 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json @@ -48,6 +48,7 @@ "Search": "Buscar", "ItemWillBeDeletedMessageWithFormat": "{0} serán borrados!", "ItemWillBeDeletedMessage": "Este elemento será borrado", - "ManageYourAccount": "Administrar cuenta" + "ManageYourAccount": "Administrar cuenta", + "OthersGroup": "Otra" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json index c2f81b9c52..4fd179573b 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json @@ -48,6 +48,7 @@ "Search": "جستجو", "ItemWillBeDeletedMessageWithFormat": "{0} حذف خواهد شد!", "ItemWillBeDeletedMessage": "این مورد حذف خواهد شد!", - "ManageYourAccount": "حساب خود را مدیریت کنید" + "ManageYourAccount": "حساب خود را مدیریت کنید", + "OthersGroup": "دیگر" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json index 1f5c6b7724..25811c92eb 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json @@ -1,21 +1,21 @@ { "culture": "fi", "texts": { - "Languages": "Kieli (kielet", + "Languages": "Kielet", "AreYouSure": "Oletko varma?", - "Cancel": "Peruuttaa", - "Clear": "Asia selvä", - "Yes": "Joo", + "Cancel": "Peruuta", + "Clear": "Tyhjennä", + "Yes": "Kyllä", "No": "Ei", "Ok": "Ok", - "Close": "kiinni", - "Save": "Tallentaa", - "SavingWithThreeDot": "Tallentaa...", + "Close": "Sulje", + "Save": "Tallenna", + "SavingWithThreeDot": "Tallennetaan...", "Actions": "Toiminnot", - "Delete": "Poistaa", + "Delete": "Poista", "SuccessfullyDeleted": "Poistettu onnistuneesti", - "Edit": "Muokata", - "Refresh": "virkistää", + "Edit": "Muokkaa", + "Refresh": "Virkistä", "Language": "Kieli", "LoadMore": "Lataa lisää", "ProcessingWithThreeDot": "Käsitellään...", @@ -23,22 +23,22 @@ "Welcome": "Tervetuloa", "Login": "Kirjaudu sisään", "Register": "Rekisteröidy", - "Logout": "Kirjautua ulos", + "Logout": "Kirjaudu ulos", "Submit": "Lähetä", "Back": "Takaisin", "PagerSearch": "Hae", "PagerNext": "Seuraava", "PagerPrevious": "Edellinen", "PagerFirst": "Ensimmäinen", - "PagerLast": "Kestää", - "PagerInfo": "Näytetään _START_ - _END_/_TOTAL_ merkinnästä", - "PagerInfo{0}{1}{2}": "Näytetään {0} - {1}/{2} merkinnästä", - "PagerInfoEmpty": "Näytetään 0 - 0/0 merkinnästä", - "PagerInfoFiltered": "(suodatettu _MAX_ merkinnän kokonaismäärästä)", - "NoDataAvailableInDatatable": "Tietoja ei ole käytettävissä", + "PagerLast": "Viimeinen", + "PagerInfo": "Näytetään _START_ - _END_/_TOTAL_ tietueesta", + "PagerInfo{0}{1}{2}": "Näytetään {0} - {1}/{2} tietueesta", + "PagerInfoEmpty": "Näytetään 0 - 0/0 tietueesta", + "PagerInfoFiltered": "(suodatettu _MAX_ tietueen kokonaismäärästä)", + "NoDataAvailableInDatatable": "Tietoja ei löydy", "Total": "kaikki yhteensä", "Selected": "valittu", - "PagerShowMenuEntries": "Näytä _MENU_ merkinnät", + "PagerShowMenuEntries": "Näytä _MENU_ tietueet", "DatatableActionDropdownDefaultText": "Toiminnot", "ChangePassword": "Vaihda salasana", "PersonalInfo": "Profiilini", @@ -48,6 +48,7 @@ "Search": "Hae", "ItemWillBeDeletedMessageWithFormat": "{0} poistetaan!", "ItemWillBeDeletedMessage": "Tämä kohde poistetaan!", - "ManageYourAccount": "Hallitse tiliäsi" + "ManageYourAccount": "Hallitse tiliäsi", + "OthersGroup": "Muut" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json index 0fc43162b2..b850315dfe 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json @@ -48,6 +48,7 @@ "Search": "Recherche", "ItemWillBeDeletedMessageWithFormat": "{0} sera supprimé!", "ItemWillBeDeletedMessage": "Cet objet va être supprimé!", - "ManageYourAccount": "Gérer votre compte" + "ManageYourAccount": "Gérer votre compte", + "OthersGroup": "Autre" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json index 2c451bc797..0a20f1dcbb 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json @@ -48,6 +48,7 @@ "Search": "खोज", "ItemWillBeDeletedMessageWithFormat": "{0} हटा दिया जाएगा!", "ItemWillBeDeletedMessage": "यह आइटम हटा दिया जाएगा!", - "ManageYourAccount": "अपने खाते का प्रबंधन" + "ManageYourAccount": "अपने खाते का प्रबंधन", + "OthersGroup": "अन्य" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json index 9cbef79b12..a6285a4cee 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json @@ -48,6 +48,7 @@ "Search": "Pretraga", "ItemWillBeDeletedMessageWithFormat": "{0} zapis će biti obrisan!", "ItemWillBeDeletedMessage": "Ovaj zapis će biti obrisan!", - "ManageYourAccount": "Upravljaj korisničkim računom" + "ManageYourAccount": "Upravljaj korisničkim računom", + "OthersGroup": "Drugi" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json index 595e2f8b62..98c0c46f54 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json @@ -48,6 +48,7 @@ "Search": "Keresés", "ItemWillBeDeletedMessageWithFormat": "{0} törlésre kerül!", "ItemWillBeDeletedMessage": "Ez az elem törlődik!", - "ManageYourAccount": "Kezelje fiókját" + "ManageYourAccount": "Kezelje fiókját", + "OthersGroup": "Egyéb" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json index a931570496..12b1565c2e 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json @@ -48,6 +48,7 @@ "Search": "Leita", "ItemWillBeDeletedMessageWithFormat": "{0} verður eytt!", "ItemWillBeDeletedMessage": "Þessum lið verður eytt!", - "ManageYourAccount": "Stillingar notandaaðgangs" + "ManageYourAccount": "Stillingar notandaaðgangs", + "OthersGroup": "Annað" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json index 3c4328619e..f07bf67e2d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json @@ -48,6 +48,7 @@ "Search": "Ricerca", "ItemWillBeDeletedMessageWithFormat": "{0} sarà eliminato!", "ItemWillBeDeletedMessage": "Questo elemento sarà eliminato!", - "ManageYourAccount": "Gestisci il tuo account" + "ManageYourAccount": "Gestisci il tuo account", + "OthersGroup": "Altra" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json index d23d2356b4..5875fe07b9 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json @@ -48,6 +48,7 @@ "Search": "Zoeken", "ItemWillBeDeletedMessageWithFormat": "{0} wordt verwijderd!", "ItemWillBeDeletedMessage": "Dit item wordt verwijderd!", - "ManageYourAccount": "Beheer uw account" + "ManageYourAccount": "Beheer uw account", + "OthersGroup": "Ander" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json index e7cd503b4f..f2b8227137 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json @@ -48,6 +48,7 @@ "Search": "Szukaj", "ItemWillBeDeletedMessageWithFormat": "{0} zostanie usunięty!", "ItemWillBeDeletedMessage": "Ten element zostanie usunięty!", - "ManageYourAccount": "Zarządzaj kontem" + "ManageYourAccount": "Zarządzaj kontem", + "OthersGroup": "Inny" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json index 03855ec698..928c9299fb 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json @@ -48,6 +48,7 @@ "Search": "Procurar", "ItemWillBeDeletedMessageWithFormat": "{0} será excluído!", "ItemWillBeDeletedMessage": "Este item será excluído!", - "ManageYourAccount": "Gerenciar sua conta" + "ManageYourAccount": "Gerenciar sua conta", + "OthersGroup": "Outra" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json index 7064064671..544b893c39 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json @@ -48,6 +48,7 @@ "Search": "Caută", "ItemWillBeDeletedMessageWithFormat": "{0} va fi şters!", "ItemWillBeDeletedMessage": "Acest articol va fi şters!", - "ManageYourAccount": "Administraţi-vă contul" + "ManageYourAccount": "Administraţi-vă contul", + "OthersGroup": "Alte" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json index dc648a2138..66f24ca65d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json @@ -48,6 +48,7 @@ "Search": "поиск", "ItemWillBeDeletedMessageWithFormat": "{0} будет удален!", "ItemWillBeDeletedMessage": "Этот предмет будет удален!", - "ManageYourAccount": "Настройте свой аккаунт" + "ManageYourAccount": "Настройте свой аккаунт", + "OthersGroup": "Другой" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json index 59b2628310..28278f04f2 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json @@ -48,6 +48,7 @@ "Search": "Hľadať", "ItemWillBeDeletedMessageWithFormat": "{0} sa vymaže!", "ItemWillBeDeletedMessage": "Táto položka bude vymazaná!", - "ManageYourAccount": "Spravovať svoje konto" + "ManageYourAccount": "Spravovať svoje konto", + "OthersGroup": "Iné" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json index 7a50fbaf25..c05c1dc202 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json @@ -48,6 +48,7 @@ "Search": "Iskanje", "ItemWillBeDeletedMessageWithFormat": "{0} bo izbrisan!", "ItemWillBeDeletedMessage": "Ta element bo izbrisan!", - "ManageYourAccount": "Upravljajte svoj račun" + "ManageYourAccount": "Upravljajte svoj račun", + "OthersGroup": "Ostalo" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json index 67ae56793a..9dc46b85a8 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json @@ -48,6 +48,9 @@ "Search": "Arama", "ItemWillBeDeletedMessageWithFormat": "{0} silinecektir!", "ItemWillBeDeletedMessage": "Bu nesne silinecektir!", - "ManageYourAccount": "Hesap yönetimi" + "ManageYourAccount": "Hesap yönetimi", + "OthersGroup": "Diğer", + "Today": "Bugün", + "Apply": "Uygula" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json index 4062990743..1b8bc5fb5b 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json @@ -48,6 +48,7 @@ "Search": "Tìm kiếm", "ItemWillBeDeletedMessageWithFormat": "{0} sẽ bị xóa!", "ItemWillBeDeletedMessage": "Vật phẩm này sẽ bị xoá!", - "ManageYourAccount": "Quản lý tài khoản của bạn" + "ManageYourAccount": "Quản lý tài khoản của bạn", + "OthersGroup": "Khác" } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json index 7c9d2798ff..133faa7386 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json @@ -48,6 +48,7 @@ "Search": "搜索", "ItemWillBeDeletedMessageWithFormat": "{0} 将被删除!", "ItemWillBeDeletedMessage": "此项将被删除!", - "ManageYourAccount": "管理你的账户" + "ManageYourAccount": "管理你的账户", + "OthersGroup": "其他" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json index 449dd67480..7bac7da3b8 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json @@ -48,6 +48,7 @@ "Search": "查詢", "ItemWillBeDeletedMessageWithFormat": "{0} 將被刪除!", "ItemWillBeDeletedMessage": "此項目將被刪除!", - "ManageYourAccount": "管理個人帳號" + "ManageYourAccount": "管理個人帳號", + "OthersGroup": "其他" } } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/AbpUnitOfWorkModule.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/AbpUnitOfWorkModule.cs index 036e101397..541340183a 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/AbpUnitOfWorkModule.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/AbpUnitOfWorkModule.cs @@ -7,6 +7,6 @@ public class AbpUnitOfWorkModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(UnitOfWorkInterceptorRegistrar.RegisterIfNeeded); + context.Services.OnRegistered(UnitOfWorkInterceptorRegistrar.RegisterIfNeeded); } } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index cc29fd23a8..8713b2ec7b 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -193,7 +193,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency if (_databaseApis.ContainsKey(key)) { - throw new AbpException("There is already a database API in this unit of work with given key: " + key); + throw new AbpException("There is already a database API in this unit of work with given key."); } _databaseApis.Add(key, api); @@ -221,7 +221,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency if (_transactionApis.ContainsKey(key)) { - throw new AbpException("There is already a transaction API in this unit of work with given key: " + key); + throw new AbpException("There is already a transaction API in this unit of work with given key."); } _transactionApis.Add(key, api); diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs index 476bf2de06..321599c35c 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs @@ -16,7 +16,7 @@ public class AbpValidationModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded); + context.Services.OnRegistered(ValidationInterceptorRegistrar.RegisterIfNeeded); AutoAddObjectValidationContributors(context.Services); } @@ -39,7 +39,7 @@ public class AbpValidationModule : AbpModule { var contributorTypes = new List(); - services.OnRegistred(context => + services.OnRegistered(context => { if (typeof(IObjectValidationContributor).IsAssignableFrom(context.ImplementationType)) { diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json index 2cdb6999b5..252bee394f 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "يجب أن يكون الحقل {0} سلسلة أحرف طولها {1} كحد أدنى و {2} كحد أقصى.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "الحقل {0} ليس عنوانا URL صالحًا مؤهلاً بالكامل سواء كان عنوان http أو https أو ftp", "The field {0} is invalid.": "الحقل {0} غير صالح.", + "The value '{0}' is invalid.": "القيمة '{0}' غير صالحة.", + "The field {0} must be a number.": "يجب أن يكون الحقل {0} رقمًا.", + "The field must be a number.": "يجب أن يكون الحقل رقمًا.", "ThisFieldIsNotAValidCreditCardNumber.": "هذا الحقل لا يمثل رقم بطاقة ائتمان صالح.", "ThisFieldIsNotValid.": "هذا الحقل غير صالح.", "ThisFieldIsNotAValidEmailAddress.": "هذا الحقل لا يمثل عنوان بريد إلكتروني صالح.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json index dce07f8c91..60e2113906 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Pole {0} musí být řetězec o minimální délce {2} a maximální délce {1} znaků.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Pole {0} není platná plně kvalifikovaná adresa http, https, nebo ftp URL.", "The field {0} is invalid.": "Pole {0} je neplatné.", + "The value '{0}' is invalid.": "Hodnota '{0}' je neplatná.", + "The field {0} must be a number.": "Pole {0} musí být číslo.", + "The field must be a number.": "Pole musí být číslo.", "ThisFieldIsNotAValidCreditCardNumber.": "V poli {0} není platné číslo kreditní karty.", "ThisFieldIsNotValid.": "{0} není platný.", "ThisFieldIsNotAValidEmailAddress.": "V poli {0} není platný email.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json index a72a44a3bb..209d8536ae 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer Mindestlänge von {2} und einer Maximallänge von {1} sein.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Das Feld {0} ist keine gültige, vollqualifizierte http-, https- oder ftp-URL.", "The field {0} is invalid.": "Das Feld {0} ist ungültig.", + "The value '{0}' is invalid.": "Der Wert '{0}' ist ungültig.", + "The field {0} must be a number.": "Das Feld {0} muss eine Zahl sein.", + "The field must be a number.": "Das Feld muss eine Zahl sein.", "ThisFieldIsNotAValidCreditCardNumber.": "Dieses Feld ist keine gültige Kreditkartennummer.", "ThisFieldIsNotValid.": "Dieses Feld ist ungültig.", "ThisFieldIsNotAValidEmailAddress.": "Dieses Feld ist keine gültige E-Mail-Adresse.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/el.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/el.json index e2df0315f3..bed1964ac4 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/el.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/el.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Το πεδίο {0} πρέπει να είναι μια συμβολοσειρά με ελάχιστο μήκος {2} και μέγιστο μήκος {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Το πεδίο {0} δεν είναι έγκυρη πλήρως πιστοποιημένη διεύθυνση URL http, https ή ftp.", "The field {0} is invalid.": "Το πεδίο {0} δεν είναι έγκυρο.", + "The value '{0}' is invalid.": "Η τιμή '{0}' δεν είναι έγκυρη.", + "The field {0} must be a number.": "Το πεδίο {0} πρέπει να είναι αριθμός.", + "The field must be a number.": "Το πεδίο πρέπει να είναι αριθμός.", "ThisFieldIsNotAValidCreditCardNumber.": "Αυτό το πεδίο δεν περιέχει έγκυρο αριθμό πιστωτικής κάρτας.", "ThisFieldIsNotValid.": "Αυτό το πεδίο δεν είναι έγκυρο.", "ThisFieldIsNotAValidEmailAddress.": "Αυτό το πεδίο δεν περιέχει έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en-GB.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en-GB.json index a53b55a853..0874616561 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en-GB.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en-GB.json @@ -17,6 +17,9 @@ "The field {0} is invalid.": "The field {0} is invalid.", "ThisFieldIsNotAValidCreditCardNumber.": "This field is not a valid credit card number.", "ThisFieldIsNotValid.": "This field is not valid.", + "The value '{0}' is invalid.": "The value '{0}' is invalid.", + "The field {0} must be a number.": "The field {0} must be a number.", + "The field must be a number.": "The field must be a number.", "ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "This field only accepts files with the following extensions: {0}", "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json index 29035fb57f..8a866ef936 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "The {0} field is not a valid fully-qualified http, https, or ftp URL.", "The field {0} is invalid.": "The field {0} is invalid.", + "The value '{0}' is invalid.": "The value '{0}' is invalid.", + "The field {0} must be a number.": "The field {0} must be a number.", + "The field must be a number.": "The field must be a number.", "ThisFieldIsNotAValidCreditCardNumber.": "This field is not a valid credit card number.", "ThisFieldIsNotValid.": "This field is not valid.", "ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.", @@ -33,4 +36,4 @@ "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "This field is not a valid fully-qualified http, https, or ftp URL.", "ThisFieldIsInvalid.": "This field is invalid." } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json index 55884667f6..969cb3f3a9 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "El campo {0} debe ser una cadena con una longitud mínima de {2} y máxima de {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "El campo {0} no es una URL (http, https o ftp) valida.", "The field {0} is invalid.": "El campo {0} no es valido.", + "The value '{0}' is invalid.": "El valor '{0}' no es válido.", + "The field {0} must be a number.": "El campo {0} debe ser un número.", + "The field must be a number.": "El campo debe ser un número.", "ThisFieldIsNotAValidCreditCardNumber.": "El campo no es un número de tarjeta de crédito valido.", "ThisFieldIsNotValid.": "Este campo no es valido.", "ThisFieldIsNotAValidEmailAddress.": "Este campo no es una dirección de e-mail valida.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fa.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fa.json index bdec7afe06..39ec042dcb 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fa.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fa.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "فیلد {0} باید رشته ای با حداقل طول {2} و حداکثر باشد طول {1}. ", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "فیلد {0} یک نشانی اینترنتی http ، https ، یا ftp کاملاً واجد شرایط نیست.", "The field {0} is invalid.": "فیلد {0} نامعتبر است.", + "The value '{0}' is invalid.": "مقدار '{0}' نامعتبر است.", + "The field {0} must be a number.": "فیلد {0} باید یک عدد باشد.", + "The field must be a number.": "فیلد باید یک عدد باشد.", "ThisFieldIsNotAValidCreditCardNumber.": "این قسمت شماره کارت اعتباری معتبری نیست.", "ThisFieldIsNotValid.": "این قسمت معتبر نیست.", "ThisFieldIsNotAValidEmailAddress.": "این قسمت آدرس ایمیل معتبری نیست.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fi.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fi.json index f4501b87cf..7219c45ccf 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fi.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fi.json @@ -6,29 +6,34 @@ "{0} is not valid.": "{0} ei kelpaa.", "The {0} field is not a valid e-mail address.": "{0} -kenttä ei ole kelvollinen sähköpostiosoite.", "The {0} field only accepts files with the following extensions: {1}": "{0} -kenttä hyväksyy vain tiedostot, joilla on seuraavat laajennukset: {1}", - "The field {0} must be a string or array type with a maximum length of '{1}'.": "Kentän {0} on oltava merkkijono- tai taulukotyyppi, jonka enimmäispituus on {1}.", - "The field {0} must be a string or array type with a minimum length of '{1}'.": "Kentän {0} on oltava merkkijono tai matriisityyppi, jonka vähimmäispituus on {1}.", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "Kentän {0} on oltava merkkijono- tai taulukko, jonka enimmäispituus on {1}.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "Kentän {0} on oltava merkkijono tai taulukko, jonka vähimmäispituus on {1}.", "The {0} field is not a valid phone number.": "{0} -kenttä ei ole kelvollinen puhelinnumero.", "The field {0} must be between {1} and {2}.": "Kentän {0} on oltava välillä {1} - {2}.", "The field {0} must match the regular expression '{1}'.": "Kenttä {0} ei vastaa pyydettyä muotoa.", "The {0} field is required.": "{0} -kenttä on pakollinen.", "The field {0} must be a string with a maximum length of {1}.": "Kentän {0} on oltava merkkijono, jonka enimmäispituus on {1}.", "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Kentän {0} on oltava merkkijono, jonka vähimmäispituus on {2} ja enimmäispituus {1}.", - "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "{0} -kenttä ei ole kelvollinen täysin hyväksytty http, https tai ftp URL.", + "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "{0} -kenttä ei ole kelvollinen http, https tai ftp URL-osoite.", "The field {0} is invalid.": "Kenttä {0} on virheellinen.", + "The value '{0}' is invalid.": "Arvo '{0}' on virheellinen.", + "The field {0} must be a number.": "Kentän {0} on oltava numero.", + "The field must be a number.": "Kentän on oltava numero.", "ThisFieldIsNotAValidCreditCardNumber.": "Tämä kenttä ei ole kelvollinen luottokortin numero.", "ThisFieldIsNotValid.": "Tämä kenttä ei kelpaa.", "ThisFieldIsNotAValidEmailAddress.": "Tämä kenttä ei ole kelvollinen sähköpostiosoite.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Tämä kenttä hyväksyy vain tiedostot, joilla on seuraavat laajennukset: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Tämän kentän on oltava merkkijono- tai taulukotyyppi, jonka enimmäispituus on {0}.", - "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Tämän kentän on oltava merkkijono- tai taulukotyyppi, jonka vähimmäispituus on {0}.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Tämän kentän on oltava merkkijono- tai taulukko, jonka enimmäispituus on {0}.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Tämän kentän on oltava merkkijono- tai taulukko, jonka vähimmäispituus on {0}.", "ThisFieldIsNotAValidPhoneNumber.": "Tämä kenttä ei ole kelvollinen puhelinnumero.", "ThisFieldMustBeBetween{0}And{1}": "Tämän kentän on oltava välillä {0} - {1}.", + "ThisFieldMustBeGreaterThanOrEqual{0}": "Tämän kentän on oltava suurempi tai yhtä suuri kuin {0}.", + "ThisFieldMustBeLessOrEqual{0}": "Tämän kentän on oltava pienempi tai yhtä suuri kuin {0}.", "ThisFieldMustMatchTheRegularExpression{0}": "Tämän kentän on vastattava säännöllistä lauseketta {0}.", "ThisFieldIsRequired.": "Tämä kenttä pitää täyttää.", "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Tämän kentän on oltava merkkijono, jonka enimmäispituus on {0}.", "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Tämän kentän on oltava merkkijono, jonka vähimmäispituus on {1} ja enimmäispituus {0}.", - "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Tämä kenttä ei ole kelvollinen täysin hyväksytty http, https tai ftp URL.", + "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Tämä kenttä ei ole kelvollinen http, https tai ftp URL-osoite.", "ThisFieldIsInvalid.": "Tämä kenttä on virheellinen." } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fr.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fr.json index 558bc9ca11..0adf748127 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fr.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/fr.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Le champ {0} doit être une chaîne d'une longueur minimale de {2} et d'une longueur maximale de {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Le champ {0} n'est pas une URL http, https ou ftp complète valide.", "The field {0} is invalid.": "Le champ {0} n'est pas valide.", + "The value '{0}' is invalid.": "La valeur '{0}' n'est pas valide.", + "The field {0} must be a number.": "Le champ {0} doit être un nombre.", + "The field must be a number.": "Le champ doit être un nombre.", "ThisFieldIsNotAValidCreditCardNumber.": "Ce champ n'est pas un numéro de carte de crédit valide.", "ThisFieldIsNotValid.": "Ce champ n'est pas valide.", "ThisFieldIsNotAValidEmailAddress.": "Ce champ n'est pas une adresse e-mail valide.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hi.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hi.json index fab1a394b4..d7ae279c18 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hi.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hi.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "फ़ील्ड {0} की लंबाई न्यूनतम {2} और अधिकतम लंबाई {1} होनी चाहिए।", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "{0} फ़ील्ड मान्य पूर्णत: योग्य http, https, या ftp URL नहीं है।", "The field {0} is invalid.": "फ़ील्ड {0} अमान्य है।", + "The value '{0}' is invalid.": "मान '{0}' अमान्य है।", + "The field {0} must be a number.": "फ़ील्ड {0} एक संख्या होनी चाहिए।", + "The field must be a number.": "फ़ील्ड एक संख्या होनी चाहिए।", "ThisFieldIsNotAValidCreditCardNumber.": "यह फ़ील्ड मान्य क्रेडिट कार्ड नंबर नहीं है।", "ThisFieldIsNotValid.": "यह फ़ील्ड मान्य नहीं है।", "ThisFieldIsNotAValidEmailAddress.": "यह फ़ील्ड मान्य ई-मेल पता नहीं है।", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hr.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hr.json index 6bb5cf7d5e..dcc01e9b0a 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hr.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hr.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Polje {0} mora biti text sa minimalnom du�inom od {2} i maksimalnom dužinom od {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "{0} nije valjan potpuno kvalificirani http, https, ili ftp URL.", "The field {0} is invalid.": "Polje {0} nije važeće.", + "The value '{0}' is invalid.": "Vrijednost '{0}' nije važeća.", + "The field {0} must be a number.": "Polje {0} mora biti broj.", + "The field must be a number.": "Polje mora biti broj.", "ThisFieldIsNotAValidCreditCardNumber.": "Ovo polje nije važeći broj kreditne kartice.", "ThisFieldIsNotValid.": "Ovo polje nije valjano.", "ThisFieldIsNotAValidEmailAddress.": "Ovo polje nije valjana e-mail adresa.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hu.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hu.json index 3db0f6f5c0..b06d9d3c47 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hu.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/hu.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "A {0} mezőnek szöveget kell tartalmaznia minimum {2} és maximum {1} hosszan.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "A {0} mező nem érvényes fully-qualified http, https, vagy ftp URL cím.", "The field {0} is invalid.": "A {0} mező nem érvényes.", + "The value '{0}' is invalid.": "A(z) '{0}' érték érvénytelen.", + "The field {0} must be a number.": "A(z) {0} mezőnek számnak kell lennie.", + "The field must be a number.": "A mezőnek számnak kell lennie.", "ThisFieldIsNotAValidCreditCardNumber.": "A mező nem érvényes bankkártya számot tartalmaz.", "ThisFieldIsNotValid.": "A mező nem érvényes.", "ThisFieldIsNotAValidEmailAddress.": "A mező nem érvényes email cím.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/is.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/is.json index 8f3ef3ae88..1f9c858d2a 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/is.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/is.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Reiturinn {0} verður að vera strengur með lágmarkslengd {2} og hámarkslengd {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Reiturinn {0} er ekki fullgild http, https, eða ftp slóð.", "The field {0} is invalid.": "Reiturinn {0} er ekki rétt útfylltur.", + "The value '{0}' is invalid.": "Gildið '{0}' er ógilt.", + "The field {0} must be a number.": "Reiturinn {0} verður að vera númer.", + "The field must be a number.": "Reiturinn verður að vera númer.", "ThisFieldIsNotAValidCreditCardNumber.": "Þessi reitur hefur ekki gilt kreditkortanúmer.", "ThisFieldIsNotValid.": "Reiturinn er ekki rétt útfylltur.", "ThisFieldIsNotAValidEmailAddress.": "Þessi reitur hefur ekki gilt netfang.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json index bad68c117f..4cf02600bc 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Il campo {0} deve essere una stringa con una lunghezza minima di {2} e una lunghezza massima di {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Il campo {0} non è un URL http, https o ftp completo e valido.", "The field {0} is invalid.": "Il campo {0} non è valido.", + "The value '{0}' is invalid.": "Il valore '{0}' non è valido.", + "The field {0} must be a number.": "Il campo {0} deve essere un numero.", + "The field must be a number.": "Il campo deve essere un numero.", "ThisFieldIsNotAValidCreditCardNumber.": "Questo campo non è un numero di carta di credito valido.", "ThisFieldIsNotValid.": "Questo campo non è valido.", "ThisFieldIsNotAValidEmailAddress.": "Questo campo non è un indirizzo e-mail valido.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json index ff0edfa54d..096d2fcbc3 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Het veld {0} moet een tekenreeks zijn met een minimale lengte van {2} en een maximale lengte van {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Het veld {0} is geen geldige, volledig gekwalificeerde http-, https- of ftp-URL.", "The field {0} is invalid.": "Het veld {0} is ongeldig.", + "The value '{0}' is invalid.": "De waarde '{0}' is ongeldig.", + "The field {0} must be a number.": "Het veld {0} moet een getal zijn.", + "The field must be a number.": "Het veld moet een getal zijn.", "ThisFieldIsNotAValidCreditCardNumber.": "Dit veld is geen geldig krediet kaartnummer.", "ThisFieldIsNotValid.": "Dir veld is ongeldig.", "ThisFieldIsNotAValidEmailAddress.": "Dit veld is geen geldig e-mail adres.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json index c4a6cfe9d6..29baf6efff 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Pole {0} musi być łańcuchem znaków o minimalnej długości {2} i maksymalnej długości {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Pole {0} nie jest prawidłowym, w pełni kwalifikowanym adresem URL http, https lub ftp.", "The field {0} is invalid.": "Pole {0} jest niepoprawne.", + "The value '{0}' is invalid.": "Wartość '{0}' jest nieprawidłowa.", + "The field {0} must be a number.": "Pole {0} musi być liczbą.", + "The field must be a number.": "Pole musi być liczbą.", "ThisFieldIsNotAValidCreditCardNumber.": "To pole nie jest prawidłowym numerem karty kredytowej.", "ThisFieldIsNotValid.": "To pole jest nieprawidłowe.", "ThisFieldIsNotAValidEmailAddress.": "To pole nie jest prawidłowym adresem e-mail.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json index 1168a19317..a0bec74d8c 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "O campo {0} deve ser uma palavra com o tamanho mínimo de {2} e tamanho máximo de {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "O campo {0} não é um http, https, ou FTP válido.", "The field {0} is invalid.": "O campo {0} é inválido.", + "The value '{0}' is invalid.": "O valor '{0}' é inválido.", + "The field {0} must be a number.": "O campo {0} deve ser um número.", + "The field must be a number.": "O campo deve ser um número.", "ThisFieldIsNotAValidCreditCardNumber.": "Não é um cartão de crédito válido.", "ThisFieldIsNotValid.": "Campo inválido.", "ThisFieldIsNotAValidEmailAddress.": "E-mail inválido.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ro-RO.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ro-RO.json index ce03ecb52b..5fe4e459a2 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ro-RO.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ro-RO.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Câmpul {0} trebuie să fie un string cu lungimea minimă de {2} şi lungimea maximă de {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Câmpul {0} nu este o adresă validă complet http, https sau ftp.", "The field {0} is invalid.": "Câmpul {0} este invalid.", + "The value '{0}' is invalid.": "Valoarea '{0}' este nevalidă.", + "The field {0} must be a number.": "Câmpul {0} trebuie să fie un număr.", + "The field must be a number.": "Câmpul trebuie să fie un număr.", "ThisFieldIsNotAValidCreditCardNumber.": "Acest câmp nu este un număr de card de credit valid.", "ThisFieldIsNotValid.": "Acest câmp nu este valid.", "ThisFieldIsNotAValidEmailAddress.": "Acest câmp nu este o adresă de e-mail validă.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json index 877a3bb1dc..a52c619933 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Поле {0} должно быть строкой с минимальной длиной {2} и максимальной длиной {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Поле {0} не является действительным полным http, https или ftp адресом.", "The field {0} is invalid.": "Значение в поле {0} недопустимо.", + "The value '{0}' is invalid.": "Значение '{0}' недопустимо.", + "The field {0} must be a number.": "Поле {0} должно быть числом.", + "The field must be a number.": "Поле должно быть числом.", "ThisFieldIsNotAValidCreditCardNumber.": "Это поле не содержит действительный номер кредитной карты.", "ThisFieldIsNotValid.": "Значение в этом поле недействительно.", "ThisFieldIsNotAValidEmailAddress.": "Это поле не содержит действительный адрес электронной почты.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sk.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sk.json index d5f310aa66..856124c31f 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sk.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sk.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Pole {0} musí byť reťazec s minimálnou dĺžkou {2} a maximálnou dĺžkou {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "V poli {0} nie je platná plne kvalifikovaná adresa http, https alebo ftp URL.", "The field {0} is invalid.": "Pole {0} je neplatné.", + "The value '{0}' is invalid.": "Hodnota '{0}' je neplatná.", + "The field {0} must be a number.": "Pole {0} musí byť číslo.", + "The field must be a number.": "Pole musí byť číslo.", "ThisFieldIsNotAValidCreditCardNumber.": "V tomto poli nie je platné číslo kreditnej karty.", "ThisFieldIsNotValid.": "Toto pole nie je platné.", "ThisFieldIsNotAValidEmailAddress.": "V tomto poli nie je platná e-mailová adresa.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sl.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sl.json index 374425728d..0eaa31916b 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sl.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sl.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Polje {0} mora biti niz z najmanjšo dolžino {2} in največjo dolžino {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Polje {0} ni veljaven popolnoma kvalificiran URL http, https ali ftp.", "The field {0} is invalid.": "Polje {0} ni veljavno.", + "The value '{0}' is invalid.": "Vrednost '{0}' ni veljavna.", + "The field {0} must be a number.": "Polje {0} mora biti številka.", + "The field must be a number.": "Polje mora biti številka.", "ThisFieldIsNotAValidCreditCardNumber.": "To polje ni veljavna številka kreditne kartice.", "ThisFieldIsNotValid.": "To polje ni veljavno.", "ThisFieldIsNotAValidEmailAddress.": "To polje ni veljaven e-poštni naslov.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json index 9d784c57d9..cb30adde0d 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "{0} alanı en az {2}, en fazla {1} uzunluğunda bir metin olmalıdır.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "{0} alanı geçerli bir http, https, ya da ftp adresi olmalıdır.", "The field {0} is invalid.": "{0} alanı geçerli değil.", + "The value '{0}' is invalid.": "{0} değeri geçerli değil.", + "The field {0} must be a number.": "{0} alanı bir sayı olmalıdır.", + "The field must be a number.": "Bu alan bir sayı olmalıdır.", "ThisFieldIsNotAValidCreditCardNumber.": "Bu alan geçerli bir kredi kartı numarası olmalıdır.", "ThisFieldIsNotValid.": "Bu alan geçerli değil.", "ThisFieldIsNotAValidEmailAddress.": "Bu alan geçerli bir e-posta adresi olmalıdır.", @@ -33,4 +36,4 @@ "ThisFieldMustBeGreaterThanOrEqual{0}": "Bu alan {0}'dan büyük veya eşit olmalıdır.", "ThisFieldMustBeLessOrEqual{0}": "Bu alan {0}'dan küçük veya eşit olmalıdır." } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/vi.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/vi.json index 9bfc2c10ca..984f5c2e2b 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/vi.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/vi.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Trường {0} phải là một chuỗi với độ dài tối thiểu {2} và tối đa là {1}.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Trường {0} không phải là một http, https, hoặc ftp URL đủ điều kiện hợp lệ.", "The field {0} is invalid.": "Trường {0} không có hiệu lực", + "The value '{0}' is invalid.": "Giá trị '{0}' không hợp lệ.", + "The field {0} must be a number.": "Trường {0} phải là một số.", + "The field must be a number.": "Trường phải là một số.", "ThisFieldIsNotAValidCreditCardNumber.": "Trường này không phải là số thẻ tín dụng hợp lệ.", "ThisFieldIsNotValid.": "Trường này không hợp lệ.", "ThisFieldIsNotAValidEmailAddress.": "Trường này không phải là địa chỉ e-mail hợp lệ.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json index 3c1a566112..262b2e08fa 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "字段{0}必须是最小长度为{2}并且最大长度{1}的字符串.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "字段{0}不是有效的完全限定的http,https或ftp URL.", "The field {0} is invalid.": "字段{0}是无效值.", + "The value '{0}' is invalid.": "'{0}'是无效值", + "The field {0} must be a number.": "字段{0}必须是数字.", + "The field must be a number.": "该字段必须是数字.", "ThisFieldIsNotAValidCreditCardNumber.": "字段不是有效的信用卡号码.", "ThisFieldIsNotValid.": "验证未通过.", "ThisFieldIsNotAValidEmailAddress.": "字段不是有效的邮箱地址.", @@ -33,4 +36,4 @@ "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "字段{0}不是有效的完全限定的http,https或ftp URL.", "ThisFieldIsInvalid.": "该字段无效." } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json index ec0fabd121..0f14ca2a40 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json @@ -16,6 +16,9 @@ "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "欄位{0}必須是最小長度為{2}並且最大長度{1}的字串.", "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "欄位{0}不是有效的完全限定的http,https或ftp URL.", "The field {0} is invalid.": "此欄位{0}是無效值.", + "The value '{0}' is invalid.": "'{0}'是無效值", + "The field {0} must be a number.": "欄位{0}必須是號碼.", + "The field must be a number.": "此欄位{0}必須是號碼.", "ThisFieldIsNotAValidCreditCardNumber.": "此欄位不是有效的信用卡號碼.", "ThisFieldIsNotValid.": "此驗證未通過.", "ThisFieldIsNotAValidEmailAddress.": "此欄位不是有效的郵箱地址.", diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs index b41ea093ff..7d5d4d0561 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs @@ -14,7 +14,7 @@ public class AbpAuthorizationTestModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.OnRegistred(onServiceRegistredContext => + context.Services.OnRegistered(onServiceRegistredContext => { if (typeof(IMyAuthorizedService1).IsAssignableFrom(onServiceRegistredContext.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType)) diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs index 42fe0da502..db7b5f5cc8 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Shouldly; using Xunit; @@ -59,7 +60,7 @@ public class BackgroundJobExecuter_Tests : BackgroundJobsTestBase jobObject.ExecutedValues.ShouldContain("42"); } - + [Fact] public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant() { @@ -77,7 +78,7 @@ public class BackgroundJobExecuter_Tests : BackgroundJobsTestBase new MyJobArgs("42", tenantId) ) ); - + await _backgroundJobExecuter.ExecuteAsync( new JobExecutionContext( ServiceProvider, @@ -91,4 +92,48 @@ public class BackgroundJobExecuter_Tests : BackgroundJobsTestBase jobObject.TenantId.ShouldBe(tenantId); asyncJobObject.TenantId.ShouldBe(tenantId); } + + [Fact] + public async Task Should_Cancel_Job() + { + //Arrange + var cts = new CancellationTokenSource(); + cts.Cancel(); + + var jobObject = GetRequiredService(); + jobObject.ExecutedValues.ShouldBeEmpty(); + + //Act + await _backgroundJobExecuter.ExecuteAsync( + new JobExecutionContext( + ServiceProvider, + typeof(MyJob), + new MyJobArgs("42"), + cts.Token + ) + ); + + //Assert + jobObject.Canceled.ShouldBeTrue(); + + //Arrange + var asyncCts = new CancellationTokenSource(); + asyncCts.Cancel(); + + var asyncJobObject = GetRequiredService(); + asyncJobObject.ExecutedValues.ShouldBeEmpty(); + + //Act + await _backgroundJobExecuter.ExecuteAsync( + new JobExecutionContext( + ServiceProvider, + typeof(MyAsyncJob), + new MyAsyncJobArgs("42"), + asyncCts.Token + ) + ); + + //Assert + asyncJobObject.Canceled.ShouldBeTrue(); + } } diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyAsyncJob.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyAsyncJob.cs index 79c5ea887a..d738b36130 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyAsyncJob.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyAsyncJob.cs @@ -1,26 +1,39 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; namespace Volo.Abp.BackgroundJobs; public class MyAsyncJob : AsyncBackgroundJob, ISingletonDependency { public List ExecutedValues { get; } = new List(); - + public Guid? TenantId { get; set; } - + private readonly ICurrentTenant _currentTenant; + private readonly ICancellationTokenProvider _cancellationTokenProvider; - public MyAsyncJob(ICurrentTenant currentTenant) + public bool Canceled { get; set; } + + public MyAsyncJob( + ICurrentTenant currentTenant, + ICancellationTokenProvider cancellationTokenProvider) { _currentTenant = currentTenant; + _cancellationTokenProvider = cancellationTokenProvider; } public override Task ExecuteAsync(MyAsyncJobArgs args) { + if (_cancellationTokenProvider.Token.IsCancellationRequested) + { + Canceled = true; + } + ExecutedValues.Add(args.Value); TenantId = _currentTenant.Id; return Task.CompletedTask; diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyJob.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyJob.cs index 3b01ec05d1..1f9658162d 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyJob.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/MyJob.cs @@ -1,25 +1,38 @@ using System; using System.Collections.Generic; +using System.Threading; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; namespace Volo.Abp.BackgroundJobs; public class MyJob : BackgroundJob, ISingletonDependency { public List ExecutedValues { get; } = new List(); - + public Guid? TenantId { get; set; } private readonly ICurrentTenant _currentTenant; - - public MyJob(ICurrentTenant currentTenant) + private readonly ICancellationTokenProvider _cancellationTokenProvider; + + public bool Canceled { get; set; } + + public MyJob( + ICurrentTenant currentTenant, + ICancellationTokenProvider cancellationTokenProvider) { _currentTenant = currentTenant; + _cancellationTokenProvider = cancellationTokenProvider; } public override void Execute(MyJobArgs args) { + if (_cancellationTokenProvider.Token.IsCancellationRequested) + { + Canceled = true; + } + ExecutedValues.Add(args.Value); TenantId = _currentTenant.Id; } diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DynamicProxy/AbpInterceptionTestBase.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DynamicProxy/AbpInterceptionTestBase.cs index f4b584701f..e244237598 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DynamicProxy/AbpInterceptionTestBase.cs +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DynamicProxy/AbpInterceptionTestBase.cs @@ -19,7 +19,7 @@ public abstract class AbpInterceptionTestBase : AbpAsyncIntegrat services.AddTransient(); services.AddTransient(); - services.OnRegistred(registration => + services.OnRegistered(registration => { if (typeof(SimpleInterceptionTargetClass) == registration.ImplementationType) { diff --git a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs index 276be829b2..37e6b07b8f 100644 --- a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs +++ b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs @@ -109,7 +109,7 @@ public class ApplicationService_FluentValidation_Tests : AbpIntegratedTest + context.Services.OnRegistered(onServiceRegistredContext => { if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType)) diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/fi.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/fi.json index b2d289d3c7..45a513ca1e 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/fi.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/fi.json @@ -2,6 +2,11 @@ "culture": "fi", "texts": { "ThisFieldIsRequired": "Tämä kenttä pitää täyttää", - "MaxLenghtErrorMessage": "Tämä kenttä voi olla enintään {0} merkkiä" + "MaxLenghtErrorMessage": "Tämä kenttä voi olla enintään {0} merkkiä", + "Enum:BookType.Undefined": "Määrittämätön ValidationResourcesta", + "Enum:BookType.0": "Määrittämätön arvo 0 ValidationResourcesta", + "BookType.Adventure": "Seikkailu ValidationResourcesta", + "BookType.1": "Seikkailu arvolla 1 ValidationResourcesta", + "Biography": "Elämäkerta ValidationResourcesta" } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/zh-Hans.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/zh-Hans.json index a6ebb5d590..9d7b5324c9 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/zh-Hans.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/zh-Hans.json @@ -2,6 +2,11 @@ "culture": "zh-Hans", "texts": { "ThisFieldIsRequired": "此字段是必填字段", - "MaxLenghtErrorMessage": "该字段最多可包含'{0}'个字符" + "MaxLenghtErrorMessage": "该字段最多可包含'{0}'个字符", + "Enum:BookType.Undefined": "", + "Enum:BookType.0": "", + "BookType.Adventure": "", + "BookType.1": "", + "Biography": "" } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs index 95a1bbd32f..594efff5a3 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs @@ -1,4 +1,4 @@ -using Volo.Abp.Localization.TestResources.Base.Validation; +using Volo.Abp.Localization.TestResources.Base.Validation; namespace Volo.Abp.Localization.TestResources.Source; diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/fi.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/fi.json index 74eabcd747..d4e546f1e3 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/fi.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/fi.json @@ -6,6 +6,11 @@ "CarPlural": "Autot", "MaxLenghtErrorMessage": "Tämän kentän pituus voi olla enintään {0} merkkiä", "Universe": "Maailmankaikkeus", - "FortyTwo": "Neljäkymmentäkaksi" + "FortyTwo": "Neljäkymmentäkaksi", + "Enum:BookType.Undefined": "Määrittelemätön", + "Enum:BookType.0": "Määrittämätön arvolla 0", + "BookType.Adventure": "Seikkailu", + "BookType.1": "Seikkailu arvolla 1", + "Biography": "Elämäkerta" } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/zh-Hans.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/zh-Hans.json index 52bba48434..f3828e90e2 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/zh-Hans.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/zh-Hans.json @@ -6,6 +6,11 @@ "CarPlural": "汽车", "MaxLenghtErrorMessage": "此字段的长度最多为'{0}'个字符", "Universe": "宇宙", - "FortyTwo": "四十二" + "FortyTwo": "四十二", + "Enum:BookType.Undefined": "Undefined", + "Enum:BookType.0": "", + "BookType.Adventure": "", + "BookType.1": "", + "Biography": "" } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj index a037cfbcf3..b26ea500bf 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj @@ -1,18 +1,21 @@ - + - - net7.0 - - + + net7.0 + + enable + - - - - - - - + + + + + + + + + diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs index f099b4fa6c..6fc3a45132 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs @@ -1,19 +1,34 @@ -using Volo.Abp.Autofac; -using Volo.Abp.Localization; -using Volo.Abp.Modularity; -using Volo.Abp.ObjectMapping; -using Volo.Abp.Settings; - -namespace Volo.Abp.MultiLingualObjects; - -[DependsOn( - typeof(AbpAutofacModule), - typeof(AbpLocalizationModule), - typeof(AbpSettingsModule), - typeof(AbpObjectMappingModule), - typeof(AbpMultiLingualObjectsModule), - typeof(AbpTestBaseModule) -)] -public class AbpMultiLingualObjectsTestModule : AbpModule -{ -} +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.AutoMapper; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Settings; + +namespace Volo.Abp.MultiLingualObjects; + +[DependsOn( + typeof(AbpAutofacModule), + typeof(AbpLocalizationModule), + typeof(AbpSettingsModule), + typeof(AbpObjectMappingModule), + typeof(AbpMultiLingualObjectsModule), + typeof(AbpTestBaseModule), + typeof(AbpAutoMapperModule) +)] +public class AbpMultiLingualObjectsTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.DefinitionProviders.Add(); + }); + context.Services.AddAutoMapperObjectMapper(); + Configure(options => + { + options.AddMaps(validate: true); + }); + } +} diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs index afe15bff99..e442d80953 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; +using Volo.Abp.AutoMapper; using Volo.Abp.Localization; using Volo.Abp.MultiLingualObjects.TestObjects; using Volo.Abp.Testing; @@ -14,40 +16,63 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest _books; + private readonly IMapperAccessor _mapperAccessor; + private readonly Dictionary _testTranslations = new() + { + ["ar"] = "C# التعمق في", + ["zh-Hans"] = "深入理解C#", + ["en"] = "C# in Depth" + }; + public MultiLingualObjectManager_Tests() { - _multiLingualObjectManager = ServiceProvider.GetRequiredService(); - + _multiLingualObjectManager = ServiceProvider.GetRequiredService(); + + //Single Lookup + _book = GetTestBook("en", "zh-Hans"); + //Bulk lookup + _books = new List + { + //has no translations + GetTestBook(), + //english only + GetTestBook("en"), + //arabic only + GetTestBook("ar"), + //arabic + english + GetTestBook("en","ar"), + //arabic + english + chineese + GetTestBook("en", "ar", "zh-Hans") + }; + _mapperAccessor = ServiceProvider.GetRequiredService(); + } + MultiLingualBook GetTestBook(params string[] included) + { var id = Guid.NewGuid(); - _book = new MultiLingualBook(id, 100) - { - Translations = new List() - }; - - var en = new MultiLingualBookTranslation - { - Language = "en", - Name = "C# in Depth", - }; - var zh = new MultiLingualBookTranslation - { - Language = "zh-Hans", - Name = "深入理解C#", - }; - - _book.Translations.Add(en); - _book.Translations.Add(zh); - } + //Single book + var res = new MultiLingualBook(id, 100); + foreach (var language in included) + { + res.Translations.Add(new MultiLingualBookTranslation + { + Language = language, + Name = _testTranslations[language], + }); + } + + return res; + } + [Fact] public async Task GetTranslationAsync() { using (CultureHelper.Use("en-us")) { var translation = await _multiLingualObjectManager.GetTranslationAsync(_book); - - translation.Name.ShouldBe("C# in Depth"); + translation.ShouldNotBeNull(); + translation.Name.ShouldBe(_testTranslations["en"]); } } @@ -57,8 +82,8 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(_book, culture: "en"); + translation.ShouldNotBeNull(); + translation.Name.ShouldBe(_testTranslations["en"]); + } + } + + + [Fact] + public async Task GetBulkTranslationsAsync() + { + using (CultureHelper.Use("en-us")) + { + var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books); + foreach (var (entity, translation) in translations) + { + if (entity.Translations.Any(x => x.Language == "en")) + { + translation.ShouldNotBeNull(); + translation.Name.ShouldBe(_testTranslations["en"]); + } + else + { + translation.ShouldBeNull(); + } + } + } + } - translation.Name.ShouldBe("C# in Depth"); + [Fact] + public async Task GetBulkTranslationsFromListAsync() + { + using (CultureHelper.Use("en-us")) + { + var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations)); + foreach (var translation in translations) + { + translation?.Name.ShouldBe(_testTranslations["en"]); + } } } + + [Fact] + public async Task TestBulkMapping() + { + using (CultureHelper.Use("en-us")) + { + var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books); + var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation); + var mapped = _mapperAccessor.Mapper.Map, List>(_books, options => + { + options.Items.Add(nameof(MultiLingualBookTranslation), translationsDict); + }); + Assert.Equal(mapped.Count, _books.Count); + for (int i = 0; i < mapped.Count; i++) + { + var og = _books[i]; + var m = mapped[i]; + Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name); + } + } + } } diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs new file mode 100644 index 0000000000..89cc9a51b7 --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs @@ -0,0 +1,23 @@ +namespace Volo.Abp.MultiLingualObjects; + +using System; +using System.Collections.Generic; +using global::AutoMapper; +using Volo.Abp.MultiLingualObjects.TestObjects; + +public class MultiLingualObjectTestProfile : Profile +{ + public MultiLingualObjectTestProfile() + { + CreateMap() + .ForMember(x => x.Name, + x => x.MapFrom((src, target, member, context) => + { + if (context.Items.TryGetValue(nameof(MultiLingualBookTranslation), out var translationsRaw) && translationsRaw is IReadOnlyDictionary translations) + { + return translations.GetValueOrDefault(src.Id)?.Name; + } + return null; + })); + } +} diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs index 92a295e0c5..3b880f48f2 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs @@ -1,19 +1,19 @@ -using System; -using System.Collections.Generic; - -namespace Volo.Abp.MultiLingualObjects.TestObjects; - -public class MultiLingualBook : IMultiLingualObject -{ - public MultiLingualBook(Guid id, decimal price) - { - Id = id; - Price = price; - } - - public Guid Id { get; } - - public decimal Price { get; set; } - - public ICollection Translations { get; set; } -} +using System; +using System.Collections.Generic; + +namespace Volo.Abp.MultiLingualObjects.TestObjects; + +public class MultiLingualBook : IMultiLingualObject +{ + public MultiLingualBook(Guid id, decimal price) + { + Id = id; + Price = price; + } + + public Guid Id { get; } + + public decimal Price { get; set; } + + public ICollection Translations { get; set; } = new List(); +} diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs index ea7e7e9afd..8d61449961 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs @@ -1,12 +1,12 @@ -using System; - -namespace Volo.Abp.MultiLingualObjects.TestObjects; - -public class MultiLingualBookDto -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public decimal Price { get; set; } -} +using System; + +namespace Volo.Abp.MultiLingualObjects.TestObjects; + +public class MultiLingualBookDto +{ + public Guid Id { get; set; } + + public string? Name { get; set; } + + public decimal Price { get; set; } +} diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs index 06cfc33b55..adf482fa17 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs @@ -1,8 +1,8 @@ -namespace Volo.Abp.MultiLingualObjects.TestObjects; - -public class MultiLingualBookTranslation : IObjectTranslation -{ - public string Name { get; set; } - - public string Language { get; set; } -} +namespace Volo.Abp.MultiLingualObjects.TestObjects; + +public class MultiLingualBookTranslation : IObjectTranslation +{ + public string? Name { get; set; } + + public required string Language { get; set; } +} diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs index 6088984cb9..c482963d75 100644 --- a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs @@ -16,6 +16,7 @@ public class AbpUiNavigationTestModule : AbpModule options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor1()); options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor2()); options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor3()); + options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor4()); options.MainMenuNames.Add(MenuManager_Tests.TestMenuContributor3.MenuName); }); diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs index b27de60c10..421e072b0f 100644 --- a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs @@ -46,7 +46,7 @@ public class MenuManager_Tests : AbpIntegratedTest mainMenu.Name.ShouldBe(StandardMenus.Main); mainMenu.DisplayName.ShouldBe("Main Menu"); - mainMenu.Items.Count.ShouldBe(2); + mainMenu.Items.Count.ShouldBe(5); mainMenu.Items[0].Name.ShouldBe("Dashboard"); mainMenu.Items[1].Name.ShouldBe(DefaultMenuNames.Application.Main.Administration); mainMenu.Items[1].Items[0].Name.ShouldBe("Administration.UserManagement"); @@ -63,12 +63,29 @@ public class MenuManager_Tests : AbpIntegratedTest mainMenu.Name.ShouldBe(StandardMenus.Main); - mainMenu.Items.Count.ShouldBe(3); + mainMenu.Items.Count.ShouldBe(6); mainMenu.Items.ShouldContain(x => x.Name == "Products"); mainMenu.Items.ShouldContain(x => x.Name == "Dashboard"); } + [Fact] + public async Task GetMainMenuAsync_GroupMenuItems() + { + var mainMenu = await _menuManager.GetMainMenuAsync(); + + mainMenu.Name.ShouldBe(StandardMenus.Main); + mainMenu.Items.Count.ShouldBe(6); + + mainMenu.Items[2].GroupName.ShouldBe("Layouts"); + mainMenu.Items[3].GroupName.ShouldBe("Layouts"); + mainMenu.Items[4].GroupName.ShouldBe(null); // No group defined + + var layoutsGroup = mainMenu.GetMenuGroup("Layouts"); + layoutsGroup.Name.ShouldBe("Layouts"); + layoutsGroup.DisplayName.ShouldBe("Layouts"); + } + /* Adds menu items: * - Administration * - User Management @@ -149,4 +166,29 @@ public class MenuManager_Tests : AbpIntegratedTest return Task.CompletedTask; } } + + /* Adds group and menu items: + * - Layouts + * - Toolbars + * - Page Header + */ + public class TestMenuContributor4 : IMenuContributor + { + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.Main) + { + return Task.CompletedTask; + } + + context.Menu.AddGroup(new ApplicationMenuGroup("Layouts", "Layouts")); + + context.Menu.AddItem(new ApplicationMenuItem("Toolbars", "Toolbars", url: "/layouts/toolbars", groupName: "Layouts")); + context.Menu.AddItem(new ApplicationMenuItem("PageHeader", "Page Header", url: "/layouts/page-header", groupName: "Layouts")); + + context.Menu.AddItem(new ApplicationMenuItem("Branding", "Branding", url: "/layouts/branding", groupName: "NotDefinedGroup")); + + return Task.CompletedTask; + } + } } diff --git a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs index 535e9fb613..f58b000af4 100644 --- a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs +++ b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs @@ -201,7 +201,7 @@ public class ApplicationService_Validation_Tests : AbpIntegratedTest + context.Services.OnRegistered(onServiceRegistredContext => { if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) && !DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType)) diff --git a/latest-versions.json b/latest-versions.json index afd4f0f8a4..1e1ceca763 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,8 +1,8 @@ [ { - "version": "7.0.2", + "version": "7.1.0", "releaseDate": "", "type": "stable", "message": "" } -] \ No newline at end of file +] diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/fi.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/fi.json index f074deeabd..027928ead3 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/fi.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/fi.json @@ -14,7 +14,7 @@ "SelfRegistrationDisabledMessage": "Itserekisteröinti on poistettu käytöstä tälle sovellukselle. Rekisteröi uusi käyttäjä ottamalla yhteyttä sovelluksen järjestelmänvalvojaan.", "LocalLoginDisabledMessage": "Paikallinen sisäänkirjautuminen on poistettu käytöstä tälle sovellukselle.", "Login": "Kirjaudu sisään", - "Cancel": "Peruuttaa", + "Cancel": "Peruuta", "Register": "Rekisteröidy", "AreYouANewUser": "Oletko uusi käyttäjä?", "AlreadyRegistered": "Jo rekisteröity?", @@ -34,6 +34,8 @@ "DisplayName:PhoneNumber": "Puhelinnumero", "PersonalSettings": "Henkilökohtaiset asetukset", "PersonalSettingsSaved": "Henkilökohtaiset asetukset tallennettu", + "PersonalSettingsChangedConfirmationModalTitle": "Henkilötiedot muuttuneet", + "PersonalSettingsChangedConfirmationModalDescription": "Jos haluat ottaa nämä muutokset käyttöön, sinun on kirjauduttava uudelleen sisään. Haluatko kirjautua nyt ulos?", "PasswordChanged": "Salasana vaihdettu", "NewPasswordConfirmFailed": "Vahvista uusi salasana.", "NewPasswordSameAsOld": "Uusi salasana ei voi olla sama kuin vanha salasana.", @@ -43,8 +45,8 @@ "Description:Abp.Account.IsSelfRegistrationEnabled": "Voiko käyttäjä rekisteröidä tilin itse.", "DisplayName:Abp.Account.EnableLocalLogin": "Todennus paikallisella tilillä", "Description:Abp.Account.EnableLocalLogin": "Ilmaisee, salliko palvelin käyttäjien todentamisen paikallisella tilillä.", - "LoggedOutTitle": "Kirjautui ulos", - "LoggedOutText": "Sinut on kirjautunut ulos ja sinut ohjataan pian.", + "LoggedOutTitle": "Kirjauduttu ulos", + "LoggedOutText": "Sinut on kirjattu ulos ja sinut ohjataan pian takaisin sovellukseen.", "ReturnToText": "Napsauta tätä ja ohjaa uudelleen osoitteeseen {0}", "OrLoginWith": "Tai kirjaudu sisään:", "ForgotPassword": "Unohtuiko salasana?", diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json index 37c8177620..1cfabd4d62 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json @@ -34,6 +34,8 @@ "DisplayName:PhoneNumber": "手机号码", "PersonalSettings": "个人设置", "PersonalSettingsSaved": "个人设置已保存", + "PersonalSettingsChangedConfirmationModalTitle": "个人信息已更改", + "PersonalSettingsChangedConfirmationModalDescription": "重新登录以应用这些更改,您要退出登录吗?", "PasswordChanged": "修改密码", "NewPasswordConfirmFailed": "请确认新密码", "NewPasswordSameAsOld": "新密码不能与旧密码相同", @@ -66,4 +68,4 @@ "AccessDenied": "拒绝访问!", "AccessDeniedMessage": "你无权访问此资源." } -} +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hant.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hant.json index 4a13985c8a..8749163c1d 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hant.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hant.json @@ -3,16 +3,16 @@ "texts": { "Menu:Account": "帳號", "UserName": "使用者名稱", - "EmailAddress": "電子信箱地址", - "UserNameOrEmailAddress": "使用者名稱或電子信箱地址", + "EmailAddress": "電子郵件地址", + "UserNameOrEmailAddress": "使用者名稱或電子郵件地址", "Password": "密碼", "RememberMe": "記住我", "UseAnotherServiceToLogin": "使用另一個服務登入", "UserLockedOutMessage": "登入失敗,使用者帳號已被鎖定.請稍後再試.", "InvalidUserNameOrPassword": "使用者名稱或密碼錯誤!", - "LoginIsNotAllowed": "無法登入!你的賬號未激活或者需要驗證郵箱地址/手機號碼.", + "LoginIsNotAllowed": "無法登入!你的帳號未啟用或者需要驗證電子郵件地址/手機號碼.", "SelfRegistrationDisabledMessage": "應用程式未開放註冊,請聯絡管理員以加入新使用者.", - "LocalLoginDisabledMessage": "應用程序未開放本地賬戶登錄.", + "LocalLoginDisabledMessage": "應用程式未開放本地帳戶登入.", "Login": "登入", "Cancel": "取消", "Register": "註冊", @@ -26,11 +26,11 @@ "DisplayName:NewPasswordConfirm": "確認新密碼", "PasswordChangedMessage": "你的密碼已修改成功.", "DisplayName:UserName": "使用者名稱", - "DisplayName:Email": "電子信箱", + "DisplayName:Email": "電子郵件信箱", "DisplayName:Name": "名字", "DisplayName:Surname": "姓", "DisplayName:Password": "密碼", - "DisplayName:EmailAddress": "電子信箱地址", + "DisplayName:EmailAddress": "電子郵件地址", "DisplayName:PhoneNumber": "電話號碼", "PersonalSettings": "個人設置", "PersonalSettingsSaved": "個人設置已保存", @@ -38,32 +38,32 @@ "NewPasswordConfirmFailed": "請確認新密碼", "NewPasswordSameAsOld": "新密碼不能與舊密碼相同", "Manage": "管理", - "MyAccount": "我的賬戶", + "MyAccount": "我的帳戶", "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "啟用自行註冊", "Description:Abp.Account.IsSelfRegistrationEnabled": "是否允許使用者自行註冊帳號.", "DisplayName:Abp.Account.EnableLocalLogin": "使用本地帳號進行身分驗證", "Description:Abp.Account.EnableLocalLogin": "伺服器是否允許使用者使用本地帳號進行身份驗證。", "LoggedOutTitle": "註銷", "LoggedOutText": "你已成功註銷並將馬上返回.", - "ReturnToText": "單擊此處返回到應用程序", + "ReturnToText": "點擊此處返回到應用程式", "OrLoginWith": "或是登入用:", "ForgotPassword": "忘記密碼?", - "SendPasswordResetLink_Information": "密碼重置鏈接將發送到您的電子郵件以重置密碼. 如果您在幾分鐘內沒有收到電子郵件,請重試.", + "SendPasswordResetLink_Information": "密碼重置連結將發送到您的電子郵件以重置密碼. 如果您在幾分鐘內沒有收到電子郵件,請重試.", "PasswordResetMailSentMessage": "帳戶恢復電子郵件已發送到您的電子郵件地址. 如果您在15分鐘內未在收件箱中看到此電子郵件,請檢查垃圾郵件,並標記為非垃圾郵件.", "ResetPassword": "重設密碼", "ConfirmPassword": "確認密碼", "ResetPassword_Information": "請輸入您的新密碼.", "YourPasswordIsSuccessfullyReset": "您的密碼已經被重置成功.", - "GoToTheApplication": "轉到應用程序", - "BackToLogin": "返回登錄", + "GoToTheApplication": "轉到應用程式", + "BackToLogin": "返回登入", "ProfileTab:Password": "更改密碼", - "ProfileTab:PersonalInfo": "個人信息", - "ReturnToApplication": "返回到應用程序", + "ProfileTab:PersonalInfo": "個人資訊", + "ReturnToApplication": "返回到應用程式", "Volo.Account:InvalidEmailAddress": "找不到給定的電子郵件地址:{0}", "PasswordReset": "重設密碼", - "PasswordResetInfoInEmail": "我們收到了帳戶恢復請求!如果你發起了此請求,請單擊以下鏈接以重置密碼.", + "PasswordResetInfoInEmail": "我們收到了帳戶恢復請求!如果你發起了此請求,請點擊以下連結以重置密碼.", "ResetMyPassword": "重置我的密碼", "AccessDenied": "拒絕訪問!", "AccessDeniedMessage": "您無權訪問此資源." } -} \ No newline at end of file +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs index 1920cdd8ad..ce1ecd0e82 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs @@ -1,6 +1,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Account.Settings; using Volo.Abp.Identity; +using Volo.Abp.Settings; namespace Volo.Abp.Account.Web.Pages.Account; @@ -28,7 +30,12 @@ public class LogoutModel : AccountPageModel return RedirectSafely(ReturnUrl, ReturnUrlHash); } - return RedirectToPage("/Account/Login"); + if (await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin)) + { + return RedirectToPage("/Account/Login"); + } + + return RedirectToPage("/"); } public virtual Task OnPostAsync() diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/fi.json b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/fi.json index e39ce6a00f..285cc214e1 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/fi.json +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/fi.json @@ -6,8 +6,8 @@ "Menu:AuditLogging": "Tarkastuslokit", "AuditLogs": "Tarkastuslokit", "HttpStatus": "Http-tila", - "HttpMethod": "Http-menetelmä", - "HttpMethodFilter": "Http-menetelmän suodatin", + "HttpMethod": "Http-metodi", + "HttpMethodFilter": "Http-metodin suodatin", "HttpRequest": "Http-pyyntö", "User": "Käyttäjä", "UserNameFilter": "Käyttäjäsuodatin", @@ -32,33 +32,34 @@ "HttpStatusCode": "Http-tilakoodi", "HttpStatusCodeFilter": "Http-tilakoodisuodatin", "ServiceName": "Palvelu", - "MethodName": "Menetelmä", + "MethodName": "Metodi", "CorrelationId": "Korrelaatiotunnus", - "ApplicationName": "sovelluksen nimi", + "ApplicationName": "Sovelluksen nimi", "ExecutionDuration": "Kesto", "ExtraProperties": "Lisäominaisuudet", "MaxDuration": "Maks. Kesto", "MinDuration": "Min. Kesto", + "MinMaxDuration": "Kesto (Min. - Max.)", "{0}Milliseconds": "{0} millisekuntia", "ExecutionTime": "Aika", "Parameters": "Parametrit", - "EntityTypeFullName": "Yhteisön tyyppi Koko nimi", + "EntityTypeFullName": "Entiteetin tyyppi Koko nimi", "Entity": "Entiteetti", "ChangeType": "Vaihda tyyppiä", "ChangeTime": "Aika", "NewValue": "Uusi arvo", "OriginalValue": "Alkuperäinen arvo", - "PropertyName": "Kiinteistön nimi", - "PropertyTypeFullName": "Kiinteistötyyppi Koko nimi", - "Yes": "Joo", + "PropertyName": "Ominaisuuden nimi", + "PropertyTypeFullName": "Ominaisuustyyppi Koko nimi", + "Yes": "Kyllä", "No": "Ei", "Changes": "Muutokset", "AverageExecutionDurationInLogsPerDay": "Keskimääräinen suorituksen kesto", "AverageExecutionDurationInMilliseconds": "Keskimääräinen suorituksen kesto millisekunteina", "ErrorRateInLogs": "Virheprosentti lokeissa", - "Success": "Menestys", - "Fault": "Vika", - "NoChanges": "Ei muutoksia)", + "Success": "Onnistui", + "Fault": "Virhe", + "NoChanges": "Ei muutoksia", "EntityChanges": "Entiteetin muutokset", "EntityId": "Entiteetin tunnus", "EntityChangeStartTime": "Pienin muutospäivä", diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/zh-Hans.json b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/zh-Hans.json index e634ef3511..1c782932a4 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/zh-Hans.json +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/zh-Hans.json @@ -39,6 +39,7 @@ "ExtraProperties": "额外属性", "MaxDuration": "最大持续时间", "MinDuration": "最小持续时间", + "MinMaxDuration": "持续时间(最小-最大)", "{0}Milliseconds": "{0} 毫秒", "ExecutionTime": "时间", "Parameters": "参数", diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Program.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Program.cs index 5f2c23ceb4..244063d37f 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Program.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Program.cs @@ -1,23 +1,40 @@ using System; +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs; +using Volo.Abp.Threading; namespace Volo.Abp.BackgroundJobs.DemoApp.HangFire; class Program { - static void Main(string[] args) + async static Task Main(string[] args) { - using (var application = AbpApplicationFactory.Create(options => + using (var application = await AbpApplicationFactory.CreateAsync(options => + { + options.UseAutofac(); + })) { - options.UseAutofac(); - })) - { - application.Initialize(); + await application.InitializeAsync(); + + await CancelableBackgroundJobAsync(application.ServiceProvider); Console.WriteLine("Started: " + typeof(Program).Namespace); Console.WriteLine("Press ENTER to stop the application..!"); Console.ReadLine(); - application.Shutdown(); + await application.ShutdownAsync(); } } + + private async static Task CancelableBackgroundJobAsync(IServiceProvider serviceProvider) + { + var backgroundJobManager = serviceProvider.GetRequiredService(); + var jobId = await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-1" }); + await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-2" }); + Thread.Sleep(1000); + BackgroundJob.Delete(jobId); + } } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Quartz/Program.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Quartz/Program.cs index 1ececcd621..eebe5fa279 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Quartz/Program.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Quartz/Program.cs @@ -1,23 +1,41 @@ using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Quartz; +using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs; +using Volo.Abp.Threading; namespace Volo.Abp.BackgroundJobs.DemoApp.Quartz; class Program { - static void Main(string[] args) + async static Task Main(string[] args) { - using (var application = AbpApplicationFactory.Create(options => + using (var application = await AbpApplicationFactory.CreateAsync(options => { options.UseAutofac(); })) { - application.Initialize(); + await application.InitializeAsync(); + + await CancelableBackgroundJobAsync(application.ServiceProvider); Console.WriteLine("Started: " + typeof(Program).Namespace); Console.WriteLine("Press ENTER to stop the application..!"); Console.ReadLine(); - application.Shutdown(); + await application.ShutdownAsync(); } } + + private async static Task CancelableBackgroundJobAsync(IServiceProvider serviceProvider) + { + var backgroundJobManager = serviceProvider.GetRequiredService(); + var jobId = await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs {Value = "test-1"}); + await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-2" }); + Thread.Sleep(1000); + var scheduler = serviceProvider.GetRequiredService(); + await scheduler.Interrupt(new JobKey(jobId.Split('.')[1],jobId.Split('.')[0])); + } } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.RabbitMq/Program.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.RabbitMq/Program.cs index 5d04169c15..a07a0498f9 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.RabbitMq/Program.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.RabbitMq/Program.cs @@ -1,23 +1,24 @@ using System; +using System.Threading.Tasks; namespace Volo.Abp.BackgroundJobs.DemoApp.RabbitMq; class Program { - static void Main(string[] args) + async static Task Main(string[] args) { - using (var application = AbpApplicationFactory.Create(options => + using (var application = await AbpApplicationFactory.CreateAsync(options => { options.UseAutofac(); })) { - application.Initialize(); + await application.InitializeAsync(); Console.WriteLine("Started: " + typeof(Program).Namespace); Console.WriteLine("Press ENTER to stop the application..!"); Console.ReadLine(); - application.Shutdown(); + await application.ShutdownAsync(); } } } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/LongRunningJob.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/LongRunningJob.cs new file mode 100644 index 0000000000..9a57331fff --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/LongRunningJob.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs +{ + public class LongRunningJob : BackgroundJob, ITransientDependency + { + private readonly ICancellationTokenProvider _cancellationTokenProvider; + + public LongRunningJob(ICancellationTokenProvider cancellationTokenProvider) + { + _cancellationTokenProvider = cancellationTokenProvider; + } + + public override void Execute(LongRunningJobArgs args) + { + lock (Console.Out) + { + var oldColor = Console.ForegroundColor; + try + { + Console.WriteLine($"Long running {args.Value} start: {DateTime.Now}"); + + for (var i = 1; i <= 10; i++) + { + _cancellationTokenProvider.Token.ThrowIfCancellationRequested(); + + Thread.Sleep(1000); + Console.WriteLine($"{args.Value} step-{i} done: {DateTime.Now}"); + } + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"Long running {args.Value} completed: {DateTime.Now}"); + } + catch (OperationCanceledException) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Long running {args.Value} cancelled!!!"); + } + finally + { + Console.ForegroundColor = oldColor; + } + } + } + } +} \ No newline at end of file diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/LongRunningJobArgs.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/LongRunningJobArgs.cs new file mode 100644 index 0000000000..fabba0fa57 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/LongRunningJobArgs.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs +{ + [BackgroundJobName("LongJob")] + public class LongRunningJobArgs + { + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleGreenJob.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleGreenJob.cs index e065e409e5..5e5c00a97d 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleGreenJob.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleGreenJob.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleYellowJob.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleYellowJob.cs index a610b6e75a..dbeae26552 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleYellowJob.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/WriteToConsoleYellowJob.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/DemoAppModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/DemoAppModule.cs index a909fb476e..976705b07d 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/DemoAppModule.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/DemoAppModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Autofac; +using System.Threading.Tasks; +using Volo.Abp.Autofac; using Volo.Abp.BackgroundJobs.DemoApp.Shared; using Volo.Abp.BackgroundJobs.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; @@ -27,19 +28,21 @@ public class DemoAppModule : AbpModule Configure(options => { - //Configure for fast running - options.JobPollPeriod = 1000; + //Configure for fast running + options.JobPollPeriod = 1000; options.DefaultFirstWaitDuration = 1; options.DefaultWaitFactor = 1; }); } - public override void OnApplicationInitialization(ApplicationInitializationContext context) + public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { //TODO: Configure console logging //context // .ServiceProvider // .GetRequiredService() // .AddConsole(LogLevel.Debug); + + return Task.CompletedTask; } } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Program.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Program.cs index f895e7adbb..f8fa05863d 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Program.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Program.cs @@ -1,23 +1,23 @@ using System; - +using System.Threading.Tasks; namespace Volo.Abp.BackgroundJobs.DemoApp; class Program { - static void Main(string[] args) + async static Task Main(string[] args) { - using (var application = AbpApplicationFactory.Create(options => + using (var application = await AbpApplicationFactory.CreateAsync(options => { options.UseAutofac(); })) { - application.Initialize(); + await application.InitializeAsync(); Console.WriteLine("Started: " + typeof(Program).Namespace); Console.WriteLine("Press ENTER to stop the application..!"); Console.ReadLine(); - application.Shutdown(); + await application.ShutdownAsync(); } } } diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor index b3ecb41862..1f469a809a 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor @@ -13,7 +13,7 @@ { } - else if (url != null) + else if (MenuItem.Url != null) { public virtual int AccessFailedCount { get; protected internal set; } + /// + /// Should change password on next login. + /// + public virtual bool ShouldChangePasswordOnNextLogin { get; protected internal set; } + /// /// A version value that is increased whenever the entity is changed. /// public virtual int EntityVersion { get; protected set; } + /// + /// Gets or sets the last password change time for the user. + /// + public virtual DateTimeOffset? LastPasswordChangeTime { get; protected set; } + //TODO: Can we make collections readonly collection, which will provide encapsulation. But... can work for all ORMs? /// @@ -368,6 +378,16 @@ public class IdentityUser : FullAuditedAggregateRoot, IUser, IHasEntityVer IsActive = isActive; } + public virtual void SetShouldChangePasswordOnNextLogin(bool shouldChangePasswordOnNextLogin) + { + ShouldChangePasswordOnNextLogin = shouldChangePasswordOnNextLogin; + } + + public virtual void SetLastPasswordChangeTime(DateTimeOffset? lastPasswordChangeTime) + { + LastPasswordChangeTime = lastPasswordChangeTime; + } + public override string ToString() { return $"{base.ToString()}, UserName = {UserName}"; diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserDelegation.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserDelegation.cs new file mode 100644 index 0000000000..8cab00f882 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserDelegation.cs @@ -0,0 +1,41 @@ +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity; + +public class IdentityUserDelegation : BasicAggregateRoot, IMultiTenant +{ + public virtual Guid? TenantId { get; protected set; } + + public virtual Guid SourceUserId { get; protected set; } + + public virtual Guid TargetUserId { get; protected set; } + + public virtual DateTime StartTime { get; protected set; } + + public virtual DateTime EndTime { get; protected set; } + + /// + /// Initializes a new instance of . + /// + protected IdentityUserDelegation() + { + } + + public IdentityUserDelegation( + Guid id, + Guid sourceUserId, + Guid targetUserId, + DateTime startTime, + DateTime endTime, + Guid? tenantId = null) + : base(id) + { + TenantId = tenantId; + SourceUserId = sourceUserId; + TargetUserId = targetUserId; + StartTime = startTime; + EndTime = endTime; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserDelegationManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserDelegationManager.cs new file mode 100644 index 0000000000..8fd4c444f5 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserDelegationManager.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; + +namespace Volo.Abp.Identity; + +public class IdentityUserDelegationManager : DomainService +{ + protected IIdentityUserDelegationRepository IdentityUserDelegationRepository { get; } + + public IdentityUserDelegationManager(IIdentityUserDelegationRepository identityUserDelegationRepository) + { + IdentityUserDelegationRepository = identityUserDelegationRepository; + } + + public virtual async Task> GetListAsync(Guid? sourceUserId = null, Guid? targetUserId = null, CancellationToken cancellationToken = default) + { + return await IdentityUserDelegationRepository.GetListAsync(sourceUserId, targetUserId, cancellationToken: cancellationToken); + } + + public virtual async Task> GetActiveDelegationsAsync(Guid targetUseId, CancellationToken cancellationToken = default) + { + return await IdentityUserDelegationRepository.GetActiveDelegationsAsync(targetUseId, cancellationToken: cancellationToken); + } + + public virtual async Task FindActiveDelegationByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await IdentityUserDelegationRepository.FindActiveDelegationByIdAsync(id, cancellationToken: cancellationToken); + } + + public virtual async Task DelegateNewUserAsync(Guid sourceUserId, Guid targetUserId, DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default) + { + if (sourceUserId == targetUserId) + { + throw new BusinessException(IdentityErrorCodes.YouCannotDelegateYourself); + } + + await IdentityUserDelegationRepository.InsertAsync( + new IdentityUserDelegation( + GuidGenerator.Create(), + sourceUserId, + targetUserId, + startTime, + endTime + ), + cancellationToken: cancellationToken + ); + } + + public virtual async Task DeleteDelegationAsync(Guid id, Guid sourceUserId, CancellationToken cancellationToken = default) + { + var delegation = await IdentityUserDelegationRepository.FindAsync(id, cancellationToken: cancellationToken); + + if (delegation != null && delegation.SourceUserId == sourceUserId) + { + await IdentityUserDelegationRepository.DeleteAsync(delegation, cancellationToken: cancellationToken); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs index d0520c9ef9..355378b8d0 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs @@ -13,6 +13,7 @@ using Volo.Abp.Domain.Services; using Volo.Abp.Identity.Settings; using Volo.Abp.Settings; using Volo.Abp.Threading; +using Volo.Abp.Timing; using Volo.Abp.Uow; namespace Volo.Abp.Identity; @@ -253,4 +254,25 @@ public class IdentityUserManager : UserManager, IDomainService return await UpdateUserAsync(user); } + + public virtual async Task ShouldPeriodicallyChangePasswordAsync(IdentityUser user) + { + Check.NotNull(user, nameof(user)); + + if (user.PasswordHash.IsNullOrWhiteSpace()) + { + return false; + } + + var forceUsersToPeriodicallyChangePassword = await SettingProvider.GetAsync(IdentitySettingNames.Password.ForceUsersToPeriodicallyChangePassword); + if (!forceUsersToPeriodicallyChangePassword) + { + return false; + } + + var lastPasswordChangeTime = user.LastPasswordChangeTime ?? DateTime.SpecifyKind(user.CreationTime, DateTimeKind.Utc); + var passwordChangePeriodDays = await SettingProvider.GetAsync(IdentitySettingNames.Password.PasswordChangePeriodDays); + + return passwordChangePeriodDays > 0 && lastPasswordChangeTime.AddDays(passwordChangePeriodDays) < DateTime.UtcNow; + } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs index c8c8fd45a9..d7d26e02e1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs @@ -12,6 +12,7 @@ using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.Guids; +using Volo.Abp.Timing; namespace Volo.Abp.Identity; @@ -268,6 +269,8 @@ public class IdentityUserStore : user.PasswordHash = passwordHash; + user.SetLastPasswordChangeTime(DateTime.UtcNow); + return Task.CompletedTask; } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs index 000781d4ef..1beb15f0b1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs @@ -122,7 +122,7 @@ public class OrganizationUnitManager : DomainService public virtual async Task GetCodeOrDefaultAsync(Guid id) { - var ou = await OrganizationUnitRepository.GetAsync(id); + var ou = await OrganizationUnitRepository.FindAsync(id); return ou?.Code; } diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs index 4bfa96d253..a58a73a996 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs @@ -19,6 +19,7 @@ public class AbpIdentityEntityFrameworkCoreModule : AbpModule options.AddRepository(); options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityLinkUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityLinkUserRepository.cs index 6a0245fa4f..035904120c 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityLinkUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityLinkUserRepository.cs @@ -20,6 +20,7 @@ public class EfCoreIdentityLinkUserRepository : EfCoreRepository FindAsync(IdentityLinkUserInfo sourceLinkUserInfo, IdentityLinkUserInfo targetLinkUserInfo, CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) + .AsNoTracking() .OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.SourceUserId == sourceLinkUserInfo.UserId && x.SourceTenantId == sourceLinkUserInfo.TenantId && x.TargetUserId == targetLinkUserInfo.UserId && x.TargetTenantId == targetLinkUserInfo.TenantId || @@ -31,7 +32,8 @@ public class EfCoreIdentityLinkUserRepository : EfCoreRepository> GetListAsync(IdentityLinkUserInfo linkUserInfo, List excludes = null, CancellationToken cancellationToken = default) { - IQueryable query = (await GetDbSetAsync()) + var query = (await GetDbSetAsync()) + .AsNoTracking() .Where(x => x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId || x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId); @@ -51,7 +53,7 @@ public class EfCoreIdentityLinkUserRepository : EfCoreRepository + var linkUsers = await (await GetDbSetAsync()).AsNoTracking().Where(x => x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId || x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId) .ToListAsync(cancellationToken: GetCancellationToken(cancellationToken)); diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserDelegationRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserDelegationRepository.cs new file mode 100644 index 0000000000..cd567ffa80 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserDelegationRepository.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Timing; + +namespace Volo.Abp.Identity.EntityFrameworkCore; + +public class EfCoreIdentityUserDelegationRepository : EfCoreRepository, IIdentityUserDelegationRepository +{ + protected IClock Clock { get; } + + public EfCoreIdentityUserDelegationRepository(IDbContextProvider dbContextProvider, IClock clock) + : base(dbContextProvider) + { + Clock = clock; + } + + public async Task> GetListAsync(Guid? sourceUserId, Guid? targetUserId, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .AsNoTracking() + .WhereIf(sourceUserId.HasValue, x => x.SourceUserId == sourceUserId) + .WhereIf(targetUserId.HasValue, x => x.TargetUserId == targetUserId) + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task> GetActiveDelegationsAsync(Guid targetUserId, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .AsNoTracking() + .Where(x => x.TargetUserId == targetUserId && + x.StartTime <= Clock.Now && + x.EndTime >= Clock.Now) + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task FindActiveDelegationByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .AsNoTracking() + .FirstOrDefaultAsync(x => + x.Id == id && + x.StartTime <= Clock.Now && + x.EndTime >= Clock.Now + , cancellationToken: GetCancellationToken(cancellationToken)); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs index e2527555b2..60abbb3054 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs @@ -348,4 +348,12 @@ public class EfCoreIdentityUserRepository : EfCoreRepository> GetListByIdsAsync(IEnumerable ids, bool includeDetails = false, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .IncludeDetails(includeDetails) + .Where(x => ids.Contains(x.Id)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs index e84b2811c5..2f409a61b2 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs @@ -18,4 +18,6 @@ public interface IIdentityDbContext : IEfCoreDbContext DbSet SecurityLogs { get; } DbSet LinkUsers { get; } + + DbSet UserDelegations { get; } } diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs index bc2b4dd4b0..79adece5e1 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs @@ -22,6 +22,8 @@ public class IdentityDbContext : AbpDbContext, IIdentityDbCon public DbSet LinkUsers { get; set; } + public DbSet UserDelegations { get; set; } + public IdentityDbContext(DbContextOptions options) : base(options) { diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index 472581fdbd..f85f72e84e 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -270,6 +270,15 @@ public static class IdentityDbContextModelBuilderExtensions }); } + builder.Entity(b => + { + b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "UserDelegations", AbpIdentityDbProperties.DbSchema); + + b.ConfigureByConvention(); + + b.ApplyObjectExtensionMappings(); + }); + builder.TryConfigureObjectExtensions(); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs index 1acc952024..90118a534e 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs @@ -19,6 +19,8 @@ public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbC public IMongoCollection LinkUsers => Collection(); + public IMongoCollection UserDelegations => Collection(); + protected override void CreateModel(IMongoModelBuilder modelBuilder) { base.CreateModel(modelBuilder); diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs index 4816664d68..5ee7ffc47e 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs @@ -37,5 +37,10 @@ public static class AbpIdentityMongoDbContextExtensions { b.CollectionName = AbpIdentityDbProperties.DbTablePrefix + "LinkUsers"; }); + + builder.Entity(b => + { + b.CollectionName = AbpIdentityDbProperties.DbTablePrefix + "UserDelegations"; + }); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs index 85fc619180..35c81b25bb 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs @@ -20,6 +20,7 @@ public class AbpIdentityMongoDbModule : AbpModule options.AddRepository(); options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs index ad2ae429e1..39bcc23b50 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs @@ -18,4 +18,6 @@ public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext IMongoCollection SecurityLogs { get; } IMongoCollection LinkUsers { get; } + + IMongoCollection UserDelegations { get; } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserDelegationRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserDelegationRepository.cs new file mode 100644 index 0000000000..a782dd57c9 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserDelegationRepository.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; +using Volo.Abp.Timing; + +namespace Volo.Abp.Identity.MongoDB; + +public class MongoIdentityUserDelegationRepository : MongoDbRepository, IIdentityUserDelegationRepository +{ + protected IClock Clock { get; } + + public MongoIdentityUserDelegationRepository(IMongoDbContextProvider dbContextProvider, IClock clock) + : base(dbContextProvider) + { + Clock = clock; + } + + public async Task> GetListAsync(Guid? sourceUserId, Guid? targetUserId, + CancellationToken cancellationToken = default) + { + return await (await GetMongoQueryableAsync(cancellationToken)) + .WhereIf(sourceUserId.HasValue, x => x.SourceUserId == sourceUserId) + .WhereIf(targetUserId.HasValue, x => x.TargetUserId == targetUserId) + .As>() + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task> GetActiveDelegationsAsync(Guid targetUserId, CancellationToken cancellationToken = default) + { + return await (await GetMongoQueryableAsync(cancellationToken)) + .Where(x => x.TargetUserId == targetUserId) + .Where(x => x.StartTime <= Clock.Now && x.EndTime >= Clock.Now) + .As>() + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task FindActiveDelegationByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await (await GetMongoQueryableAsync(cancellationToken)) + .FirstOrDefaultAsync(x => + x.Id == id && + x.StartTime <= Clock.Now && + x.EndTime >= Clock.Now + , cancellationToken: GetCancellationToken(cancellationToken)); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs index ac2ab63809..ae805d9a03 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs @@ -316,4 +316,11 @@ public class MongoIdentityUserRepository : MongoDbRepository> GetListByIdsAsync(IEnumerable ids, bool includeDetails = false, CancellationToken cancellationToken = default) + { + return await (await GetMongoQueryableAsync(cancellationToken)) + .Where(x => ids.Contains(x.Id)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } } diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs index 8949d7dafd..34f933ced4 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs @@ -3,10 +3,12 @@ using Volo.Abp.Authorization.Permissions; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.Identity.Localization; +using Volo.Abp.Identity.Settings; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; using Volo.Abp.PermissionManagement.Identity; +using Volo.Abp.Settings; using Volo.Abp.Threading; using Volo.Abp.VirtualFileSystem; using Volo.Abp.Uow; @@ -49,6 +51,11 @@ public class AbpIdentityDomainTestModule : AbpModule { options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; }); + + Configure(options => + { + options.ValueProviders.Add(); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserDelegationManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserDelegationManager_Tests.cs new file mode 100644 index 0000000000..c85f17230f --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserDelegationManager_Tests.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Identity; + +public class IdentityUserDelegationManager_Tests : AbpIdentityDomainTestBase +{ + protected IdentityUserDelegationManager IdentityUserDelegationManager { get; } + protected IdentityTestData TestData { get; } + + public IdentityUserDelegationManager_Tests() + { + IdentityUserDelegationManager = GetRequiredService(); + TestData = GetRequiredService(); + } + + [Fact] + public async Task GetListAsync() + { + (await IdentityUserDelegationManager.GetListAsync(Guid.NewGuid(), Guid.NewGuid())).Count.ShouldBe(0); + + (await IdentityUserDelegationManager.GetListAsync(TestData.UserJohnId, null)).Count.ShouldBe(2); + + (await IdentityUserDelegationManager.GetListAsync(null, TestData.UserDavidId)).Count.ShouldBe(3); + + (await IdentityUserDelegationManager.GetListAsync(TestData.UserNeoId, TestData.UserDavidId)).Count.ShouldBe(1); + } + + [Fact] + public async Task GetActiveDelegationsAsync() + { + var activeDelegations = await IdentityUserDelegationManager.GetActiveDelegationsAsync(TestData.UserDavidId); + activeDelegations.Count.ShouldBe(2); + activeDelegations[0].SourceUserId.ShouldBe(TestData.UserJohnId); + activeDelegations[0].TargetUserId.ShouldBe(TestData.UserDavidId); + activeDelegations[1].SourceUserId.ShouldBe(TestData.UserNeoId); + activeDelegations[1].TargetUserId.ShouldBe(TestData.UserDavidId); + } + + [Fact] + public async Task FindActiveDelegationByIdAsync() + { + var activeDelegations = await IdentityUserDelegationManager.GetActiveDelegationsAsync(TestData.UserDavidId); + var activeDelegation = await IdentityUserDelegationManager.FindActiveDelegationByIdAsync(activeDelegations[0].Id); + activeDelegation.ShouldNotBeNull(); + activeDelegation.SourceUserId.ShouldBe(TestData.UserJohnId); + activeDelegation.TargetUserId.ShouldBe(TestData.UserDavidId); + } + + [Fact] + public async Task DelegateNewUserAsync() + { + await Should.ThrowAsync(IdentityUserDelegationManager.DelegateNewUserAsync( + TestData.UserJohnId, + TestData.UserJohnId, + DateTime.Now.AddDays(-1), + DateTime.Now)); + + await IdentityUserDelegationManager.DelegateNewUserAsync( + TestData.UserJohnId, + TestData.UserBobId, + DateTime.Now.AddDays(-1), + DateTime.Now.AddDays(1)); + + (await IdentityUserDelegationManager.GetActiveDelegationsAsync(TestData.UserBobId)).Count.ShouldBe(1); + } +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs index 29b51a9b54..517313abcd 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Shouldly; +using Volo.Abp.Identity.Settings; using Volo.Abp.Uow; using Xunit; @@ -214,6 +215,40 @@ public class IdentityUserManager_Tests : AbpIdentityDomainTestBase } } + [Fact] + public async Task ShouldPeriodicallyChangePasswordAsync_Return_False() + { + var user = CreateRandomUser(); + AddPeriodicallyChangePasswordSettings(); + + (await _identityUserManager.CreateAsync(user)).CheckErrors(); + (await _identityUserManager.ShouldPeriodicallyChangePasswordAsync(user)).ShouldBeFalse(); + + await _identityUserManager.AddPasswordAsync(user, IdentityDataSeedContributor.AdminPasswordDefaultValue); + (await _identityUserManager.ShouldPeriodicallyChangePasswordAsync(user)).ShouldBeFalse(); + + user.CreationTime = DateTime.Now; + user.SetLastPasswordChangeTime(null); + (await _identityUserManager.ShouldPeriodicallyChangePasswordAsync(user)).ShouldBeFalse(); + } + + [Fact] + public async Task ShouldPeriodicallyChangePasswordAsync_Return_True() + { + var user = CreateRandomUser(); + AddPeriodicallyChangePasswordSettings(); + + (await _identityUserManager.CreateAsync(user)).CheckErrors(); + await _identityUserManager.AddPasswordAsync(user, IdentityDataSeedContributor.AdminPasswordDefaultValue); + + user.SetLastPasswordChangeTime(DateTime.UtcNow.AddDays(-3)); + (await _identityUserManager.ShouldPeriodicallyChangePasswordAsync(user)).ShouldBeTrue(); + + user.CreationTime = DateTime.Now.AddDays(-3); + user.SetLastPasswordChangeTime(null); + (await _identityUserManager.ShouldPeriodicallyChangePasswordAsync(user)).ShouldBeTrue(); + } + private async Task CreateRandomDefaultRoleAsync() { await _identityRoleRepository.InsertAsync( @@ -235,4 +270,10 @@ public class IdentityUserManager_Tests : AbpIdentityDomainTestBase Guid.NewGuid().ToString() + "@abp.io" ); } + + private static void AddPeriodicallyChangePasswordSettings() + { + TestSettingValueProvider.AddSetting(IdentitySettingNames.Password.PasswordChangePeriodDays, 2.ToString()); + TestSettingValueProvider.AddSetting(IdentitySettingNames.Password.ForceUsersToPeriodicallyChangePassword, true.ToString()); + } } diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestSettingValueProvider.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestSettingValueProvider.cs new file mode 100644 index 0000000000..e70abe6e1f --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestSettingValueProvider.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; + +namespace Volo.Abp.Identity; + +public class TestSettingValueProvider : ISettingValueProvider, ITransientDependency +{ + public const string ProviderName = "Test"; + + private readonly static Dictionary Values = new (); + + public string Name => ProviderName; + + public static void AddSetting(string name, string value) + { + Values[name] = value; + } + + public Task GetOrNullAsync(SettingDefinition setting) + { + return Task.FromResult(Values.GetOrDefault(setting.Name)); + } + + public Task> GetAllAsync(SettingDefinition[] settings) + { + return Task.FromResult(settings.Select(x => new SettingValue(x.Name, Values.GetOrDefault(x.Name))).ToList()); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityUserDelegationepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityUserDelegationepository_Tests.cs new file mode 100644 index 0000000000..92648defe7 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityUserDelegationepository_Tests.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Identity.EntityFrameworkCore; + +public class IdentityUserDelegationepository_Tests : IdentityUserDelegationepository_Tests +{ + +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserDelegationepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserDelegationepository_Tests.cs new file mode 100644 index 0000000000..5fb94b614a --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserDelegationepository_Tests.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Volo.Abp.Identity.MongoDB; + +[Collection(MongoTestCollection.Name)] +public class IdentityUserDelegationepository_Tests: IdentityUserDelegationepository_Tests +{ + +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs index 788f974b5e..dc2b28690a 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs @@ -22,6 +22,7 @@ public class AbpIdentityTestDataBuilder : ITransientDependency private readonly OrganizationUnitManager _organizationUnitManager; private readonly IIdentityLinkUserRepository _identityLinkUserRepository; private readonly IdentityLinkUserManager _identityLinkUserManager; + private readonly IIdentityUserDelegationRepository _identityUserDelegationRepository; private IdentityRole _adminRole; private IdentityRole _moderatorRole; @@ -42,7 +43,8 @@ public class AbpIdentityTestDataBuilder : ITransientDependency IdentityTestData testData, OrganizationUnitManager organizationUnitManager, IIdentityLinkUserRepository identityLinkUserRepository, - IdentityLinkUserManager identityLinkUserManager) + IdentityLinkUserManager identityLinkUserManager, + IIdentityUserDelegationRepository identityUserDelegationRepository) { _guidGenerator = guidGenerator; _userRepository = userRepository; @@ -55,6 +57,7 @@ public class AbpIdentityTestDataBuilder : ITransientDependency _organizationUnitManager = organizationUnitManager; _identityLinkUserRepository = identityLinkUserRepository; _identityLinkUserManager = identityLinkUserManager; + _identityUserDelegationRepository = identityUserDelegationRepository; _identitySecurityLogRepository = identitySecurityLogRepository; } @@ -66,6 +69,7 @@ public class AbpIdentityTestDataBuilder : ITransientDependency await AddLinkUsers(); await AddClaimTypes(); await AddSecurityLogs(); + await AddUserDelegations(); } private async Task AddRoles() @@ -197,4 +201,28 @@ public class AbpIdentityTestDataBuilder : ITransientDependency CreationTime = new DateTime(2020, 01, 02, 10, 0, 0) })); } + + private async Task AddUserDelegations() + { + await _identityUserDelegationRepository.InsertAsync( + new IdentityUserDelegation(_guidGenerator.Create(), + _testData.UserJohnId, + _testData.UserDavidId, + DateTime.Now.AddDays(-2), + DateTime.Now.AddDays(-1))); + + await _identityUserDelegationRepository.InsertAsync( + new IdentityUserDelegation(_guidGenerator.Create(), + _testData.UserJohnId, + _testData.UserDavidId, + DateTime.Now.AddDays(-1), + DateTime.Now.AddDays(1))); + + await _identityUserDelegationRepository.InsertAsync( + new IdentityUserDelegation(_guidGenerator.Create(), + _testData.UserNeoId, + _testData.UserDavidId, + DateTime.Now.AddDays(-1), + DateTime.Now.AddDays(1))); + } } diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserDelegationepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserDelegationepository_Tests.cs new file mode 100644 index 0000000000..50d5dd8a6f --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserDelegationepository_Tests.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.Identity; + +public abstract class IdentityUserDelegationepository_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule +{ + protected IIdentityUserRepository UserRepository { get; } + protected IIdentityUserDelegationRepository IdentityUserDelegationRepository { get; } + protected IdentityTestData TestData { get; } + + public IdentityUserDelegationepository_Tests() + { + UserRepository = GetRequiredService(); + IdentityUserDelegationRepository = GetRequiredService(); + TestData = GetRequiredService(); + } + + [Fact] + public async Task GetListAsync() + { + (await IdentityUserDelegationRepository.GetListAsync(Guid.NewGuid(), Guid.NewGuid())).Count.ShouldBe(0); + + (await IdentityUserDelegationRepository.GetListAsync(TestData.UserJohnId, null)).Count.ShouldBe(2); + + (await IdentityUserDelegationRepository.GetListAsync(null, TestData.UserDavidId)).Count.ShouldBe(3); + + (await IdentityUserDelegationRepository.GetListAsync(TestData.UserNeoId, TestData.UserDavidId)).Count.ShouldBe(1); + } + + [Fact] + public async Task GetActiveDelegationsAsync() + { + var activeDelegations = await IdentityUserDelegationRepository.GetActiveDelegationsAsync(TestData.UserDavidId); + activeDelegations.Count.ShouldBe(2); + activeDelegations[0].SourceUserId.ShouldBe(TestData.UserJohnId); + activeDelegations[0].TargetUserId.ShouldBe(TestData.UserDavidId); + activeDelegations[1].SourceUserId.ShouldBe(TestData.UserNeoId); + activeDelegations[1].TargetUserId.ShouldBe(TestData.UserDavidId); + } + + [Fact] + public async Task GetActiveDelegationOrNullAsync() + { + var activeDelegations = await IdentityUserDelegationRepository.GetActiveDelegationsAsync(TestData.UserDavidId); + var activeDelegation = await IdentityUserDelegationRepository.FindActiveDelegationByIdAsync(activeDelegations[0].Id); + activeDelegation.ShouldNotBeNull(); + activeDelegation.SourceUserId.ShouldBe(TestData.UserJohnId); + activeDelegation.TargetUserId.ShouldBe(TestData.UserDavidId); + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs index cf69fda91a..b2c7ebecb6 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs @@ -17,8 +17,11 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; +using Volo.Abp.Identity.Settings; using Volo.Abp.IdentityServer.Localization; using Volo.Abp.Security.Claims; +using Volo.Abp.Settings; +using Volo.Abp.Timing; using Volo.Abp.Uow; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; @@ -28,7 +31,7 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity; public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { protected SignInManager SignInManager { get; } - protected UserManager UserManager { get; } + protected IdentityUserManager UserManager { get; } protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } protected ILogger> Logger { get; } protected IStringLocalizer Localizer { get; } @@ -36,15 +39,18 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator protected AbpIdentityOptions AbpIdentityOptions { get; } protected IOptions IdentityOptions { get; } + protected ISettingProvider SettingProvider { get; } + public AbpResourceOwnerPasswordValidator( - UserManager userManager, + IdentityUserManager userManager, SignInManager signInManager, IdentitySecurityLogManager identitySecurityLogManager, ILogger> logger, IStringLocalizer localizer, IOptions abpIdentityOptions, IServiceScopeFactory serviceScopeFactory, - IOptions identityOptions) + IOptions identityOptions, + ISettingProvider settingProvider) { UserManager = userManager; SignInManager = signInManager; @@ -54,6 +60,7 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator ServiceScopeFactory = serviceScopeFactory; AbpIdentityOptions = abpIdentityOptions.Value; IdentityOptions = identityOptions; + SettingProvider = settingProvider; } /// @@ -122,6 +129,19 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator else if (result.IsNotAllowed) { Logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.UserName); + + if (user.ShouldChangePasswordOnNextLogin) + { + await HandleShouldChangePasswordOnNextLoginAsync(context, user, context.Password); + return; + } + + if (await UserManager.ShouldPeriodicallyChangePasswordAsync(user)) + { + await HandlePeriodicallyChangePasswordAsync(context, user, context.Password); + return; + } + errorDescription = Localizer["LoginIsNotAllowed"]; } else @@ -193,6 +213,75 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator } } + protected virtual async Task HandleShouldChangePasswordOnNextLoginAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user, string currentPassword) + { + await HandlerChangePasswordAsync(context, user, currentPassword, ChangePasswordType.ShouldChangePasswordOnNextLogin); + } + + protected virtual async Task HandlePeriodicallyChangePasswordAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user, string currentPassword) + { + await HandlerChangePasswordAsync(context, user, currentPassword, ChangePasswordType.PeriodicallyChangePassword); + } + + protected virtual async Task HandlerChangePasswordAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user, string currentPassword, ChangePasswordType changePasswordType) + { + var changePasswordToken = context.Request?.Raw?["ChangePasswordToken"]; + var newPassword = context.Request?.Raw?["NewPassword"]; + if (!changePasswordToken.IsNullOrWhiteSpace() && !currentPassword.IsNullOrWhiteSpace() && !newPassword.IsNullOrWhiteSpace()) + { + if (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString(), changePasswordToken)) + { + var changePasswordResult = await UserManager.ChangePasswordAsync(user, currentPassword, newPassword); + if (changePasswordResult.Succeeded) + { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = IdentitySecurityLogActionConsts.ChangePassword, + UserName = context.UserName, + ClientId = await FindClientIdAsync(context) + }); + + if (changePasswordType == ChangePasswordType.ShouldChangePasswordOnNextLogin) + { + user.SetShouldChangePasswordOnNextLogin(false); + } + + await UserManager.UpdateAsync(user); + await SetSuccessResultAsync(context, user); + } + else + { + Logger.LogInformation("ChangePassword failed for username: {username}, reason: {changePasswordResult}", context.UserName, changePasswordResult); + context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, changePasswordResult.Errors.Select(x => x.Description).JoinAsString(", ")); + } + } + else + { + Logger.LogInformation("Authentication failed for username: {username}, reason: InvalidAuthenticatorCode", context.UserName); + context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, Localizer["InvalidAuthenticatorCode"]); + } + } + else + { + Logger.LogInformation($"Authentication failed for username: {{{context.UserName}}}, reason: {{{changePasswordType.ToString()}}}"); + context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, nameof(user.ShouldChangePasswordOnNextLogin), + new Dictionary() + { + {"userId", user.Id}, + {"changePasswordToken", await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString())} + }); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, + Action = IdentityServerSecurityLogActionConsts.LoginNotAllowed, + UserName = context.UserName, + ClientId = await FindClientIdAsync(context) + }); + } + } + protected virtual async Task SetSuccessResultAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user) { var sub = await UserManager.GetUserIdAsync(user); @@ -266,4 +355,10 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator return Task.CompletedTask; } + + public enum ChangePasswordType + { + ShouldChangePasswordOnNextLogin, + PeriodicallyChangePassword + } } diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.Designer.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230404033745_Initial.Designer.cs similarity index 98% rename from modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.Designer.cs rename to modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230404033745_Initial.Designer.cs index 64ad616624..34487a75a8 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.Designer.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230404033745_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace OpenIddict.Demo.Server.Migrations { [DbContext(typeof(ServerDbContext))] - [Migration("20230106050616_Initial")] + [Migration("20230404033745_Initial")] partial class Initial { /// @@ -235,6 +235,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -421,6 +424,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasDefaultValue(false) .HasColumnName("EmailConfirmed"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -449,6 +455,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -497,6 +506,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -699,6 +711,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(128)") .HasColumnName("DisplayName"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -1277,6 +1292,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230404033745_Initial.cs similarity index 98% rename from modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.cs rename to modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230404033745_Initial.cs index 611a9ad2ca..611109065a 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230404033745_Initial.cs @@ -106,6 +106,7 @@ namespace OpenIddict.Demo.Server.Migrations ParentId = table.Column(type: "uniqueidentifier", nullable: true), Code = table.Column(type: "nvarchar(95)", maxLength: 95, nullable: false), DisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -186,6 +187,7 @@ namespace OpenIddict.Demo.Server.Migrations IsDefault = table.Column(type: "bit", nullable: false), IsStatic = table.Column(type: "bit", nullable: false), IsPublic = table.Column(type: "bit", nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) }, @@ -240,6 +242,7 @@ namespace OpenIddict.Demo.Server.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -278,6 +281,9 @@ namespace OpenIddict.Demo.Server.Migrations LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs index c0599ebe15..6dcdccdd99 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs @@ -232,6 +232,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -418,6 +421,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasDefaultValue(false) .HasColumnName("EmailConfirmed"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -446,6 +452,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -494,6 +503,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -696,6 +708,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(128)") .HasColumnName("DisplayName"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -1274,6 +1289,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj index d3f9e0e9cb..ccd59d2932 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj @@ -20,8 +20,8 @@ - - - + + + diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index 8a664da5c1..3ee5dbf126 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -5,6 +5,7 @@ using OpenIddict.Server; using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.Modularity; +using Volo.Abp.OpenIddict.Scopes; using Volo.Abp.OpenIddict.WildcardDomains; using Volo.Abp.Security.Claims; @@ -133,6 +134,7 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule } builder.AddEventHandler(RemoveClaimsFromClientCredentialsGrantType.Descriptor); + builder.AddEventHandler(AttachScopes.Descriptor); services.ExecutePreConfiguredActions(builder); }); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs index 6406c935c7..dd9d46b2de 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs @@ -12,7 +12,9 @@ using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; +using Volo.Abp.Identity.Settings; using Volo.Abp.MultiTenancy; +using Volo.Abp.Settings; using Volo.Abp.Uow; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; @@ -28,6 +30,8 @@ public partial class TokenController protected IOptions IdentityOptions => LazyServiceProvider.LazyGetRequiredService>(); protected IdentitySecurityLogManager IdentitySecurityLogManager => LazyServiceProvider.LazyGetRequiredService(); + protected ISettingProvider SettingProvider => LazyServiceProvider.LazyGetRequiredService(); + [UnitOfWork] protected virtual async Task HandlePasswordAsync(OpenIddictRequest request) { @@ -101,6 +105,17 @@ public partial class TokenController else if (result.IsNotAllowed) { Logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", request.Username); + + if (user.ShouldChangePasswordOnNextLogin) + { + return await HandleShouldChangePasswordOnNextLoginAsync(request, user, request.Password); + } + + if (await UserManager.ShouldPeriodicallyChangePasswordAsync(user)) + { + return await HandlePeriodicallyChangePasswordAsync(request, user, request.Password); + } + errorDescription = "You are not allowed to login! Your account is inactive or needs to confirm your email/phone number."; } else @@ -197,8 +212,7 @@ public partial class TokenController items: new Dictionary { [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - nameof(SignInResult.RequiresTwoFactor), + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = nameof(SignInResult.RequiresTwoFactor) }, parameters: new Dictionary { @@ -210,6 +224,96 @@ public partial class TokenController } } + protected virtual async Task HandleShouldChangePasswordOnNextLoginAsync(OpenIddictRequest request, IdentityUser user, string currentPassword) + { + return await HandleChangePasswordAsync(request, user, currentPassword, ChangePasswordType.ShouldChangePasswordOnNextLogin); + } + + protected virtual async Task HandlePeriodicallyChangePasswordAsync(OpenIddictRequest request, IdentityUser user, string currentPassword) + { + return await HandleChangePasswordAsync(request, user, currentPassword, ChangePasswordType.PeriodicallyChangePassword); + } + + protected virtual async Task HandleChangePasswordAsync(OpenIddictRequest request, IdentityUser user, string currentPassword, ChangePasswordType changePasswordType) + { + var changePasswordToken = request.GetParameter("ChangePasswordToken")?.ToString(); + var newPassword = request.GetParameter("NewPassword")?.ToString(); + if (!changePasswordToken.IsNullOrWhiteSpace() && !currentPassword.IsNullOrWhiteSpace() && !newPassword.IsNullOrWhiteSpace()) + { + if (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString(), changePasswordToken)) + { + var changePasswordResult = await UserManager.ChangePasswordAsync(user, currentPassword, newPassword); + if (changePasswordResult.Succeeded) + { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict, + Action = IdentitySecurityLogActionConsts.ChangePassword, + UserName = request.Username, + ClientId = request.ClientId + }); + + if (changePasswordType == ChangePasswordType.ShouldChangePasswordOnNextLogin) + { + user.SetShouldChangePasswordOnNextLogin(false); + } + + await UserManager.UpdateAsync(user); + return await SetSuccessResultAsync(request, user); + } + else + { + Logger.LogInformation("ChangePassword failed for username: {username}, reason: {changePasswordResult}", request.Username, changePasswordResult.Errors.Select(x => x.Description).JoinAsString(", ")); + + var properties = new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = changePasswordResult.Errors.Select(x => x.Description).JoinAsString(", ") + }); + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + } + else + { + Logger.LogInformation("Authentication failed for username: {username}, reason: InvalidAuthenticatorCode", request.Username); + + var properties = new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "Invalid authenticator code!" + }); + + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + } + else + { + Logger.LogInformation($"Authentication failed for username: {{{request.Username}}}, reason: {{{changePasswordType.ToString()}}}"); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext + { + Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict, + Action = OpenIddictSecurityLogActionConsts.LoginNotAllowed, + UserName = request.Username, + ClientId = request.ClientId + }); + + var properties = new AuthenticationProperties( + items: new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = changePasswordType.ToString() + }, + parameters: new Dictionary + { + ["userId"] = user.Id.ToString("N"), + ["changePasswordToken"] = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString()) + }); + + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + } + protected virtual async Task SetSuccessResultAsync(OpenIddictRequest request, IdentityUser user) { // Create a new ClaimsPrincipal containing the claims that @@ -240,4 +344,10 @@ public partial class TokenController await UserManager.GetTwoFactorEnabledAsync(user) && (await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0; } + + public enum ChangePasswordType + { + ShouldChangePasswordOnNextLogin, + PeriodicallyChangePassword + } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Scopes/AttachScopes.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Scopes/AttachScopes.cs new file mode 100644 index 0000000000..75401ada2b --- /dev/null +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Scopes/AttachScopes.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using OpenIddict.Server; + +namespace Volo.Abp.OpenIddict.Scopes; + +public class AttachScopes : IOpenIddictServerHandler +{ + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(OpenIddictServerHandlers.Discovery.AttachScopes.Descriptor.Order + 1) + .SetType(OpenIddictServerHandlerType.Custom) + .Build(); + + private readonly IOpenIddictScopeRepository _scopeRepository; + + public AttachScopes(IOpenIddictScopeRepository scopeRepository) + { + _scopeRepository = scopeRepository; + } + + public async ValueTask HandleAsync(OpenIddictServerEvents.HandleConfigurationRequestContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var scopes = await _scopeRepository.GetListAsync(); + context.Scopes.UnionWith(scopes.Select(x => x.Name)); + } +} diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo.Abp.OpenIddict.Domain.Shared.csproj b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo.Abp.OpenIddict.Domain.Shared.csproj index d7cb8d2592..61eb0ad9ec 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo.Abp.OpenIddict.Domain.Shared.csproj +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo.Abp.OpenIddict.Domain.Shared.csproj @@ -14,7 +14,7 @@ - + diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Localization/OpenIddict/fi.json b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Localization/OpenIddict/fi.json new file mode 100644 index 0000000000..43dbfb17ac --- /dev/null +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Localization/OpenIddict/fi.json @@ -0,0 +1,15 @@ +{ + "culture": "fi", + "texts": { + "TheOpenIDConnectRequestCannotBeRetrieved": "OpenID Connect -pyyntöä ei voi noutaa.", + "TheUserDetailsCannotBbeRetrieved": "Käyttäjätietoja ei voi hakea.", + "TheApplicationDetailsCannotBeFound": "Sovelluksen tietoja ei löydy.", + "DetailsConcerningTheCallingClientApplicationCannotBeFound": "Kutsuvan asiakassovelluksen tietoja ei löydy.", + "TheSpecifiedGrantTypeIsNotImplemented": "Määritettyä lupatyyppiä {0} ei ole otettu käyttöön.", + "Authorization": "Valtuutus", + "DoYouWantToGrantAccessToYourData": "Haluatko myöntää käyttäjälle {0} pääsyn tietoihisi?", + "ScopesRequested": "Laajuudet pyydetty", + "Accept": "Hyväksy", + "Deny": "Kiellä" + } +} \ No newline at end of file diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.csproj b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.csproj index 456ea01da2..f0affe755d 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.csproj +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.csproj @@ -17,7 +17,7 @@ - + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index ed877cea31..bd52741202 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -5,6 +5,7 @@ "OnlyProviderPermissons": "Vain tämä palveluntarjoaja", "All": "Kaikki", "SelectAllInAllTabs": "Myönnä kaikki käyttöoikeudet", - "SelectAllInThisTab": "Valitse kaikki" + "SelectAllInThisTab": "Valitse kaikki", + "SaveWithoutAnyPermissionsWarningMessage": "Haluatko varmasti tallentaa ilman käyttöoikeuksia?" } } \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json index 70eef69695..2e0c11d63f 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json @@ -1,17 +1,27 @@ { "culture": "fi", "texts": { - "Settings": "asetukset", - "SuccessfullySaved": "Tallentaminen onnistui", + "Settings": "Asetukset", + "SuccessfullySaved": "Tallennettu onnistuneesti", "Permission:SettingManagement": "Asetusten hallinta", "Permission:Emailing": "Sähköpostiviestit", + "Permission:EmailingTest": "Sähköpostitesti", + "SendTestEmail": "Lähetä testisähköposti", + "SenderEmailAddress": "Lähettäjän sähköpostiosoite", + "TargetEmailAddress": "Kohdesähköpostiosoite", + "Subject": "Aihe", + "Body": "Runko", + "TestEmailSubject": "Testisähköposti {0}", + "TestEmailBody": "Testaa sähköpostiviestiä täällä", + "SuccessfullySent": "Lähetetty onnistuneesti", + "Send": "Lähetä", "Menu:Emailing": "Sähköpostiviestit", "SmtpHost": "Isäntä", - "SmtpPort": "Satama", + "SmtpPort": "Portti", "SmtpUserName": "Käyttäjänimi", "SmtpPassword": "Salasana", "SmtpDomain": "Verkkotunnus", - "SmtpEnableSsl": "Ota käyttöön ssl", + "SmtpEnableSsl": "Ota käyttöön SSL", "SmtpUseDefaultCredentials": "Käytä oletusarvoisia tunnistetietoja", "DefaultFromAddress": "Oletus osoitteesta", "DefaultFromDisplayName": "Oletus näyttönimestä", @@ -21,4 +31,4 @@ "Feature:AllowChangingEmailSettings": "Salli sähköpostiasetusten muuttaminen.", "Feature:AllowChangingEmailSettingsDescription": "Salli sähköpostiasetusten muuttaminen." } -} +} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json index 179ec3c1d6..7721629201 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json @@ -8,14 +8,14 @@ "TenantName": "Vuokralaisen nimi", "DisplayName:TenantName": "Vuokralaisen nimi", "TenantDeletionConfirmationMessage": "Vuokralainen {0} poistetaan. Vahvistatko sen?", - "ConnectionStrings": "Yhteysjonot", - "DisplayName:DefaultConnectionString": "Oletusyhteysmerkkijono", + "ConnectionStrings": "Tietokantayhteydet", + "DisplayName:DefaultConnectionString": "Oletus tietokantayhteys", "DisplayName:UseSharedDatabase": "Käytä jaettua tietokantaa", "Permission:TenantManagement": "Vuokralaisten hallinta", - "Permission:Create": "Luoda", - "Permission:Edit": "Muokata", - "Permission:Delete": "Poistaa", - "Permission:ManageConnectionStrings": "Hallitse yhteysmerkkijonoja", + "Permission:Create": "Luonti", + "Permission:Edit": "Muokkaus", + "Permission:Delete": "Poisto", + "Permission:ManageConnectionStrings": "Hallitse tietokantayhteyksiä", "Permission:ManageFeatures": "Hallitse ominaisuuksia", "DisplayName:AdminEmailAddress": "Järjestelmänvalvojan sähköpostiosoite", "DisplayName:AdminPassword": "Järjestelmänvalvojan salasana" diff --git a/npm/ng-packs/angular.json b/npm/ng-packs/angular.json index 01e482cd85..14416d1d3a 100644 --- a/npm/ng-packs/angular.json +++ b/npm/ng-packs/angular.json @@ -195,14 +195,54 @@ "assets": ["apps/dev-app/src/favicon.ico", "apps/dev-app/src/assets"], "styles": [ { - "input": "node_modules/bootstrap/dist/css/bootstrap.rtl.min.css", + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/bootstrap-dim.css", "inject": false, - "bundleName": "bootstrap-rtl.min" + "bundleName": "bootstrap-dim" }, { - "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", - "inject": true, - "bundleName": "bootstrap-ltr.min" + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/ng-bundle.css", + "inject": false, + "bundleName": "ng-bundle" + }, + { + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/side-menu/layout-bundle.css", + "inject": false, + "bundleName": "layout-bundle" + }, + { + "input": "node_modules/@abp/ng.theme.lepton-x/assets/css/abp-bundle.css", + "inject": false, + "bundleName": "abp-bundle" + }, + { + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/bootstrap-dim.rtl.css", + "inject": false, + "bundleName": "bootstrap-dim.rtl" + }, + { + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/ng-bundle.rtl.css", + "inject": false, + "bundleName": "ng-bundle.rtl" + }, + { + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/side-menu/layout-bundle.rtl.css", + "inject": false, + "bundleName": "layout-bundle.rtl" + }, + { + "input": "node_modules/@abp/ng.theme.lepton-x/assets/css/abp-bundle.rtl.css", + "inject": false, + "bundleName": "abp-bundle.rtl" + }, + { + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/font-bundle.rtl.css", + "inject": false, + "bundleName": "font-bundle.rtl" + }, + { + "input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/font-bundle.css", + "inject": false, + "bundleName": "font-bundle" }, { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", @@ -234,6 +274,7 @@ "inject": false, "bundleName": "ng-zorro-antd-tree" }, + "node_modules/bootstrap-icons/font/bootstrap-icons.css", "apps/dev-app/src/styles.scss" ], "scripts": [] @@ -715,7 +756,7 @@ } }, "tags": [], - "implicitDependencies": ["core","oauth"] + "implicitDependencies": ["core", "oauth"] } } } diff --git a/npm/ng-packs/apps/dev-app/src/app/app.module.ts b/npm/ng-packs/apps/dev-app/src/app/app.module.ts index 52ebd38362..56e38b40ee 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.module.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.module.ts @@ -4,7 +4,8 @@ import { registerLocale } from '@abp/ng.core/locale'; import { IdentityConfigModule } from '@abp/ng.identity/config'; import { SettingManagementConfigModule } from '@abp/ng.setting-management/config'; import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config'; -import { ThemeBasicModule } from '@abp/ng.theme.basic'; +import { ThemeLeptonXModule } from '@abp/ng.theme.lepton-x'; +import { SideMenuLayoutModule } from '@abp/ng.theme.lepton-x/layouts'; import { ThemeSharedModule } from '@abp/ng.theme.shared'; import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @@ -34,7 +35,8 @@ import { AbpOAuthModule } from '@abp/ng.oauth'; TenantManagementConfigModule.forRoot(), FeatureManagementModule.forRoot(), SettingManagementConfigModule.forRoot(), - ThemeBasicModule.forRoot(), + ThemeLeptonXModule.forRoot(), + SideMenuLayoutModule.forRoot(), ], providers: [APP_ROUTE_PROVIDER], declarations: [AppComponent], diff --git a/npm/ng-packs/apps/dev-app/src/app/home/home.component.html b/npm/ng-packs/apps/dev-app/src/app/home/home.component.html index 2d249da3d8..7024ab69e5 100644 --- a/npm/ng-packs/apps/dev-app/src/app/home/home.component.html +++ b/npm/ng-packs/apps/dev-app/src/app/home/home.component.html @@ -26,8 +26,8 @@

Let's improve your application!

Here are some links to help you get started:

-
-
+ +
-
-
+ +

Meet the ABP Commercial

A Complete Web Application Platform Built on the ABP Framework

- -
-
+ +

ABP Commercial is a platform based on the open source ABP framework. It provides pre-built application modules, rapid @@ -271,8 +270,9 @@ " >

-
- + + +

{ +export abstract class AbstractTreeService { abstract id: string; abstract parentId: string; abstract hide: (item: T) => boolean; @@ -17,6 +24,8 @@ export abstract class AbstractTreeService[]>([]); private _visible$ = new BehaviorSubject[]>([]); + protected othersGroup: string; + get flat(): T[] { return this._flat$.value; } @@ -50,6 +59,15 @@ export abstract class AbstractTreeService[]): RouteGroup[] | undefined { + const map = createGroupMap(list, this.othersGroup); + if (!map) { + return undefined; + } + + return Array.from(map, ([key, items]) => ({ group: key, items })); + } + private filterWith(setOrMap: Set | Map): T[] { return this._flat$.value.filter(item => !setOrMap.has(item[this.id])); } @@ -157,6 +175,7 @@ export abstract class AbstractNavTreeService .createOnUpdateStream(state => state) .subscribe(() => this.refresh()); this.permissionService = injector.get(PermissionService); + this.othersGroup = injector.get(OTHERS_GROUP); } protected isGranted({ requiredPolicy }: T): boolean { @@ -180,4 +199,19 @@ export abstract class AbstractNavTreeService } @Injectable({ providedIn: 'root' }) -export class RoutesService extends AbstractNavTreeService {} +export class RoutesService extends AbstractNavTreeService { + private hasPathOrChild(item: TreeNode): boolean { + return Boolean(item.path) || this.hasChildren(item.name); + } + + get groupedVisible(): RouteGroup[] | undefined { + return this.createGroupedTree(this.visible.filter(item => this.hasPathOrChild(item))); + } + + get groupedVisible$(): Observable[] | undefined> { + return this.visible$.pipe( + map(items => items.filter(item => this.hasPathOrChild(item))), + map(visible => this.createGroupedTree(visible)), + ); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts index 51c81a5b3c..ab9c6a76ae 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts @@ -1,4 +1,4 @@ -import { Subject } from 'rxjs'; +import { Subject, lastValueFrom } from 'rxjs'; import { take } from 'rxjs/operators'; import { RoutesService } from '../services/routes.service'; import { DummyInjector } from './utils/common.utils'; @@ -10,6 +10,7 @@ export const mockRoutesService = (injectorPayload = {} as { [key: string]: any } const injector = new DummyInjector({ PermissionService: mockPermissionService(), ConfigStateService: { createOnUpdateStream: () => updateStream$ }, + OTHERS_GROUP: 'OthersGroup', ...injectorPayload, }); return new RoutesService(injector); @@ -17,6 +18,11 @@ export const mockRoutesService = (injectorPayload = {} as { [key: string]: any } describe('Routes Service', () => { let service: RoutesService; + + const fooGroup = 'FooGroup'; + const barGroup = 'BarGroup'; + const othersGroup = 'OthersGroup'; + const routes = [ { path: '/foo', name: 'foo' }, { path: '/foo/bar', name: 'bar', parentName: 'foo', invisible: true, order: 2 }, @@ -25,6 +31,14 @@ describe('Routes Service', () => { { path: '/foo/x', name: 'x', parentName: 'foo', order: 1 }, ]; + const groupedRoutes = [ + { path: '/foo', name: 'foo', group: fooGroup }, + { path: '/foo/y', name: 'y', parentName: 'foo' }, + { path: '/foo/bar', name: 'bar', group: barGroup }, + { path: '/foo/bar/baz', name: 'baz', group: barGroup }, + { path: '/foo/z', name: 'z' }, + ]; + beforeEach(() => { service = mockRoutesService(); }); @@ -33,9 +47,9 @@ describe('Routes Service', () => { it('should add given routes as flat$, tree$, and visible$', async () => { service.add(routes); - const flat = await service.flat$.pipe(take(1)).toPromise(); - const tree = await service.tree$.pipe(take(1)).toPromise(); - const visible = await service.visible$.pipe(take(1)).toPromise(); + const flat = await lastValueFrom(service.flat$.pipe(take(1))); + const tree = await lastValueFrom(service.tree$.pipe(take(1))); + const visible = await lastValueFrom(service.visible$.pipe(take(1))); expect(flat.length).toBe(5); expect(flat[0].name).toBe('baz'); @@ -59,6 +73,52 @@ describe('Routes Service', () => { }); }); + describe('#groupedVisible', () => { + it('should return undefined when there are no visible routes', async () => { + service.add(routes); + const result = await lastValueFrom(service.groupedVisible$.pipe(take(1))); + expect(result).toBeUndefined(); + }); + + it( + 'should group visible routes under "' + othersGroup + '" when no group is specified', + async () => { + service.add([ + { path: '/foo', name: 'foo' }, + { path: '/foo/bar', name: 'bar', group: '' }, + { path: '/foo/bar/baz', name: 'baz', group: undefined }, + { path: '/x', name: 'y', group: 'z' }, + ]); + + const result = await lastValueFrom(service.groupedVisible$.pipe(take(1))); + + expect(result[0].group).toBe(othersGroup); + expect(result[0].items[0].name).toBe('foo'); + expect(result[0].items[1].name).toBe('bar'); + expect(result[0].items[2].name).toBe('baz'); + }, + ); + + it('should return grouped route list', async () => { + service.add(groupedRoutes); + + const tree = await lastValueFrom(service.groupedVisible$.pipe(take(1))); + + expect(tree.length).toBe(3); + + expect(tree[0].group).toBe('FooGroup'); + expect(tree[0].items[0].name).toBe('foo'); + expect(tree[0].items[0].children[0].name).toBe('y'); + + expect(tree[1].group).toBe('BarGroup'); + expect(tree[1].items[0].name).toBe('bar'); + expect(tree[1].items[1].name).toBe('baz'); + + expect(tree[2].group).toBe(othersGroup); + expect(tree[2].items[0].name).toBe('z'); + }); + }); + describe('#find', () => { it('should return node found based on query', () => { service.add(routes); diff --git a/npm/ng-packs/packages/core/src/lib/tests/utils/common.utils.ts b/npm/ng-packs/packages/core/src/lib/tests/utils/common.utils.ts index e568b63a17..01612a987e 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/utils/common.utils.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/utils/common.utils.ts @@ -14,6 +14,6 @@ export class DummyInjector extends Injector { ): T; get(token: any, notFoundValue?: any): any; get(token, notFoundValue?, flags?: InjectFlags): any { - return this.payload[token.name || token]; + return this.payload[token.name || token._desc || token]; } } diff --git a/npm/ng-packs/packages/core/src/lib/tokens/index.ts b/npm/ng-packs/packages/core/src/lib/tokens/index.ts index f0e724259a..d01f652b98 100644 --- a/npm/ng-packs/packages/core/src/lib/tokens/index.ts +++ b/npm/ng-packs/packages/core/src/lib/tokens/index.ts @@ -12,3 +12,4 @@ export * from './pipe-to-login.token'; export * from './set-token-response-to-storage.token'; export * from './check-authentication-state'; export * from './http-context.token'; +export * from './others-group.token' \ No newline at end of file diff --git a/npm/ng-packs/packages/core/src/lib/tokens/others-group.token.ts b/npm/ng-packs/packages/core/src/lib/tokens/others-group.token.ts new file mode 100644 index 0000000000..23d7d65b92 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tokens/others-group.token.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const OTHERS_GROUP = new InjectionToken('OTHERS_GROUP'); diff --git a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts index bfc938c158..8ea8cc504c 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts @@ -1,3 +1,5 @@ +import { isArray } from './common-utils'; + /* eslint-disable @typescript-eslint/ban-types */ export class BaseTreeNode { children: TreeNode[] = []; @@ -74,6 +76,28 @@ export function createTreeNodeFilterCreator( }; } +export function createGroupMap( + list: TreeNode[], + othersGroupKey: string, +) { + if (!isArray(list) || !list.some(node => Boolean(node.group))) return undefined; + + const mapGroup = new Map[]>(); + + for (const node of list) { + const group = node?.group || othersGroupKey; + if (typeof group !== 'string') { + throw new Error(`Invalid group: ${group}`); + } + + const items = mapGroup.get(group) || []; + items.push(node); + mapGroup.set(group, items); + } + + return mapGroup; +} + export type TreeNode = { [K in keyof T]: T[K]; } & { @@ -82,6 +106,11 @@ export type TreeNode = { parent?: TreeNode; }; +export type RouteGroup = { + readonly group: string; + readonly items: TreeNode[]; +}; + export type NodeKey = number | string | symbol | undefined | null; export type NodeValue any> = F extends undefined diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index 0b7db30ace..eaf21949f3 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -49,18 +49,24 @@ *ngFor="let roleGroup of roleGroups; let i = index; trackBy: trackByFn" class="form-check mb-2" > - - + [label]="roles[i].name" + > +

-
diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index cb0cdd9755..6dd1ab9f17 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -1,12 +1,28 @@ -import { ListService, PagedResultDto } from "@abp/ng.core"; -import { GetIdentityUsersInput, IdentityRoleDto, IdentityUserDto, IdentityUserService } from "@abp/ng.identity/proxy"; -import { ePermissionManagementComponents } from "@abp/ng.permission-management"; -import { Confirmation, ConfirmationService, ToasterService } from "@abp/ng.theme.shared"; -import { EXTENSIONS_IDENTIFIER, FormPropData, generateFormFromProps } from "@abp/ng.theme.shared/extensions"; -import { Component, Injector, OnInit, TemplateRef, TrackByFunction, ViewChild } from "@angular/core"; -import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from "@angular/forms"; -import { finalize, switchMap, tap } from "rxjs/operators"; -import { eIdentityComponents } from "../../enums/components"; +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { + GetIdentityUsersInput, + IdentityRoleDto, + IdentityUserDto, + IdentityUserService, +} from '@abp/ng.identity/proxy'; +import { ePermissionManagementComponents } from '@abp/ng.permission-management'; +import {Confirmation, ConfirmationService, eFormComponets, ToasterService} from '@abp/ng.theme.shared'; +import { + EXTENSIONS_IDENTIFIER, + FormPropData, + generateFormFromProps, +} from '@abp/ng.theme.shared/extensions'; +import { + Component, + Injector, + OnInit, + TemplateRef, + TrackByFunction, + ViewChild, +} from '@angular/core'; +import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { finalize, switchMap, tap } from 'rxjs/operators'; +import { eIdentityComponents } from '../../enums/components'; @Component({ selector: 'abp-users', @@ -43,7 +59,9 @@ export class UsersComponent implements OnInit { permissionManagementKey = ePermissionManagementComponents.PermissionManagement; - entityDisplayName?: string; + entityDisplayName: string; + + inputKey=eFormComponets.FormCheckboxComponent trackByFn: TrackByFunction = (index, item) => Object.keys(item)[0] || index; diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index 6193a5bc20..5a3373121c 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -81,7 +81,7 @@ export class PermissionManagementComponent }); }); } else { - this.selectedGroup = null; + this.setSelectedGroup(null); this._visible = false; this.visibleChange.emit(false); } @@ -110,10 +110,22 @@ export class PermissionManagementComponent modalBusy = false; + selectedGroupPermissions: PermissionWithStyle[] = []; + trackByFn: TrackByFunction = (_, item) => item.name; - get selectedGroupPermissions(): PermissionWithStyle[] { - if (!this.selectedGroup) return []; + constructor(protected service: PermissionsService, protected configState: ConfigStateService) {} + + getChecked(name: string) { + return (this.permissions.find(per => per.name === name) || { isGranted: false }).isGranted; + } + + setSelectedGroup(group: PermissionGroupDto) { + this.selectedGroup = group; + if (!this.selectedGroup) { + this.selectedGroupPermissions = []; + return; + } const margin = `margin-${ (document.body.dir as LocaleDirection) === 'rtl' ? 'right' : 'left' @@ -123,7 +135,7 @@ export class PermissionManagementComponent (this.data.groups.find(group => group.name === this.selectedGroup?.name) || {}).permissions || []; - return permissions.map( + this.selectedGroupPermissions = permissions.map( permission => ({ ...permission, @@ -133,12 +145,6 @@ export class PermissionManagementComponent ); } - constructor(protected service: PermissionsService, protected configState: ConfigStateService) {} - - getChecked(name: string) { - return (this.permissions.find(per => per.name === name) || { isGranted: false }).isGranted; - } - setDisabled(permissions: PermissionGrantInfoDto[]) { if (permissions.length) { this.disableSelectAllTab = permissions.every( @@ -248,7 +254,7 @@ export class PermissionManagementComponent onChangeGroup(group: PermissionGroupDto) { this.setDisabled(group.permissions); - this.selectedGroup = group; + this.setSelectedGroup(group); this.setTabCheckboxState(); } @@ -291,8 +297,8 @@ export class PermissionManagementComponent return this.service.get(this.providerName, this.providerKey).pipe( tap((permissionRes: GetPermissionListResultDto) => { this.data = permissionRes; - this.selectedGroup = permissionRes.groups[0]; this.permissions = getPermissions(permissionRes.groups); + this.setSelectedGroup(permissionRes.groups[0]); this.disabledSelectAllInAllTabs = this.permissions.every( per => per.isGranted && diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts new file mode 100644 index 0000000000..4f6d8a9b73 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'abp-card-body', + template: ` +
+ +
`, + host: { + class: 'card-body', + } +}) +export class CardBodyComponent { + @Input() cardBodyClass: string; + @Input() cardBodyStyle: string; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts new file mode 100644 index 0000000000..ed5f0c64f3 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'abp-card-footer', + template: ` +
+ +
+ `, + styles: [], + host: { + class: 'card-footer', + } +}) +export class CardFooterComponent { + @Input() cardFooterStyle: string; + @Input() cardFooterClass: string; + +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts new file mode 100644 index 0000000000..1ec374757c --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'abp-card-header', + template: ` +
+ +
+ `, + styles: [], + host: { + class: 'card-header', + } +}) +export class CardHeaderComponent { + @Input() cardHeaderClass: string; + @Input() cardHeaderStyle: string; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.directive.ts new file mode 100644 index 0000000000..e7ecbfd881 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.directive.ts @@ -0,0 +1,9 @@ +import { Directive } from "@angular/core"; + +@Directive({ + selector: `abp-card-header, [abp-card-header], [abpCardHeader]`, + host: { + class: 'card-header', + }, +}) +export class CardHeader {} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-img-top.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-img-top.directive.ts new file mode 100644 index 0000000000..384f9f3989 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-img-top.directive.ts @@ -0,0 +1,9 @@ +import { Directive } from "@angular/core"; + +@Directive({ + selector: `abp-card-img-top, [abp-card-img-top], [abpCardImgTop]`, + host: { + class: 'card-img-top', + }, +}) +export class CardImgTop {} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-subtitle.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-subtitle.directive.ts new file mode 100644 index 0000000000..862a439ea8 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-subtitle.directive.ts @@ -0,0 +1,9 @@ +import { Directive } from "@angular/core"; + +@Directive({ + selector: `abp-card-subtitle, [abp-card-subtitle], [abpCardSubtitle]`, + host: { + class: 'card-subtitle', + }, +}) +export class CardSubtitle {} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-title.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-title.directive.ts new file mode 100644 index 0000000000..143a868b64 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-title.directive.ts @@ -0,0 +1,9 @@ +import { Directive } from "@angular/core"; + +@Directive({ + selector: `abp-card-title, [abp-card-title], [abpCardTitle]`, + host: { + class: 'card-title', + }, +}) +export class CardTitle {} \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts new file mode 100644 index 0000000000..149866bff0 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts @@ -0,0 +1,14 @@ +import { Component, Directive, Input } from '@angular/core'; + + +@Component({ + selector: 'abp-card', + template: `
+ +
`, +}) +export class CardComponent { + @Input() cardClass: string; + + @Input() cardStyle: string; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.module.ts new file mode 100644 index 0000000000..1a3e62113c --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.module.ts @@ -0,0 +1,28 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { CardBodyComponent } from './card-body.component'; +import { CardComponent } from './card.component'; +import { CardHeaderComponent } from './card-header.component'; +import { CardFooterComponent } from './card-footer.component'; +import { CardTitle } from './card-title.directive'; +import { CardSubtitle } from './card-subtitle.directive'; +import { CardImgTop } from './card-img-top.directive'; +import { CardHeader } from './card-header.directive'; + +const declarationsWithExports = [ + CardComponent, + CardBodyComponent, + CardHeaderComponent, + CardFooterComponent, + CardTitle, + CardSubtitle, + CardImgTop, + CardHeader, +]; + +@NgModule({ + declarations: [...declarationsWithExports], + imports: [CommonModule], + exports: [...declarationsWithExports], +}) +export class CardModule { } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/index.ts new file mode 100644 index 0000000000..ab901f35fe --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/index.ts @@ -0,0 +1,9 @@ +export * from './card.module'; +export * from './card.component'; +export * from './card-body.component'; +export * from './card-footer.component'; +export * from './card-header.component'; +export * from './card-title.directive'; +export * from './card-subtitle.directive'; +export * from './card-img-top.directive'; +export * from './card-header.directive'; \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts new file mode 100644 index 0000000000..ee2dd5aeac --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts @@ -0,0 +1,50 @@ +import { AbstractNgModelComponent } from '@abp/ng.core'; +import { Component, EventEmitter, forwardRef, Injector, Input, Output } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +@Component({ + selector: 'abp-checkbox', + template: ` +
+ + +
+ `, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormCheckboxComponent), + multi: true, + }, + ], +}) +export class FormCheckboxComponent extends AbstractNgModelComponent { + @Input() label?: string; + @Input() labelClass = 'form-check-label'; + @Input() checkboxId!: string; + @Input() checkboxStyle: + | { + [klass: string]: any; + } + | null + | undefined; + @Input() checkboxClass = 'form-check-input'; + @Input() checkboxReadonly = false; + @Output() checkboxBlur = new EventEmitter(); + @Output() checkboxFocus = new EventEmitter(); + + constructor(injector: Injector) { + super(injector); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts new file mode 100644 index 0000000000..991383fe5a --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts @@ -0,0 +1,53 @@ +import { AbstractNgModelComponent } from '@abp/ng.core'; +import { Component, EventEmitter, forwardRef, Injector, Input, Output } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +@Component({ + selector: 'abp-form-input', + template: ` +
+ + +
+ `, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormInputComponent), + multi: true, + }, + ], +}) +export class FormInputComponent extends AbstractNgModelComponent { + @Input() inputId!: string; + @Input() inputReadonly = false; + @Input() label = ''; + @Input() labelClass = 'form-label'; + @Input() inputPlaceholder = ''; + @Input() inputType = 'text'; + @Input() inputStyle: + | { + [klass: string]: any; + } + | null + | undefined; + @Input() inputClass = 'form-control'; + @Output() formBlur = new EventEmitter(); + @Output() formFocus = new EventEmitter(); + + constructor(injector: Injector) { + super(injector); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts index 810d5a991a..238bc19589 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts @@ -11,3 +11,6 @@ export * from './modal/modal.component'; export * from './toast-container/toast-container.component'; export * from './toast/toast.component'; export * from './password/password.component'; +export * from './card/index'; +export * from './checkbox/checkbox.component'; +export * from './form-input/form-input.component'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/enums/form.ts b/npm/ng-packs/packages/theme-shared/src/lib/enums/form.ts new file mode 100644 index 0000000000..9ccbb93655 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/enums/form.ts @@ -0,0 +1,4 @@ +export enum eFormComponets { + FormInputComponent = 'FormInputComponent', + FormCheckboxComponent = 'FormCheckboxComponent', +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/enums/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/enums/index.ts index 3bda94b078..9e781067f7 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/enums/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/enums/index.ts @@ -1 +1,2 @@ +export * from './form'; export * from './route-names'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card-body.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-body.component.spec.ts new file mode 100644 index 0000000000..83cc302625 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-body.component.spec.ts @@ -0,0 +1,46 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { CardBodyComponent } from '../components'; + +describe('AbpCardBodyComponent', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(CardBodyComponent); + + beforeEach( + () => + (spectator = createHost( + ` +

Body

+
`, + { + hostProps: { attributes: { autofocus: '', name: 'abp-card-body' } }, + }, + )), + ); + + it('should create an instance', () => { + expect(spectator).toBeTruthy(); + }); + + it('should have class card-body', () => { + expect(spectator.element.classList).toContain('card-body'); + }); + + it('should have div', () => { + expect(spectator.query('div')).toBeTruthy(); + }); + + it('should have background-color red', () => { + expect((spectator.query('div') as HTMLElement).style.backgroundColor).toEqual('red'); + }); + + it('should have p tag', () => { + expect(spectator.query('p')).toBeTruthy(); + }); + + it('should have p tag with Body text', () => { + expect(spectator.query('p').textContent).toContain('Body'); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card-footer.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-footer.component.spec.ts new file mode 100644 index 0000000000..2b98479b9f --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-footer.component.spec.ts @@ -0,0 +1,46 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { CardFooterComponent } from '../components'; + +describe('AbpCardFooterComponent', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(CardFooterComponent); + + beforeEach( + () => + (spectator = createHost( + ` +

Footer

+
`, + { + hostProps: { attributes: { autofocus: '', name: 'abp-card-footer' } }, + }, + )), + ); + + it('should create an instance', () => { + expect(spectator).toBeTruthy(); + }); + + it('should have class card-footer', () => { + expect(spectator.element.classList).toContain('card-footer'); + }); + + it('should have div', () => { + expect(spectator.query('div')).toBeTruthy(); + }); + + it('should have background-color red', () => { + expect((spectator.query('div') as HTMLElement).style.backgroundColor).toEqual('red'); + }); + + it('should have p tag', () => { + expect(spectator.query('p')).toBeTruthy(); + }); + + it('should have p tag with Footer text', () => { + expect((spectator.query('p') as HTMLElement).textContent).toContain('Footer'); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.component.spec.ts new file mode 100644 index 0000000000..279cfe90bf --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.component.spec.ts @@ -0,0 +1,37 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { CardHeaderComponent } from '../components'; + +describe('AbpCardHeaderComponent', () => { + let spectator: SpectatorHost; + const createHost = createHostFactory(CardHeaderComponent); + + beforeEach( + () => + (spectator = createHost( + ` + Header + `, + { + hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } }, + }, + )), + ); + + it('should create an instance', () => { + expect(spectator).toBeTruthy(); + }); + + it('should have class card-header', () => { + expect(spectator.element.classList).toContain('card-header'); + }); + + it('should have background-color red', () => { + expect((spectator.query('div') as HTMLElement).style.backgroundColor).toEqual('red'); + }); + + it('should have Header text', () => { + expect(spectator.element.textContent).toContain('Header'); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.directive.spec.ts new file mode 100644 index 0000000000..d811014447 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.directive.spec.ts @@ -0,0 +1,28 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { CardHeader } from '../components'; + +describe('AbpCardHeaderDirective', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(CardHeader); + + beforeEach( + () => + (spectator = createHost( + `
+
`, + { + hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } }, + }, + )), + ); + + it('should create an instance', () => { + expect(spectator).toBeTruthy(); + }); + + it('should have class card-header', () => { + expect(spectator.element.classList).toContain('card-header'); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card-img-top.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-img-top.directive.spec.ts new file mode 100644 index 0000000000..2a1ee9cff3 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-img-top.directive.spec.ts @@ -0,0 +1,25 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { CardImgTop } from '../components'; + +describe('AbpCardImgTopDirective', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(CardImgTop); + + beforeEach( + () => + (spectator = createHost( + ``, + )), + ); + + it('should create an instance', () => { + expect(spectator).toBeTruthy(); + }); + + it('should have class card-img-top', () => { + expect(spectator.element.classList).toContain('card-img-top'); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card-subtitle.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-subtitle.directive.spec.ts new file mode 100644 index 0000000000..4995067635 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-subtitle.directive.spec.ts @@ -0,0 +1,22 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { CardSubtitle } from '../components'; + +describe('AbpCardSubtitleDirective', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(CardSubtitle); + + beforeEach(() => (spectator = createHost(`

CardSubtitle

`))); + + it('should create an instance', () => { + expect(spectator).toBeTruthy(); + }); + + it('should have class card-subtitle', () => { + expect(spectator.element.classList).toContain('card-subtitle'); + }); + + it('should have CardSubtitle text', () => { + expect(spectator.element.textContent).toContain('CardSubtitle'); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card-title.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-title.directive.spec.ts new file mode 100644 index 0000000000..10360e23aa --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card-title.directive.spec.ts @@ -0,0 +1,18 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { CardTitle } from '../components'; + +describe('AbpCardTitleDirective', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(CardTitle); + + beforeEach(() => (spectator = createHost(`
CardTitle
`))); + + it('should create an instance', () => { + expect(spectator).toBeTruthy(); + }); + + it('should have class card-title', () => { + expect(spectator.element.classList).toContain('card-title'); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts new file mode 100644 index 0000000000..3dbc2156f1 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts @@ -0,0 +1,75 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { + CardComponent, + CardBodyComponent, + CardFooterComponent, + CardHeaderComponent, + CardHeader, + CardTitle, + CardImgTop, + CardSubtitle, +} from '../components/card'; + +describe('CardComponent', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory({ + component: CardComponent, + declarations: [ + CardHeaderComponent, + CardTitle, + CardSubtitle, + CardBodyComponent, + CardImgTop, + CardFooterComponent, + ], + }); + + beforeEach( + () => + (spectator = createHost( + ` + + + Card title +

Card subtitle

+
+ + +

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+ +
Go somewhere + + + `, + { + hostProps: { attributes: { autofocus: '', name: 'abp-card' } }, + }, + )), + ); + + it('should display the card-header', () => { + expect(spectator.query('abp-card-header')).toBeTruthy(); + }); + + it('should display the card-title', () => { + expect(spectator.query('abp-card-title')).toBeTruthy(); + }); + + it('should display the card-subtitle', () => { + expect(spectator.query('p[abp-card-subtitle]')).toBeTruthy(); + }); + + it('should display the card-body', () => { + expect(spectator.query('abp-card-body')).toBeTruthy(); + }); + + it('should display the card-img-top', () => { + expect(spectator.query('abp-card-img-top')).toBeTruthy(); + }); + + it('should display the card-footer', () => { + expect(spectator.query('abp-card-footer')).toBeTruthy(); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts new file mode 100644 index 0000000000..e2fba6d0da --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts @@ -0,0 +1,44 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { FormCheckboxComponent } from '../components/checkbox/checkbox.component'; + +describe('FormCheckboxComponent', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(FormCheckboxComponent); + + beforeEach( + () => + (spectator = createHost( + '', + { + hostProps: { attributes: { autofocus: '', name: 'abp-checkbox' } }, + }, + )), + ); + + it('should display the input', () => { + expect(spectator.query('input')).toBeTruthy(); + }); + + it('should equal the default classes to form-check-input', () => { + expect(spectator.query('input')).toHaveClass('form-check-input'); + }); + + it('should equal the default type to checkbox', () => { + expect(spectator.query('input')).toHaveAttribute('type', 'checkbox'); + }); + + it('should be readonly when checkboxReadonly is true', () => { + spectator.component.checkboxReadonly = true; + spectator.detectComponentChanges(); + expect(spectator.query('[readonly]')).toBeTruthy(); + }); + + it('should not contain readonly when checboxReadonly is false', () => { + spectator.component.checkboxReadonly = false; + spectator.detectComponentChanges(); + expect(spectator.query('[disabled]')).toBeFalsy(); + }); + +}); + diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts new file mode 100644 index 0000000000..660cb72165 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts @@ -0,0 +1,46 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { FormInputComponent } from '../components/form-input/form-input.component'; + + +describe('FormInputComponent', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory(FormInputComponent); + + beforeEach( + () => + (spectator = createHost( + '', + { + hostProps: { attributes: { autofocus: '', name: 'abp-form-input' } }, + }, + )), + ); + + it('should display the input', () => { + expect(spectator.query('input')).toBeTruthy(); + }); + + it('should equal the default classes to form-control', () => { + expect(spectator.query('input')).toHaveClass('form-control'); + }); + + it('should equal the default type to text', () => { + expect(spectator.query('input')).toHaveAttribute('type', 'text'); + }); + + it('should be readonly when inputReadonly is true', () => { + spectator.component.inputReadonly = true; + spectator.detectComponentChanges(); + expect(spectator.query('[readonly]')).toBeTruthy(); + }); + + it('should not contain readonly when inputReadonly is false', () => { + spectator.component.inputReadonly = false; + spectator.detectComponentChanges(); + expect(spectator.query('[disabled]')).toBeFalsy(); + }); + +}); + diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 20c7de13d4..0fea3f166c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -36,7 +36,10 @@ import { HTTP_ERROR_CONFIG, httpErrorConfigFactory } from './tokens/http-error.t import { DateParserFormatter } from './utils/date-parser-formatter'; import { CONFIRMATION_ICONS, DEFAULT_CONFIRMATION_ICONS } from './tokens/confirmation-icons.token'; import { PasswordComponent } from './components/password/password.component'; +import { CardModule } from './components/card/card.module'; import { AbpVisibleDirective } from './directives'; +import { FormInputComponent } from './components/form-input/form-input.component'; +import { FormCheckboxComponent } from './components/checkbox/checkbox.component'; const declarationsWithExports = [ BreadcrumbComponent, @@ -54,6 +57,8 @@ const declarationsWithExports = [ LoadingDirective, ModalCloseDirective, AbpVisibleDirective, + FormInputComponent, + FormCheckboxComponent ]; @NgModule({ @@ -63,12 +68,20 @@ const declarationsWithExports = [ NgxValidateCoreModule, NgbPaginationModule, EllipsisModule, + CardModule, + ], declarations: [...declarationsWithExports, HttpErrorWrapperComponent], - exports: [NgxDatatableModule, EllipsisModule, NgxValidateCoreModule, ...declarationsWithExports], + exports: [ + NgxDatatableModule, + EllipsisModule, + NgxValidateCoreModule, + CardModule, + ...declarationsWithExports + ], providers: [DatePipe], }) -export class BaseThemeSharedModule {} +export class BaseThemeSharedModule { } @NgModule({ imports: [BaseThemeSharedModule], diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index ac29fd53ad..71e32f80ed 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -30,6 +30,7 @@ "@abp/ng.identity": ["packages/identity/src/public-api.ts"], "@abp/ng.identity/config": ["packages/identity/config/src/public-api.ts"], "@abp/ng.identity/proxy": ["packages/identity/proxy/src/public-api.ts"], + "@abp/ng.oauth": ["packages/oauth/src/public-api.ts"], "@abp/ng.permission-management": ["packages/permission-management/src/public-api.ts"], "@abp/ng.permission-management/proxy": [ "packages/permission-management/proxy/src/public-api.ts" @@ -43,8 +44,7 @@ "@abp/ng.theme.basic/testing": ["packages/theme-basic/testing/src/public-api.ts"], "@abp/ng.theme.shared": ["packages/theme-shared/src/public-api.ts"], "@abp/ng.theme.shared/extensions": ["packages/theme-shared/extensions/src/public-api.ts"], - "@abp/ng.theme.shared/testing": ["packages/theme-shared/testing/src/public-api.ts"], - "@abp/ng.oauth": ["packages/oauth/src/public-api.ts"] + "@abp/ng.theme.shared/testing": ["packages/theme-shared/testing/src/public-api.ts"] } }, "exclude": ["node_modules", "tmp"] diff --git a/npm/packs/flag-icon-css/abp.resourcemapping.js b/npm/packs/flag-icon-css/abp.resourcemapping.js deleted file mode 100644 index 8c3ae431dd..0000000000 --- a/npm/packs/flag-icon-css/abp.resourcemapping.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - mappings: { - "@node_modules/flag-icon-css/css/*": "@libs/flag-icon-css/css", - "@node_modules/flag-icon-css/flags/1x1/*": "@libs/flag-icon-css/flags/1x1" - } -} \ No newline at end of file diff --git a/npm/packs/flag-icons/abp.resourcemapping.js b/npm/packs/flag-icons/abp.resourcemapping.js new file mode 100644 index 0000000000..377a2aee6f --- /dev/null +++ b/npm/packs/flag-icons/abp.resourcemapping.js @@ -0,0 +1,6 @@ +module.exports = { + mappings: { + "@node_modules/flag-icons/css/*": "@libs/flag-icons/css", + "@node_modules/flag-icons/flags/1x1/*": "@libs/flag-icons/flags/1x1" + } +} \ No newline at end of file diff --git a/npm/packs/flag-icons/package.json b/npm/packs/flag-icons/package.json new file mode 100644 index 0000000000..d77c47197b --- /dev/null +++ b/npm/packs/flag-icons/package.json @@ -0,0 +1,11 @@ +{ + "version": "7.0.1", + "name": "@abp/flag-icons", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "flag-icons": "6.6.6" + }, + "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" +} \ No newline at end of file diff --git a/nupkg/pack.ps1 b/nupkg/pack.ps1 index 0835ed790c..671bb2c448 100644 --- a/nupkg/pack.ps1 +++ b/nupkg/pack.ps1 @@ -23,8 +23,9 @@ foreach($project in $projects) { # Create nuget pack Write-Info "[$i / $projectsCount] - Packing project: $projectName" Set-Location $projectFolder -# dotnet clean - dotnet pack -c Release -- /maxcpucount + + #dotnet clean + dotnet pack -c Release --no-build if (-Not $?) { Write-Error "Packaging failed for the project: $projectName" diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20221220114435_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20230324070018_Initial.Designer.cs similarity index 98% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20221220114435_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20230324070018_Initial.Designer.cs index a1747ee5fa..6baa1f955f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20221220114435_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20230324070018_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20221220114435_Initial")] + [Migration("20230324070018_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -717,6 +717,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -765,6 +768,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -827,6 +833,33 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20221220114435_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20230324070018_Initial.cs similarity index 97% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20221220114435_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20230324070018_Initial.cs index 90d52b8c31..f8e92f9d6b 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20221220114435_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20230324070018_Initial.cs @@ -292,6 +292,22 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations table.PrimaryKey("PK_AbpTenants", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpUsers", columns: table => new @@ -315,7 +331,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -988,6 +1006,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations migrationBuilder.DropTable( name: "AbpUserClaims"); + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + migrationBuilder.DropTable( name: "AbpUserLogins"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs index c881e44e52..3115f987ef 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -714,6 +714,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -762,6 +765,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -824,6 +830,33 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20221205080257_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20221205080257_Initial.cs deleted file mode 100644 index cfed2777bd..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20221205080257_Initial.cs +++ /dev/null @@ -1,1030 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace MyCompanyName.MyProjectName.Migrations -{ - /// - public partial class Initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AbpAuditLogs", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - UserId = table.Column(type: "uniqueidentifier", nullable: true), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ImpersonatorUserId = table.Column(type: "uniqueidentifier", nullable: true), - ImpersonatorUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ImpersonatorTenantId = table.Column(type: "uniqueidentifier", nullable: true), - ImpersonatorTenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ExecutionTime = table.Column(type: "datetime2", nullable: false), - ExecutionDuration = table.Column(type: "int", nullable: false), - ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ClientName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - HttpMethod = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), - Url = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Exceptions = table.Column(type: "nvarchar(max)", nullable: true), - Comments = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - HttpStatusCode = table.Column(type: "int", nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpClaimTypes", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Required = table.Column(type: "bit", nullable: false), - IsStatic = table.Column(type: "bit", nullable: false), - Regex = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - RegexDescription = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ValueType = table.Column(type: "int", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpFeatureGroups", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpFeatures", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - IsVisibleToClients = table.Column(type: "bit", nullable: false), - IsAvailableToHost = table.Column(type: "bit", nullable: false), - AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ValueType = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpFeatures", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpFeatureValues", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Value = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpFeatureValues", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpLinkUsers", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), - SourceTenantId = table.Column(type: "uniqueidentifier", nullable: true), - TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), - TargetTenantId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpLinkUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpOrganizationUnits", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ParentId = table.Column(type: "uniqueidentifier", nullable: true), - Code = table.Column(type: "nvarchar(95)", maxLength: 95, nullable: false), - DisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpOrganizationUnits", x => x.Id); - table.ForeignKey( - name: "FK_AbpOrganizationUnits_AbpOrganizationUnits_ParentId", - column: x => x.ParentId, - principalTable: "AbpOrganizationUnits", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "AbpPermissionGrants", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpPermissionGrants", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpPermissionGroups", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpPermissionGroups", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpPermissions", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - IsEnabled = table.Column(type: "bit", nullable: false), - MultiTenancySide = table.Column(type: "tinyint", nullable: false), - Providers = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - StateCheckers = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpPermissions", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpRoles", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - IsDefault = table.Column(type: "bit", nullable: false), - IsStatic = table.Column(type: "bit", nullable: false), - IsPublic = table.Column(type: "bit", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpSecurityLogs", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - Identity = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - Action = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - UserId = table.Column(type: "uniqueidentifier", nullable: true), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpSettings", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Value = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: false), - ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpSettings", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpTenants", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpTenants", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpUsers", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - Surname = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - EmailConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), - PasswordHash = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - SecurityStamp = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - IsExternal = table.Column(type: "bit", nullable: false, defaultValue: false), - PhoneNumber = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), - IsActive = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), - AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictApplications", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ClientId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ClientSecret = table.Column(type: "nvarchar(max)", nullable: true), - ConsentType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - DisplayName = table.Column(type: "nvarchar(max)", nullable: true), - DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), - Permissions = table.Column(type: "nvarchar(max)", nullable: true), - PostLogoutRedirectUris = table.Column(type: "nvarchar(max)", nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), - Requirements = table.Column(type: "nvarchar(max)", nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ClientUri = table.Column(type: "nvarchar(max)", nullable: true), - LogoUri = table.Column(type: "nvarchar(max)", nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictApplications", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictScopes", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Descriptions = table.Column(type: "nvarchar(max)", nullable: true), - DisplayName = table.Column(type: "nvarchar(max)", nullable: true), - DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - Resources = table.Column(type: "nvarchar(max)", nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictScopes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpAuditLogActions", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), - ServiceName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - MethodName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - Parameters = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - ExecutionTime = table.Column(type: "datetime2", nullable: false), - ExecutionDuration = table.Column(type: "int", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpAuditLogActions", x => x.Id); - table.ForeignKey( - name: "FK_AbpAuditLogActions_AbpAuditLogs_AuditLogId", - column: x => x.AuditLogId, - principalTable: "AbpAuditLogs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpEntityChanges", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ChangeTime = table.Column(type: "datetime2", nullable: false), - ChangeType = table.Column(type: "tinyint", nullable: false), - EntityTenantId = table.Column(type: "uniqueidentifier", nullable: true), - EntityId = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - EntityTypeFullName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpEntityChanges", x => x.Id); - table.ForeignKey( - name: "FK_AbpEntityChanges_AbpAuditLogs_AuditLogId", - column: x => x.AuditLogId, - principalTable: "AbpAuditLogs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpOrganizationUnitRoles", - columns: table => new - { - RoleId = table.Column(type: "uniqueidentifier", nullable: false), - OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpOrganizationUnitRoles", x => new { x.OrganizationUnitId, x.RoleId }); - table.ForeignKey( - name: "FK_AbpOrganizationUnitRoles_AbpOrganizationUnits_OrganizationUnitId", - column: x => x.OrganizationUnitId, - principalTable: "AbpOrganizationUnits", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AbpOrganizationUnitRoles_AbpRoles_RoleId", - column: x => x.RoleId, - principalTable: "AbpRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpRoleClaims", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - RoleId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AbpRoleClaims_AbpRoles_RoleId", - column: x => x.RoleId, - principalTable: "AbpRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpTenantConnectionStrings", - columns: table => new - { - TenantId = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - Value = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpTenantConnectionStrings", x => new { x.TenantId, x.Name }); - table.ForeignKey( - name: "FK_AbpTenantConnectionStrings_AbpTenants_TenantId", - column: x => x.TenantId, - principalTable: "AbpTenants", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserClaims", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - UserId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AbpUserClaims_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserLogins", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ProviderKey = table.Column(type: "nvarchar(196)", maxLength: 196, nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserLogins", x => new { x.UserId, x.LoginProvider }); - table.ForeignKey( - name: "FK_AbpUserLogins_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserOrganizationUnits", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserOrganizationUnits", x => new { x.OrganizationUnitId, x.UserId }); - table.ForeignKey( - name: "FK_AbpUserOrganizationUnits_AbpOrganizationUnits_OrganizationUnitId", - column: x => x.OrganizationUnitId, - principalTable: "AbpOrganizationUnits", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AbpUserOrganizationUnits_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserRoles", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - RoleId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AbpUserRoles_AbpRoles_RoleId", - column: x => x.RoleId, - principalTable: "AbpRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AbpUserRoles_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserTokens", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AbpUserTokens_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictAuthorizations", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), - CreationDate = table.Column(type: "datetime2", nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - Scopes = table.Column(type: "nvarchar(max)", nullable: true), - Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id); - table.ForeignKey( - name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "OpenIddictApplications", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "AbpEntityPropertyChanges", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - EntityChangeId = table.Column(type: "uniqueidentifier", nullable: false), - NewValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - OriginalValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - PropertyName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - PropertyTypeFullName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpEntityPropertyChanges", x => x.Id); - table.ForeignKey( - name: "FK_AbpEntityPropertyChanges_AbpEntityChanges_EntityChangeId", - column: x => x.EntityChangeId, - principalTable: "AbpEntityChanges", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictTokens", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), - AuthorizationId = table.Column(type: "uniqueidentifier", nullable: true), - CreationDate = table.Column(type: "datetime2", nullable: true), - ExpirationDate = table.Column(type: "datetime2", nullable: true), - Payload = table.Column(type: "nvarchar(max)", nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - RedemptionDate = table.Column(type: "datetime2", nullable: true), - ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictTokens", x => x.Id); - table.ForeignKey( - name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "OpenIddictApplications", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId", - column: x => x.AuthorizationId, - principalTable: "OpenIddictAuthorizations", - principalColumn: "Id"); - }); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogActions_AuditLogId", - table: "AbpAuditLogActions", - column: "AuditLogId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogActions_TenantId_ServiceName_MethodName_ExecutionTime", - table: "AbpAuditLogActions", - columns: new[] { "TenantId", "ServiceName", "MethodName", "ExecutionTime" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogs_TenantId_ExecutionTime", - table: "AbpAuditLogs", - columns: new[] { "TenantId", "ExecutionTime" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogs_TenantId_UserId_ExecutionTime", - table: "AbpAuditLogs", - columns: new[] { "TenantId", "UserId", "ExecutionTime" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpEntityChanges_AuditLogId", - table: "AbpEntityChanges", - column: "AuditLogId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpEntityChanges_TenantId_EntityTypeFullName_EntityId", - table: "AbpEntityChanges", - columns: new[] { "TenantId", "EntityTypeFullName", "EntityId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpEntityPropertyChanges_EntityChangeId", - table: "AbpEntityPropertyChanges", - column: "EntityChangeId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatureGroups_Name", - table: "AbpFeatureGroups", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatures_GroupName", - table: "AbpFeatures", - column: "GroupName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatures_Name", - table: "AbpFeatures", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", - table: "AbpFeatureValues", - columns: new[] { "Name", "ProviderName", "ProviderKey" }, - unique: true, - filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpLinkUsers_SourceUserId_SourceTenantId_TargetUserId_TargetTenantId", - table: "AbpLinkUsers", - columns: new[] { "SourceUserId", "SourceTenantId", "TargetUserId", "TargetTenantId" }, - unique: true, - filter: "[SourceTenantId] IS NOT NULL AND [TargetTenantId] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpOrganizationUnitRoles_RoleId_OrganizationUnitId", - table: "AbpOrganizationUnitRoles", - columns: new[] { "RoleId", "OrganizationUnitId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpOrganizationUnits_Code", - table: "AbpOrganizationUnits", - column: "Code"); - - migrationBuilder.CreateIndex( - name: "IX_AbpOrganizationUnits_ParentId", - table: "AbpOrganizationUnits", - column: "ParentId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissionGrants_TenantId_Name_ProviderName_ProviderKey", - table: "AbpPermissionGrants", - columns: new[] { "TenantId", "Name", "ProviderName", "ProviderKey" }, - unique: true, - filter: "[TenantId] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissionGroups_Name", - table: "AbpPermissionGroups", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissions_GroupName", - table: "AbpPermissions", - column: "GroupName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissions_Name", - table: "AbpPermissions", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpRoleClaims_RoleId", - table: "AbpRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpRoles_NormalizedName", - table: "AbpRoles", - column: "NormalizedName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_Action", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "Action" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_ApplicationName", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "ApplicationName" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_Identity", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "Identity" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_UserId", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "UserId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSettings_Name_ProviderName_ProviderKey", - table: "AbpSettings", - columns: new[] { "Name", "ProviderName", "ProviderKey" }, - unique: true, - filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpTenants_Name", - table: "AbpTenants", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserClaims_UserId", - table: "AbpUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserLogins_LoginProvider_ProviderKey", - table: "AbpUserLogins", - columns: new[] { "LoginProvider", "ProviderKey" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserOrganizationUnits_UserId_OrganizationUnitId", - table: "AbpUserOrganizationUnits", - columns: new[] { "UserId", "OrganizationUnitId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserRoles_RoleId_UserId", - table: "AbpUserRoles", - columns: new[] { "RoleId", "UserId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_Email", - table: "AbpUsers", - column: "Email"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_NormalizedEmail", - table: "AbpUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_NormalizedUserName", - table: "AbpUsers", - column: "NormalizedUserName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_UserName", - table: "AbpUsers", - column: "UserName"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictApplications_ClientId", - table: "OpenIddictApplications", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type", - table: "OpenIddictAuthorizations", - columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictScopes_Name", - table: "OpenIddictScopes", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type", - table: "OpenIddictTokens", - columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictTokens_AuthorizationId", - table: "OpenIddictTokens", - column: "AuthorizationId"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictTokens_ReferenceId", - table: "OpenIddictTokens", - column: "ReferenceId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AbpAuditLogActions"); - - migrationBuilder.DropTable( - name: "AbpClaimTypes"); - - migrationBuilder.DropTable( - name: "AbpEntityPropertyChanges"); - - migrationBuilder.DropTable( - name: "AbpFeatureGroups"); - - migrationBuilder.DropTable( - name: "AbpFeatures"); - - migrationBuilder.DropTable( - name: "AbpFeatureValues"); - - migrationBuilder.DropTable( - name: "AbpLinkUsers"); - - migrationBuilder.DropTable( - name: "AbpOrganizationUnitRoles"); - - migrationBuilder.DropTable( - name: "AbpPermissionGrants"); - - migrationBuilder.DropTable( - name: "AbpPermissionGroups"); - - migrationBuilder.DropTable( - name: "AbpPermissions"); - - migrationBuilder.DropTable( - name: "AbpRoleClaims"); - - migrationBuilder.DropTable( - name: "AbpSecurityLogs"); - - migrationBuilder.DropTable( - name: "AbpSettings"); - - migrationBuilder.DropTable( - name: "AbpTenantConnectionStrings"); - - migrationBuilder.DropTable( - name: "AbpUserClaims"); - - migrationBuilder.DropTable( - name: "AbpUserLogins"); - - migrationBuilder.DropTable( - name: "AbpUserOrganizationUnits"); - - migrationBuilder.DropTable( - name: "AbpUserRoles"); - - migrationBuilder.DropTable( - name: "AbpUserTokens"); - - migrationBuilder.DropTable( - name: "OpenIddictScopes"); - - migrationBuilder.DropTable( - name: "OpenIddictTokens"); - - migrationBuilder.DropTable( - name: "AbpEntityChanges"); - - migrationBuilder.DropTable( - name: "AbpTenants"); - - migrationBuilder.DropTable( - name: "AbpOrganizationUnits"); - - migrationBuilder.DropTable( - name: "AbpRoles"); - - migrationBuilder.DropTable( - name: "AbpUsers"); - - migrationBuilder.DropTable( - name: "OpenIddictAuthorizations"); - - migrationBuilder.DropTable( - name: "AbpAuditLogs"); - - migrationBuilder.DropTable( - name: "OpenIddictApplications"); - } - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20221205080257_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20230324070058_Initial.Designer.cs similarity index 97% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20221205080257_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20230324070058_Initial.Designer.cs index 81cbbcf0d0..0eba223650 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20221205080257_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20230324070058_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20221205080257_Initial")] + [Migration("20230324070058_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -497,6 +497,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -683,6 +686,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasDefaultValue(false) .HasColumnName("EmailConfirmed"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -711,6 +717,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -759,6 +768,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -821,6 +833,33 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") @@ -961,6 +1000,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(128)") .HasColumnName("DisplayName"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -1539,6 +1581,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20221220114625_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20230324070058_Initial.cs similarity index 97% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20221220114625_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20230324070058_Initial.cs index 75e2c5889d..3c62b40bf4 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20221220114625_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20230324070058_Initial.cs @@ -292,6 +292,22 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpTenants", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpUsers", columns: table => new @@ -315,7 +331,9 @@ namespace MyCompanyName.MyProjectName.Migrations LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -988,6 +1006,9 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpUserClaims"); + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + migrationBuilder.DropTable( name: "AbpUserLogins"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs index d2becf58f5..60afa4e2c9 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -494,6 +494,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -680,6 +683,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasDefaultValue(false) .HasColumnName("EmailConfirmed"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -708,6 +714,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -756,6 +765,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -818,6 +830,33 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") @@ -958,6 +997,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(128)") .HasColumnName("DisplayName"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -1536,6 +1578,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220913013918_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220913013918_Initial.cs deleted file mode 100644 index d34c67552d..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220913013918_Initial.cs +++ /dev/null @@ -1,1027 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace MyCompanyName.MyProjectName.Host.Migrations -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AbpAuditLogs", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - UserId = table.Column(type: "uniqueidentifier", nullable: true), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ImpersonatorUserId = table.Column(type: "uniqueidentifier", nullable: true), - ImpersonatorUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ImpersonatorTenantId = table.Column(type: "uniqueidentifier", nullable: true), - ImpersonatorTenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ExecutionTime = table.Column(type: "datetime2", nullable: false), - ExecutionDuration = table.Column(type: "int", nullable: false), - ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ClientName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - HttpMethod = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), - Url = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Exceptions = table.Column(type: "nvarchar(max)", nullable: true), - Comments = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - HttpStatusCode = table.Column(type: "int", nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpClaimTypes", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Required = table.Column(type: "bit", nullable: false), - IsStatic = table.Column(type: "bit", nullable: false), - Regex = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - RegexDescription = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ValueType = table.Column(type: "int", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpFeatureGroups", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpFeatures", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - IsVisibleToClients = table.Column(type: "bit", nullable: false), - IsAvailableToHost = table.Column(type: "bit", nullable: false), - AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ValueType = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpFeatures", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpFeatureValues", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Value = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpFeatureValues", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpLinkUsers", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), - SourceTenantId = table.Column(type: "uniqueidentifier", nullable: true), - TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), - TargetTenantId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpLinkUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpOrganizationUnits", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ParentId = table.Column(type: "uniqueidentifier", nullable: true), - Code = table.Column(type: "nvarchar(95)", maxLength: 95, nullable: false), - DisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpOrganizationUnits", x => x.Id); - table.ForeignKey( - name: "FK_AbpOrganizationUnits_AbpOrganizationUnits_ParentId", - column: x => x.ParentId, - principalTable: "AbpOrganizationUnits", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "AbpPermissionGrants", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpPermissionGrants", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpPermissionGroups", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpPermissionGroups", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpPermissions", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - IsEnabled = table.Column(type: "bit", nullable: false), - MultiTenancySide = table.Column(type: "tinyint", nullable: false), - Providers = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - StateCheckers = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpPermissions", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpRoles", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - IsDefault = table.Column(type: "bit", nullable: false), - IsStatic = table.Column(type: "bit", nullable: false), - IsPublic = table.Column(type: "bit", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpSecurityLogs", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - Identity = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - Action = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), - UserId = table.Column(type: "uniqueidentifier", nullable: true), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpSettings", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Value = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: false), - ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpSettings", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpTenants", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpTenants", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpUsers", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - Surname = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - EmailConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), - PasswordHash = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - SecurityStamp = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - IsExternal = table.Column(type: "bit", nullable: false, defaultValue: false), - PhoneNumber = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), - IsActive = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), - AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictApplications", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ClientId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ClientSecret = table.Column(type: "nvarchar(max)", nullable: true), - ConsentType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - DisplayName = table.Column(type: "nvarchar(max)", nullable: true), - DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), - Permissions = table.Column(type: "nvarchar(max)", nullable: true), - PostLogoutRedirectUris = table.Column(type: "nvarchar(max)", nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), - Requirements = table.Column(type: "nvarchar(max)", nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ClientUri = table.Column(type: "nvarchar(max)", nullable: true), - LogoUri = table.Column(type: "nvarchar(max)", nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictApplications", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictScopes", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Descriptions = table.Column(type: "nvarchar(max)", nullable: true), - DisplayName = table.Column(type: "nvarchar(max)", nullable: true), - DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - Resources = table.Column(type: "nvarchar(max)", nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictScopes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AbpAuditLogActions", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), - ServiceName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - MethodName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), - Parameters = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - ExecutionTime = table.Column(type: "datetime2", nullable: false), - ExecutionDuration = table.Column(type: "int", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpAuditLogActions", x => x.Id); - table.ForeignKey( - name: "FK_AbpAuditLogActions_AbpAuditLogs_AuditLogId", - column: x => x.AuditLogId, - principalTable: "AbpAuditLogs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpEntityChanges", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ChangeTime = table.Column(type: "datetime2", nullable: false), - ChangeType = table.Column(type: "tinyint", nullable: false), - EntityTenantId = table.Column(type: "uniqueidentifier", nullable: true), - EntityId = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - EntityTypeFullName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpEntityChanges", x => x.Id); - table.ForeignKey( - name: "FK_AbpEntityChanges_AbpAuditLogs_AuditLogId", - column: x => x.AuditLogId, - principalTable: "AbpAuditLogs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpOrganizationUnitRoles", - columns: table => new - { - RoleId = table.Column(type: "uniqueidentifier", nullable: false), - OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpOrganizationUnitRoles", x => new { x.OrganizationUnitId, x.RoleId }); - table.ForeignKey( - name: "FK_AbpOrganizationUnitRoles_AbpOrganizationUnits_OrganizationUnitId", - column: x => x.OrganizationUnitId, - principalTable: "AbpOrganizationUnits", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AbpOrganizationUnitRoles_AbpRoles_RoleId", - column: x => x.RoleId, - principalTable: "AbpRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpRoleClaims", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - RoleId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AbpRoleClaims_AbpRoles_RoleId", - column: x => x.RoleId, - principalTable: "AbpRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpTenantConnectionStrings", - columns: table => new - { - TenantId = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - Value = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpTenantConnectionStrings", x => new { x.TenantId, x.Name }); - table.ForeignKey( - name: "FK_AbpTenantConnectionStrings_AbpTenants_TenantId", - column: x => x.TenantId, - principalTable: "AbpTenants", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserClaims", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - UserId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AbpUserClaims_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserLogins", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - ProviderKey = table.Column(type: "nvarchar(196)", maxLength: 196, nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserLogins", x => new { x.UserId, x.LoginProvider }); - table.ForeignKey( - name: "FK_AbpUserLogins_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserOrganizationUnits", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserOrganizationUnits", x => new { x.OrganizationUnitId, x.UserId }); - table.ForeignKey( - name: "FK_AbpUserOrganizationUnits_AbpOrganizationUnits_OrganizationUnitId", - column: x => x.OrganizationUnitId, - principalTable: "AbpOrganizationUnits", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AbpUserOrganizationUnits_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserRoles", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - RoleId = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AbpUserRoles_AbpRoles_RoleId", - column: x => x.RoleId, - principalTable: "AbpRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AbpUserRoles_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AbpUserTokens", - columns: table => new - { - UserId = table.Column(type: "uniqueidentifier", nullable: false), - LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AbpUserTokens_AbpUsers_UserId", - column: x => x.UserId, - principalTable: "AbpUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictAuthorizations", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), - CreationDate = table.Column(type: "datetime2", nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - Scopes = table.Column(type: "nvarchar(max)", nullable: true), - Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id); - table.ForeignKey( - name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "OpenIddictApplications", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "AbpEntityPropertyChanges", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - TenantId = table.Column(type: "uniqueidentifier", nullable: true), - EntityChangeId = table.Column(type: "uniqueidentifier", nullable: false), - NewValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - OriginalValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - PropertyName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - PropertyTypeFullName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpEntityPropertyChanges", x => x.Id); - table.ForeignKey( - name: "FK_AbpEntityPropertyChanges_AbpEntityChanges_EntityChangeId", - column: x => x.EntityChangeId, - principalTable: "AbpEntityChanges", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OpenIddictTokens", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), - AuthorizationId = table.Column(type: "uniqueidentifier", nullable: true), - CreationDate = table.Column(type: "datetime2", nullable: true), - ExpirationDate = table.Column(type: "datetime2", nullable: true), - Payload = table.Column(type: "nvarchar(max)", nullable: true), - Properties = table.Column(type: "nvarchar(max)", nullable: true), - RedemptionDate = table.Column(type: "datetime2", nullable: true), - ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OpenIddictTokens", x => x.Id); - table.ForeignKey( - name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "OpenIddictApplications", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId", - column: x => x.AuthorizationId, - principalTable: "OpenIddictAuthorizations", - principalColumn: "Id"); - }); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogActions_AuditLogId", - table: "AbpAuditLogActions", - column: "AuditLogId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogActions_TenantId_ServiceName_MethodName_ExecutionTime", - table: "AbpAuditLogActions", - columns: new[] { "TenantId", "ServiceName", "MethodName", "ExecutionTime" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogs_TenantId_ExecutionTime", - table: "AbpAuditLogs", - columns: new[] { "TenantId", "ExecutionTime" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpAuditLogs_TenantId_UserId_ExecutionTime", - table: "AbpAuditLogs", - columns: new[] { "TenantId", "UserId", "ExecutionTime" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpEntityChanges_AuditLogId", - table: "AbpEntityChanges", - column: "AuditLogId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpEntityChanges_TenantId_EntityTypeFullName_EntityId", - table: "AbpEntityChanges", - columns: new[] { "TenantId", "EntityTypeFullName", "EntityId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpEntityPropertyChanges_EntityChangeId", - table: "AbpEntityPropertyChanges", - column: "EntityChangeId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatureGroups_Name", - table: "AbpFeatureGroups", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatures_GroupName", - table: "AbpFeatures", - column: "GroupName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatures_Name", - table: "AbpFeatures", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", - table: "AbpFeatureValues", - columns: new[] { "Name", "ProviderName", "ProviderKey" }, - unique: true, - filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpLinkUsers_SourceUserId_SourceTenantId_TargetUserId_TargetTenantId", - table: "AbpLinkUsers", - columns: new[] { "SourceUserId", "SourceTenantId", "TargetUserId", "TargetTenantId" }, - unique: true, - filter: "[SourceTenantId] IS NOT NULL AND [TargetTenantId] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpOrganizationUnitRoles_RoleId_OrganizationUnitId", - table: "AbpOrganizationUnitRoles", - columns: new[] { "RoleId", "OrganizationUnitId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpOrganizationUnits_Code", - table: "AbpOrganizationUnits", - column: "Code"); - - migrationBuilder.CreateIndex( - name: "IX_AbpOrganizationUnits_ParentId", - table: "AbpOrganizationUnits", - column: "ParentId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissionGrants_TenantId_Name_ProviderName_ProviderKey", - table: "AbpPermissionGrants", - columns: new[] { "TenantId", "Name", "ProviderName", "ProviderKey" }, - unique: true, - filter: "[TenantId] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissionGroups_Name", - table: "AbpPermissionGroups", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissions_GroupName", - table: "AbpPermissions", - column: "GroupName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpPermissions_Name", - table: "AbpPermissions", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AbpRoleClaims_RoleId", - table: "AbpRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpRoles_NormalizedName", - table: "AbpRoles", - column: "NormalizedName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_Action", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "Action" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_ApplicationName", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "ApplicationName" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_Identity", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "Identity" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSecurityLogs_TenantId_UserId", - table: "AbpSecurityLogs", - columns: new[] { "TenantId", "UserId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpSettings_Name_ProviderName_ProviderKey", - table: "AbpSettings", - columns: new[] { "Name", "ProviderName", "ProviderKey" }, - unique: true, - filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AbpTenants_Name", - table: "AbpTenants", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserClaims_UserId", - table: "AbpUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserLogins_LoginProvider_ProviderKey", - table: "AbpUserLogins", - columns: new[] { "LoginProvider", "ProviderKey" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserOrganizationUnits_UserId_OrganizationUnitId", - table: "AbpUserOrganizationUnits", - columns: new[] { "UserId", "OrganizationUnitId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpUserRoles_RoleId_UserId", - table: "AbpUserRoles", - columns: new[] { "RoleId", "UserId" }); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_Email", - table: "AbpUsers", - column: "Email"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_NormalizedEmail", - table: "AbpUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_NormalizedUserName", - table: "AbpUsers", - column: "NormalizedUserName"); - - migrationBuilder.CreateIndex( - name: "IX_AbpUsers_UserName", - table: "AbpUsers", - column: "UserName"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictApplications_ClientId", - table: "OpenIddictApplications", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type", - table: "OpenIddictAuthorizations", - columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictScopes_Name", - table: "OpenIddictScopes", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type", - table: "OpenIddictTokens", - columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictTokens_AuthorizationId", - table: "OpenIddictTokens", - column: "AuthorizationId"); - - migrationBuilder.CreateIndex( - name: "IX_OpenIddictTokens_ReferenceId", - table: "OpenIddictTokens", - column: "ReferenceId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AbpAuditLogActions"); - - migrationBuilder.DropTable( - name: "AbpClaimTypes"); - - migrationBuilder.DropTable( - name: "AbpEntityPropertyChanges"); - - migrationBuilder.DropTable( - name: "AbpFeatureGroups"); - - migrationBuilder.DropTable( - name: "AbpFeatures"); - - migrationBuilder.DropTable( - name: "AbpFeatureValues"); - - migrationBuilder.DropTable( - name: "AbpLinkUsers"); - - migrationBuilder.DropTable( - name: "AbpOrganizationUnitRoles"); - - migrationBuilder.DropTable( - name: "AbpPermissionGrants"); - - migrationBuilder.DropTable( - name: "AbpPermissionGroups"); - - migrationBuilder.DropTable( - name: "AbpPermissions"); - - migrationBuilder.DropTable( - name: "AbpRoleClaims"); - - migrationBuilder.DropTable( - name: "AbpSecurityLogs"); - - migrationBuilder.DropTable( - name: "AbpSettings"); - - migrationBuilder.DropTable( - name: "AbpTenantConnectionStrings"); - - migrationBuilder.DropTable( - name: "AbpUserClaims"); - - migrationBuilder.DropTable( - name: "AbpUserLogins"); - - migrationBuilder.DropTable( - name: "AbpUserOrganizationUnits"); - - migrationBuilder.DropTable( - name: "AbpUserRoles"); - - migrationBuilder.DropTable( - name: "AbpUserTokens"); - - migrationBuilder.DropTable( - name: "OpenIddictScopes"); - - migrationBuilder.DropTable( - name: "OpenIddictTokens"); - - migrationBuilder.DropTable( - name: "AbpEntityChanges"); - - migrationBuilder.DropTable( - name: "AbpTenants"); - - migrationBuilder.DropTable( - name: "AbpOrganizationUnits"); - - migrationBuilder.DropTable( - name: "AbpRoles"); - - migrationBuilder.DropTable( - name: "AbpUsers"); - - migrationBuilder.DropTable( - name: "OpenIddictAuthorizations"); - - migrationBuilder.DropTable( - name: "AbpAuditLogs"); - - migrationBuilder.DropTable( - name: "OpenIddictApplications"); - } - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220913013918_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20230324070125_Initial.Designer.cs similarity index 97% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220913013918_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20230324070125_Initial.Designer.cs index bc2a256ed3..fe2d18ec1e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220913013918_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20230324070125_Initial.Designer.cs @@ -13,18 +13,19 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Host.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20220913013918_Initial")] + [Migration("20230324070125_Initial")] partial class Initial { + /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => { @@ -496,6 +497,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -682,6 +686,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasDefaultValue(false) .HasColumnName("EmailConfirmed"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -710,6 +717,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -758,6 +768,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -820,6 +833,33 @@ namespace MyCompanyName.MyProjectName.Host.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") @@ -960,6 +1000,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(128)") .HasColumnName("DisplayName"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -1538,6 +1581,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20230324070125_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20230324070125_Initial.cs new file mode 100644 index 0000000000..7af7c9a871 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20230324070125_Initial.cs @@ -0,0 +1,1055 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyCompanyName.MyProjectName.Host.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpAuditLogs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + UserId = table.Column(type: "uniqueidentifier", nullable: true), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ImpersonatorUserId = table.Column(type: "uniqueidentifier", nullable: true), + ImpersonatorUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ImpersonatorTenantId = table.Column(type: "uniqueidentifier", nullable: true), + ImpersonatorTenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ExecutionTime = table.Column(type: "datetime2", nullable: false), + ExecutionDuration = table.Column(type: "int", nullable: false), + ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ClientName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + HttpMethod = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), + Url = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Exceptions = table.Column(type: "nvarchar(max)", nullable: true), + Comments = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + HttpStatusCode = table.Column(type: "int", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpClaimTypes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Required = table.Column(type: "bit", nullable: false), + IsStatic = table.Column(type: "bit", nullable: false), + Regex = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + RegexDescription = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatureValues", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Value = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureValues", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpLinkUsers", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + SourceTenantId = table.Column(type: "uniqueidentifier", nullable: true), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetTenantId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpLinkUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpOrganizationUnits", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ParentId = table.Column(type: "uniqueidentifier", nullable: true), + Code = table.Column(type: "nvarchar(95)", maxLength: 95, nullable: false), + DisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpOrganizationUnits", x => x.Id); + table.ForeignKey( + name: "FK_AbpOrganizationUnits_AbpOrganizationUnits_ParentId", + column: x => x.ParentId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "AbpPermissionGrants", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpPermissionGrants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpPermissionGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpPermissionGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpPermissions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + IsEnabled = table.Column(type: "bit", nullable: false), + MultiTenancySide = table.Column(type: "tinyint", nullable: false), + Providers = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + StateCheckers = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpPermissions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpRoles", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + IsDefault = table.Column(type: "bit", nullable: false), + IsStatic = table.Column(type: "bit", nullable: false), + IsPublic = table.Column(type: "bit", nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + Identity = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + Action = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + UserId = table.Column(type: "uniqueidentifier", nullable: true), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpSettings", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Value = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: false), + ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpTenants", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpTenants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpUsers", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + Surname = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + EmailConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), + PasswordHash = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + SecurityStamp = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + IsExternal = table.Column(type: "bit", nullable: false, defaultValue: false), + PhoneNumber = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), + IsActive = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), + AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictApplications", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ClientId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ClientSecret = table.Column(type: "nvarchar(max)", nullable: true), + ConsentType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + DisplayName = table.Column(type: "nvarchar(max)", nullable: true), + DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), + Permissions = table.Column(type: "nvarchar(max)", nullable: true), + PostLogoutRedirectUris = table.Column(type: "nvarchar(max)", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), + Requirements = table.Column(type: "nvarchar(max)", nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ClientUri = table.Column(type: "nvarchar(max)", nullable: true), + LogoUri = table.Column(type: "nvarchar(max)", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictApplications", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictScopes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Descriptions = table.Column(type: "nvarchar(max)", nullable: true), + DisplayName = table.Column(type: "nvarchar(max)", nullable: true), + DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + Resources = table.Column(type: "nvarchar(max)", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpAuditLogActions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), + ServiceName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + MethodName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + Parameters = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), + ExecutionTime = table.Column(type: "datetime2", nullable: false), + ExecutionDuration = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAuditLogActions", x => x.Id); + table.ForeignKey( + name: "FK_AbpAuditLogActions_AbpAuditLogs_AuditLogId", + column: x => x.AuditLogId, + principalTable: "AbpAuditLogs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpEntityChanges", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ChangeTime = table.Column(type: "datetime2", nullable: false), + ChangeType = table.Column(type: "tinyint", nullable: false), + EntityTenantId = table.Column(type: "uniqueidentifier", nullable: true), + EntityId = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + EntityTypeFullName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEntityChanges", x => x.Id); + table.ForeignKey( + name: "FK_AbpEntityChanges_AbpAuditLogs_AuditLogId", + column: x => x.AuditLogId, + principalTable: "AbpAuditLogs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpOrganizationUnitRoles", + columns: table => new + { + RoleId = table.Column(type: "uniqueidentifier", nullable: false), + OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpOrganizationUnitRoles", x => new { x.OrganizationUnitId, x.RoleId }); + table.ForeignKey( + name: "FK_AbpOrganizationUnitRoles_AbpOrganizationUnits_OrganizationUnitId", + column: x => x.OrganizationUnitId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpOrganizationUnitRoles_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpRoleClaims", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RoleId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AbpRoleClaims_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpTenantConnectionStrings", + columns: table => new + { + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Value = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpTenantConnectionStrings", x => new { x.TenantId, x.Name }); + table.ForeignKey( + name: "FK_AbpTenantConnectionStrings_AbpTenants_TenantId", + column: x => x.TenantId, + principalTable: "AbpTenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserClaims", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + UserId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AbpUserClaims_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserLogins", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ProviderKey = table.Column(type: "nvarchar(196)", maxLength: 196, nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserLogins", x => new { x.UserId, x.LoginProvider }); + table.ForeignKey( + name: "FK_AbpUserLogins_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserOrganizationUnits", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserOrganizationUnits", x => new { x.OrganizationUnitId, x.UserId }); + table.ForeignKey( + name: "FK_AbpUserOrganizationUnits_AbpOrganizationUnits_OrganizationUnitId", + column: x => x.OrganizationUnitId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpUserOrganizationUnits_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserRoles", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + RoleId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AbpUserRoles_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpUserRoles_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserTokens", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AbpUserTokens_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictAuthorizations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), + CreationDate = table.Column(type: "datetime2", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + Scopes = table.Column(type: "nvarchar(max)", nullable: true), + Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "AbpEntityPropertyChanges", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + EntityChangeId = table.Column(type: "uniqueidentifier", nullable: false), + NewValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + OriginalValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + PropertyName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + PropertyTypeFullName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEntityPropertyChanges", x => x.Id); + table.ForeignKey( + name: "FK_AbpEntityPropertyChanges_AbpEntityChanges_EntityChangeId", + column: x => x.EntityChangeId, + principalTable: "AbpEntityChanges", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictTokens", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), + AuthorizationId = table.Column(type: "uniqueidentifier", nullable: true), + CreationDate = table.Column(type: "datetime2", nullable: true), + ExpirationDate = table.Column(type: "datetime2", nullable: true), + Payload = table.Column(type: "nvarchar(max)", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + RedemptionDate = table.Column(type: "datetime2", nullable: true), + ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictTokens", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId", + column: x => x.AuthorizationId, + principalTable: "OpenIddictAuthorizations", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogActions_AuditLogId", + table: "AbpAuditLogActions", + column: "AuditLogId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogActions_TenantId_ServiceName_MethodName_ExecutionTime", + table: "AbpAuditLogActions", + columns: new[] { "TenantId", "ServiceName", "MethodName", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogs_TenantId_ExecutionTime", + table: "AbpAuditLogs", + columns: new[] { "TenantId", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogs_TenantId_UserId_ExecutionTime", + table: "AbpAuditLogs", + columns: new[] { "TenantId", "UserId", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityChanges_AuditLogId", + table: "AbpEntityChanges", + column: "AuditLogId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityChanges_TenantId_EntityTypeFullName_EntityId", + table: "AbpEntityChanges", + columns: new[] { "TenantId", "EntityTypeFullName", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityPropertyChanges_EntityChangeId", + table: "AbpEntityPropertyChanges", + column: "EntityChangeId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", + table: "AbpFeatureValues", + columns: new[] { "Name", "ProviderName", "ProviderKey" }, + unique: true, + filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpLinkUsers_SourceUserId_SourceTenantId_TargetUserId_TargetTenantId", + table: "AbpLinkUsers", + columns: new[] { "SourceUserId", "SourceTenantId", "TargetUserId", "TargetTenantId" }, + unique: true, + filter: "[SourceTenantId] IS NOT NULL AND [TargetTenantId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnitRoles_RoleId_OrganizationUnitId", + table: "AbpOrganizationUnitRoles", + columns: new[] { "RoleId", "OrganizationUnitId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnits_Code", + table: "AbpOrganizationUnits", + column: "Code"); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnits_ParentId", + table: "AbpOrganizationUnits", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissionGrants_TenantId_Name_ProviderName_ProviderKey", + table: "AbpPermissionGrants", + columns: new[] { "TenantId", "Name", "ProviderName", "ProviderKey" }, + unique: true, + filter: "[TenantId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissionGroups_Name", + table: "AbpPermissionGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissions_GroupName", + table: "AbpPermissions", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissions_Name", + table: "AbpPermissions", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpRoleClaims_RoleId", + table: "AbpRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpRoles_NormalizedName", + table: "AbpRoles", + column: "NormalizedName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSettings_Name_ProviderName_ProviderKey", + table: "AbpSettings", + columns: new[] { "Name", "ProviderName", "ProviderKey" }, + unique: true, + filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpTenants_Name", + table: "AbpTenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserClaims_UserId", + table: "AbpUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserLogins_LoginProvider_ProviderKey", + table: "AbpUserLogins", + columns: new[] { "LoginProvider", "ProviderKey" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserOrganizationUnits_UserId_OrganizationUnitId", + table: "AbpUserOrganizationUnits", + columns: new[] { "UserId", "OrganizationUnitId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserRoles_RoleId_UserId", + table: "AbpUserRoles", + columns: new[] { "RoleId", "UserId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_Email", + table: "AbpUsers", + column: "Email"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_NormalizedEmail", + table: "AbpUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_NormalizedUserName", + table: "AbpUsers", + column: "NormalizedUserName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_UserName", + table: "AbpUsers", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictApplications_ClientId", + table: "OpenIddictApplications", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type", + table: "OpenIddictAuthorizations", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictScopes_Name", + table: "OpenIddictScopes", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type", + table: "OpenIddictTokens", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_AuthorizationId", + table: "OpenIddictTokens", + column: "AuthorizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ReferenceId", + table: "OpenIddictTokens", + column: "ReferenceId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpAuditLogActions"); + + migrationBuilder.DropTable( + name: "AbpClaimTypes"); + + migrationBuilder.DropTable( + name: "AbpEntityPropertyChanges"); + + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + + migrationBuilder.DropTable( + name: "AbpFeatureValues"); + + migrationBuilder.DropTable( + name: "AbpLinkUsers"); + + migrationBuilder.DropTable( + name: "AbpOrganizationUnitRoles"); + + migrationBuilder.DropTable( + name: "AbpPermissionGrants"); + + migrationBuilder.DropTable( + name: "AbpPermissionGroups"); + + migrationBuilder.DropTable( + name: "AbpPermissions"); + + migrationBuilder.DropTable( + name: "AbpRoleClaims"); + + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + + migrationBuilder.DropTable( + name: "AbpSettings"); + + migrationBuilder.DropTable( + name: "AbpTenantConnectionStrings"); + + migrationBuilder.DropTable( + name: "AbpUserClaims"); + + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + + migrationBuilder.DropTable( + name: "AbpUserLogins"); + + migrationBuilder.DropTable( + name: "AbpUserOrganizationUnits"); + + migrationBuilder.DropTable( + name: "AbpUserRoles"); + + migrationBuilder.DropTable( + name: "AbpUserTokens"); + + migrationBuilder.DropTable( + name: "OpenIddictScopes"); + + migrationBuilder.DropTable( + name: "OpenIddictTokens"); + + migrationBuilder.DropTable( + name: "AbpEntityChanges"); + + migrationBuilder.DropTable( + name: "AbpTenants"); + + migrationBuilder.DropTable( + name: "AbpOrganizationUnits"); + + migrationBuilder.DropTable( + name: "AbpRoles"); + + migrationBuilder.DropTable( + name: "AbpUsers"); + + migrationBuilder.DropTable( + name: "OpenIddictAuthorizations"); + + migrationBuilder.DropTable( + name: "AbpAuditLogs"); + + migrationBuilder.DropTable( + name: "OpenIddictApplications"); + } + } +} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs index b4acd389c1..3d693cc761 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -19,10 +19,10 @@ namespace MyCompanyName.MyProjectName.Host.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => { @@ -494,6 +494,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -680,6 +683,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasDefaultValue(false) .HasColumnName("EmailConfirmed"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -708,6 +714,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -756,6 +765,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -818,6 +830,33 @@ namespace MyCompanyName.MyProjectName.Host.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") @@ -958,6 +997,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(128)") .HasColumnName("DisplayName"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); @@ -1536,6 +1578,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("EntityVersion") + .HasColumnType("int"); + b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20221220103129_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20230324070202_Initial.Designer.cs similarity index 98% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20221220103129_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20230324070202_Initial.Designer.cs index e062627c6a..1f2dc7a5fb 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20221220103129_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20230324070202_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Mvc.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20221220103129_Initial")] + [Migration("20230324070202_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -717,6 +717,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -765,6 +768,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -827,6 +833,33 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20221220103129_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20230324070202_Initial.cs similarity index 97% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20221220103129_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20230324070202_Initial.cs index cebd34749a..1bb1b884bd 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20221220103129_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20230324070202_Initial.cs @@ -292,6 +292,22 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations table.PrimaryKey("PK_AbpTenants", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpUsers", columns: table => new @@ -315,7 +331,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -988,6 +1006,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations migrationBuilder.DropTable( name: "AbpUserClaims"); + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + migrationBuilder.DropTable( name: "AbpUserLogins"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs index 8498fc5efe..835bdad6be 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -714,6 +714,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -762,6 +765,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -824,6 +830,33 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs index 1033c73a57..fa55b7d44f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs @@ -4,6 +4,7 @@ using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using Medallion.Threading; using Medallion.Threading.Redis; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; @@ -163,6 +164,7 @@ public class MyProjectNameBlazorModule : AbpModule .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); + options.IntrospectAccessToken(); }) .AddAbpOpenIdConnect("oidc", options => { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameDbContext.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameDbContext.cs index 46f1217ffa..e1afd1a339 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameDbContext.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameDbContext.cs @@ -45,6 +45,7 @@ public class MyProjectNameDbContext : public DbSet OrganizationUnits { get; set; } public DbSet SecurityLogs { get; set; } public DbSet LinkUsers { get; set; } + public DbSet UserDelegations { get; set; } // Tenant Management public DbSet Tenants { get; set; } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20221220102812_Initial.Designer.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20230324065930_Initial.Designer.cs similarity index 98% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20221220102812_Initial.Designer.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20230324065930_Initial.Designer.cs index dc2a86c8b6..effdc6d3ee 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20221220102812_Initial.Designer.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20230324065930_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20221220102812_Initial")] + [Migration("20230324065930_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -770,6 +770,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -818,6 +821,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -880,6 +886,32 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20221220102812_Initial.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20230324065930_Initial.cs similarity index 98% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20221220102812_Initial.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20230324065930_Initial.cs index 42ecfe8c2c..ccf3e9776c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20221220102812_Initial.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20230324065930_Initial.cs @@ -313,6 +313,22 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpTenants", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpUsers", columns: table => new @@ -336,7 +352,9 @@ namespace MyCompanyName.MyProjectName.Migrations LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -1017,6 +1035,9 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpUserClaims"); + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + migrationBuilder.DropTable( name: "AbpUserLogins"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs index 674c8c4fc3..3805a9ee89 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -767,6 +767,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -815,6 +818,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -877,6 +883,32 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20221220114625_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20230324070230_Initial.Designer.cs similarity index 98% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20221220114625_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20230324070230_Initial.Designer.cs index 88bcfda8b0..4c20a44d53 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20221220114625_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20230324070230_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(AuthServerDbContext))] - [Migration("20221220114625_Initial")] + [Migration("20230324070230_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -717,6 +717,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -765,6 +768,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -827,6 +833,33 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20230324070230_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20230324070230_Initial.cs new file mode 100644 index 0000000000..3c62b40bf4 --- /dev/null +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20230324070230_Initial.cs @@ -0,0 +1,1055 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyCompanyName.MyProjectName.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpAuditLogs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + UserId = table.Column(type: "uniqueidentifier", nullable: true), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ImpersonatorUserId = table.Column(type: "uniqueidentifier", nullable: true), + ImpersonatorUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ImpersonatorTenantId = table.Column(type: "uniqueidentifier", nullable: true), + ImpersonatorTenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ExecutionTime = table.Column(type: "datetime2", nullable: false), + ExecutionDuration = table.Column(type: "int", nullable: false), + ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ClientName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + HttpMethod = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), + Url = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Exceptions = table.Column(type: "nvarchar(max)", nullable: true), + Comments = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + HttpStatusCode = table.Column(type: "int", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpClaimTypes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Required = table.Column(type: "bit", nullable: false), + IsStatic = table.Column(type: "bit", nullable: false), + Regex = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + RegexDescription = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatureValues", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Value = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureValues", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpLinkUsers", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + SourceTenantId = table.Column(type: "uniqueidentifier", nullable: true), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetTenantId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpLinkUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpOrganizationUnits", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ParentId = table.Column(type: "uniqueidentifier", nullable: true), + Code = table.Column(type: "nvarchar(95)", maxLength: 95, nullable: false), + DisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpOrganizationUnits", x => x.Id); + table.ForeignKey( + name: "FK_AbpOrganizationUnits_AbpOrganizationUnits_ParentId", + column: x => x.ParentId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "AbpPermissionGrants", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpPermissionGrants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpPermissionGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpPermissionGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpPermissions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + IsEnabled = table.Column(type: "bit", nullable: false), + MultiTenancySide = table.Column(type: "tinyint", nullable: false), + Providers = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + StateCheckers = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpPermissions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpRoles", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + IsDefault = table.Column(type: "bit", nullable: false), + IsStatic = table.Column(type: "bit", nullable: false), + IsPublic = table.Column(type: "bit", nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + Identity = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + Action = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + UserId = table.Column(type: "uniqueidentifier", nullable: true), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + TenantName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + CorrelationId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ClientIpAddress = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + BrowserInfo = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpSettings", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Value = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: false), + ProviderName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + ProviderKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpTenants", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpTenants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpUsers", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + Surname = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + EmailConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), + PasswordHash = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + SecurityStamp = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + IsExternal = table.Column(type: "bit", nullable: false, defaultValue: false), + PhoneNumber = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false, defaultValue: false), + IsActive = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), + AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), + EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictApplications", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ClientId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ClientSecret = table.Column(type: "nvarchar(max)", nullable: true), + ConsentType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + DisplayName = table.Column(type: "nvarchar(max)", nullable: true), + DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), + Permissions = table.Column(type: "nvarchar(max)", nullable: true), + PostLogoutRedirectUris = table.Column(type: "nvarchar(max)", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), + Requirements = table.Column(type: "nvarchar(max)", nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ClientUri = table.Column(type: "nvarchar(max)", nullable: true), + LogoUri = table.Column(type: "nvarchar(max)", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictApplications", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictScopes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Descriptions = table.Column(type: "nvarchar(max)", nullable: true), + DisplayName = table.Column(type: "nvarchar(max)", nullable: true), + DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + Resources = table.Column(type: "nvarchar(max)", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpAuditLogActions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), + ServiceName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + MethodName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + Parameters = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), + ExecutionTime = table.Column(type: "datetime2", nullable: false), + ExecutionDuration = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAuditLogActions", x => x.Id); + table.ForeignKey( + name: "FK_AbpAuditLogActions_AbpAuditLogs_AuditLogId", + column: x => x.AuditLogId, + principalTable: "AbpAuditLogs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpEntityChanges", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AuditLogId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ChangeTime = table.Column(type: "datetime2", nullable: false), + ChangeType = table.Column(type: "tinyint", nullable: false), + EntityTenantId = table.Column(type: "uniqueidentifier", nullable: true), + EntityId = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + EntityTypeFullName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEntityChanges", x => x.Id); + table.ForeignKey( + name: "FK_AbpEntityChanges_AbpAuditLogs_AuditLogId", + column: x => x.AuditLogId, + principalTable: "AbpAuditLogs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpOrganizationUnitRoles", + columns: table => new + { + RoleId = table.Column(type: "uniqueidentifier", nullable: false), + OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpOrganizationUnitRoles", x => new { x.OrganizationUnitId, x.RoleId }); + table.ForeignKey( + name: "FK_AbpOrganizationUnitRoles_AbpOrganizationUnits_OrganizationUnitId", + column: x => x.OrganizationUnitId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpOrganizationUnitRoles_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpRoleClaims", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RoleId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AbpRoleClaims_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpTenantConnectionStrings", + columns: table => new + { + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Value = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpTenantConnectionStrings", x => new { x.TenantId, x.Name }); + table.ForeignKey( + name: "FK_AbpTenantConnectionStrings_AbpTenants_TenantId", + column: x => x.TenantId, + principalTable: "AbpTenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserClaims", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + UserId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ClaimType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ClaimValue = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AbpUserClaims_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserLogins", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + ProviderKey = table.Column(type: "nvarchar(196)", maxLength: 196, nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserLogins", x => new { x.UserId, x.LoginProvider }); + table.ForeignKey( + name: "FK_AbpUserLogins_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserOrganizationUnits", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + OrganizationUnitId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserOrganizationUnits", x => new { x.OrganizationUnitId, x.UserId }); + table.ForeignKey( + name: "FK_AbpUserOrganizationUnits_AbpOrganizationUnits_OrganizationUnitId", + column: x => x.OrganizationUnitId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpUserOrganizationUnits_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserRoles", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + RoleId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AbpUserRoles_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpUserRoles_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserTokens", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + LoginProvider = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AbpUserTokens_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictAuthorizations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), + CreationDate = table.Column(type: "datetime2", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + Scopes = table.Column(type: "nvarchar(max)", nullable: true), + Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "AbpEntityPropertyChanges", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + EntityChangeId = table.Column(type: "uniqueidentifier", nullable: false), + NewValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + OriginalValue = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + PropertyName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + PropertyTypeFullName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEntityPropertyChanges", x => x.Id); + table.ForeignKey( + name: "FK_AbpEntityPropertyChanges_AbpEntityChanges_EntityChangeId", + column: x => x.EntityChangeId, + principalTable: "AbpEntityChanges", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictTokens", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ApplicationId = table.Column(type: "uniqueidentifier", nullable: true), + AuthorizationId = table.Column(type: "uniqueidentifier", nullable: true), + CreationDate = table.Column(type: "datetime2", nullable: true), + ExpirationDate = table.Column(type: "datetime2", nullable: true), + Payload = table.Column(type: "nvarchar(max)", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + RedemptionDate = table.Column(type: "datetime2", nullable: true), + ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictTokens", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId", + column: x => x.AuthorizationId, + principalTable: "OpenIddictAuthorizations", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogActions_AuditLogId", + table: "AbpAuditLogActions", + column: "AuditLogId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogActions_TenantId_ServiceName_MethodName_ExecutionTime", + table: "AbpAuditLogActions", + columns: new[] { "TenantId", "ServiceName", "MethodName", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogs_TenantId_ExecutionTime", + table: "AbpAuditLogs", + columns: new[] { "TenantId", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogs_TenantId_UserId_ExecutionTime", + table: "AbpAuditLogs", + columns: new[] { "TenantId", "UserId", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityChanges_AuditLogId", + table: "AbpEntityChanges", + column: "AuditLogId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityChanges_TenantId_EntityTypeFullName_EntityId", + table: "AbpEntityChanges", + columns: new[] { "TenantId", "EntityTypeFullName", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityPropertyChanges_EntityChangeId", + table: "AbpEntityPropertyChanges", + column: "EntityChangeId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", + table: "AbpFeatureValues", + columns: new[] { "Name", "ProviderName", "ProviderKey" }, + unique: true, + filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpLinkUsers_SourceUserId_SourceTenantId_TargetUserId_TargetTenantId", + table: "AbpLinkUsers", + columns: new[] { "SourceUserId", "SourceTenantId", "TargetUserId", "TargetTenantId" }, + unique: true, + filter: "[SourceTenantId] IS NOT NULL AND [TargetTenantId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnitRoles_RoleId_OrganizationUnitId", + table: "AbpOrganizationUnitRoles", + columns: new[] { "RoleId", "OrganizationUnitId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnits_Code", + table: "AbpOrganizationUnits", + column: "Code"); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnits_ParentId", + table: "AbpOrganizationUnits", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissionGrants_TenantId_Name_ProviderName_ProviderKey", + table: "AbpPermissionGrants", + columns: new[] { "TenantId", "Name", "ProviderName", "ProviderKey" }, + unique: true, + filter: "[TenantId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissionGroups_Name", + table: "AbpPermissionGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissions_GroupName", + table: "AbpPermissions", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissions_Name", + table: "AbpPermissions", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpRoleClaims_RoleId", + table: "AbpRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpRoles_NormalizedName", + table: "AbpRoles", + column: "NormalizedName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSettings_Name_ProviderName_ProviderKey", + table: "AbpSettings", + columns: new[] { "Name", "ProviderName", "ProviderKey" }, + unique: true, + filter: "[ProviderName] IS NOT NULL AND [ProviderKey] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AbpTenants_Name", + table: "AbpTenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserClaims_UserId", + table: "AbpUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserLogins_LoginProvider_ProviderKey", + table: "AbpUserLogins", + columns: new[] { "LoginProvider", "ProviderKey" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserOrganizationUnits_UserId_OrganizationUnitId", + table: "AbpUserOrganizationUnits", + columns: new[] { "UserId", "OrganizationUnitId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserRoles_RoleId_UserId", + table: "AbpUserRoles", + columns: new[] { "RoleId", "UserId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_Email", + table: "AbpUsers", + column: "Email"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_NormalizedEmail", + table: "AbpUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_NormalizedUserName", + table: "AbpUsers", + column: "NormalizedUserName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_UserName", + table: "AbpUsers", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictApplications_ClientId", + table: "OpenIddictApplications", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type", + table: "OpenIddictAuthorizations", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictScopes_Name", + table: "OpenIddictScopes", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type", + table: "OpenIddictTokens", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_AuthorizationId", + table: "OpenIddictTokens", + column: "AuthorizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ReferenceId", + table: "OpenIddictTokens", + column: "ReferenceId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpAuditLogActions"); + + migrationBuilder.DropTable( + name: "AbpClaimTypes"); + + migrationBuilder.DropTable( + name: "AbpEntityPropertyChanges"); + + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + + migrationBuilder.DropTable( + name: "AbpFeatureValues"); + + migrationBuilder.DropTable( + name: "AbpLinkUsers"); + + migrationBuilder.DropTable( + name: "AbpOrganizationUnitRoles"); + + migrationBuilder.DropTable( + name: "AbpPermissionGrants"); + + migrationBuilder.DropTable( + name: "AbpPermissionGroups"); + + migrationBuilder.DropTable( + name: "AbpPermissions"); + + migrationBuilder.DropTable( + name: "AbpRoleClaims"); + + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + + migrationBuilder.DropTable( + name: "AbpSettings"); + + migrationBuilder.DropTable( + name: "AbpTenantConnectionStrings"); + + migrationBuilder.DropTable( + name: "AbpUserClaims"); + + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + + migrationBuilder.DropTable( + name: "AbpUserLogins"); + + migrationBuilder.DropTable( + name: "AbpUserOrganizationUnits"); + + migrationBuilder.DropTable( + name: "AbpUserRoles"); + + migrationBuilder.DropTable( + name: "AbpUserTokens"); + + migrationBuilder.DropTable( + name: "OpenIddictScopes"); + + migrationBuilder.DropTable( + name: "OpenIddictTokens"); + + migrationBuilder.DropTable( + name: "AbpEntityChanges"); + + migrationBuilder.DropTable( + name: "AbpTenants"); + + migrationBuilder.DropTable( + name: "AbpOrganizationUnits"); + + migrationBuilder.DropTable( + name: "AbpRoles"); + + migrationBuilder.DropTable( + name: "AbpUsers"); + + migrationBuilder.DropTable( + name: "OpenIddictAuthorizations"); + + migrationBuilder.DropTable( + name: "AbpAuditLogs"); + + migrationBuilder.DropTable( + name: "OpenIddictApplications"); + } + } +} diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs index 1f8fc82b42..931645fcd4 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -714,6 +714,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -762,6 +765,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -824,6 +830,33 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20221220103713_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20230324070306_Initial.Designer.cs similarity index 97% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20221220103713_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20230324070306_Initial.Designer.cs index 452252dbbf..ec67c21814 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20221220103713_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20230324070306_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20221220103713_Initial")] + [Migration("20230324070306_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -717,6 +717,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -765,6 +768,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -827,6 +833,33 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20221220103713_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20230324070306_Initial.cs similarity index 97% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20221220103713_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20230324070306_Initial.cs index a0e5f0dcf1..bb45390890 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20221220103713_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20230324070306_Initial.cs @@ -292,6 +292,22 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations table.PrimaryKey("PK_AbpTenants", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpUsers", columns: table => new @@ -315,7 +331,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -825,6 +843,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations migrationBuilder.DropTable( name: "AbpUserClaims"); + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + migrationBuilder.DropTable( name: "AbpUserLogins"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs index 54362b1b03..82640002ba 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -714,6 +714,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -762,6 +765,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -824,6 +830,33 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20221220103825_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20230324070335_Initial.Designer.cs similarity index 97% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20221220103825_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20230324070335_Initial.Designer.cs index 20c70b78e1..4f740ff8a5 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20221220103825_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20230324070335_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20221220103825_Initial")] + [Migration("20230324070335_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -717,6 +717,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -765,6 +768,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -827,6 +833,33 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20221220103825_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20230324070335_Initial.cs similarity index 97% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20221220103825_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20230324070335_Initial.cs index 88f25f62c6..fddce44b7d 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20221220103825_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20230324070335_Initial.cs @@ -292,6 +292,22 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpTenants", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpUserDelegations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + SourceUserId = table.Column(type: "uniqueidentifier", nullable: false), + TargetUserId = table.Column(type: "uniqueidentifier", nullable: false), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserDelegations", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpUsers", columns: table => new @@ -315,7 +331,9 @@ namespace MyCompanyName.MyProjectName.Migrations LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), LockoutEnabled = table.Column(type: "bit", nullable: false, defaultValue: false), AccessFailedCount = table.Column(type: "int", nullable: false, defaultValue: 0), + ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), + LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -825,6 +843,9 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpUserClaims"); + migrationBuilder.DropTable( + name: "AbpUserDelegations"); + migrationBuilder.DropTable( name: "AbpUserLogins"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs index 52bc3189e8..02398bbd56 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace MyCompanyName.MyProjectName.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -714,6 +714,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("LastPasswordChangeTime") + .HasColumnType("datetimeoffset"); + b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -762,6 +765,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(256)") .HasColumnName("SecurityStamp"); + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("bit"); + b.Property("Surname") .HasMaxLength(64) .HasColumnType("nvarchar(64)") @@ -824,6 +830,33 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpUserClaims", (string)null); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("SourceUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("TargetUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpUserDelegations", (string)null); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { b.Property("UserId") diff --git a/tools/localization-key-synchronizer/LocalizationKeySynchronizer.exe b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.exe new file mode 100644 index 0000000000..c8675a0a67 Binary files /dev/null and b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.exe differ diff --git a/tools/localization-key-synchronizer/LocalizationKeySynchronizer.sln b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.sln new file mode 100644 index 0000000000..a9a9ba0988 --- /dev/null +++ b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalizationKeySynchronizer", "src\LocalizationKeySynchronizer.csproj", "{FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/tools/localization-key-synchronizer/README.md b/tools/localization-key-synchronizer/README.md new file mode 100644 index 0000000000..f2b9401a30 --- /dev/null +++ b/tools/localization-key-synchronizer/README.md @@ -0,0 +1,27 @@ +# Streamline Localization in Your ABP Project with this Console Tool + +Are you tired of manually managing your localization files for your software projects? Look no further than our new console application designed to streamline your localization workflow! + +![image](https://user-images.githubusercontent.com/58659931/218817197-827d1934-4378-4ccb-87d3-a9118cb5203d.png) + +The application's main menu provides three options: find asynchronous keys, apply changes in the exported file, or replace keys. Let's take a look at each option in more details. + +![image](https://user-images.githubusercontent.com/58659931/218817488-0ba34e67-3039-4162-8291-5eb4669a8868.png) +![image](https://user-images.githubusercontent.com/58659931/218818263-d8d2d8c5-fc77-40a6-ba5c-e1fb7a0180be.png) +![image](https://user-images.githubusercontent.com/58659931/218818359-79a65d48-4895-4ed8-9d34-b6282939ca48.png) + +If you choose to find asynchronous keys, you will be prompted to enter the default language path. Once entered, a multi-select menu will appear with all the JSON files in that folder. You can choose to find keys that do not match the number of arguments, missing keys, or both. If you choose to find the missing keys, you will be prompted to enter the absolute path to export the missing keys. Once entered, the export process will begin, and the application will close. + +![image](https://user-images.githubusercontent.com/58659931/218818963-747766a5-b1c0-420f-95d2-7017a5f63925.png) + +If you choose to find the keys that do not match the number of arguments, you can choose to delete, export, or both. If you choose to delete, the process will complete, and the application will close. If you choose to export, you will be prompted to enter the export path. Once entered, the export process will begin, and the application will close. If you choose to do both, both processes will complete, and the application will close. + +![image](https://user-images.githubusercontent.com/58659931/218820012-e1596f88-5916-4f4f-a482-402c692ed207.png) + +If you choose to apply changes in the exported file, you will be prompted to import the file, and the process will complete. + +![image](https://user-images.githubusercontent.com/58659931/218820350-bfc0ccae-06e8-46d6-853d-7a2d1df3b156.png) + + If you choose to replace keys, you will be prompted to enter the localization folder path, the old key, the new key, and select the JSON files where you want the changes to be made. Once all information is entered, the application will begin the replacement process, and it will close. + +Overall, our console application offers a simple and effective solution to manage your localization files. Try it out yourself today! diff --git a/tools/localization-key-synchronizer/src/AbpAsyncKey.cs b/tools/localization-key-synchronizer/src/AbpAsyncKey.cs new file mode 100644 index 0000000000..e46bb2d7be --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpAsyncKey.cs @@ -0,0 +1,16 @@ +namespace LocalizationKeySynchronizer; + +public class AbpAsyncKey +{ + public string NewValue = string.Empty; + + public AbpAsyncKey(string key, string reference) + { + Key = key; + Reference = reference; + } + + public virtual string Type => GetType().Name; + public string Key { get; set; } + public string Reference { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpAsyncLocalization.cs b/tools/localization-key-synchronizer/src/AbpAsyncLocalization.cs new file mode 100644 index 0000000000..396975f5b7 --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpAsyncLocalization.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace LocalizationKeySynchronizer; + +public class AbpAsyncLocalization +{ + public AbpAsyncLocalization(AbpLocalization localization, AbpLocalization reference, List asyncKeys) + { + Localization = localization; + Reference = reference; + AsyncKeys = asyncKeys; + } + + public AbpLocalization Localization { get; set; } + public AbpLocalization Reference { get; set; } + + public List AsyncKeys { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpAsyncLocalizationViewModel.cs b/tools/localization-key-synchronizer/src/AbpAsyncLocalizationViewModel.cs new file mode 100644 index 0000000000..c2a17ae378 --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpAsyncLocalizationViewModel.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace LocalizationKeySynchronizer; + +public class AbpAsyncLocalizationViewModel +{ + public AbpAsyncLocalizationViewModel(string referenceCulture, string culture, string path, List asyncKeys) + { + ReferenceCulture = referenceCulture; + Culture = culture; + Path = path; + AsyncKeys = asyncKeys; + } + + public string ReferenceCulture { get; set; } + public string Culture { get; set; } + public string Path { get; set; } + + public List AsyncKeys { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpLocalization.cs b/tools/localization-key-synchronizer/src/AbpLocalization.cs new file mode 100644 index 0000000000..78d82d556a --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpLocalization.cs @@ -0,0 +1,14 @@ +namespace LocalizationKeySynchronizer; + +public class AbpLocalization +{ + public AbpLocalization(string filePath, AbpLocalizationInfo localizationInfo) + { + FilePath = filePath; + LocalizationInfo = localizationInfo; + } + + public string FilePath { get; set; } + + public AbpLocalizationInfo LocalizationInfo { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpLocalizationInfo.cs b/tools/localization-key-synchronizer/src/AbpLocalizationInfo.cs new file mode 100644 index 0000000000..d8db3b4b1b --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpLocalizationInfo.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace LocalizationKeySynchronizer; + +// This class is used to deserialize the JSON string from culture file. +public class AbpLocalizationInfo +{ + public AbpLocalizationInfo(string culture, Dictionary texts) + { + Culture = culture; + Texts = texts; + } + + public string Culture { get; set; } + public Dictionary Texts { get; set; } + + public static bool TryDeserialize(string json, out AbpLocalizationInfo? localizationInfo) + { + return JsonHelper.TryDeserialize(json, out localizationInfo); + } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/ArgumentCountMismatch.cs b/tools/localization-key-synchronizer/src/ArgumentCountMismatch.cs new file mode 100644 index 0000000000..1b3630f116 --- /dev/null +++ b/tools/localization-key-synchronizer/src/ArgumentCountMismatch.cs @@ -0,0 +1,15 @@ +namespace LocalizationKeySynchronizer; + +public class ArgumentCountMismatch : AbpAsyncKey +{ + public ArgumentCountMismatch(string key, string reference, int referenceArgumentCount, int argumentCount, string value) : base(key, reference) + { + ReferenceArgumentCount = referenceArgumentCount; + ArgumentCount = argumentCount; + Value = value; + } + + public int ReferenceArgumentCount { get; } + public int ArgumentCount { get; } + public string Value { get; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/JsonHelper.cs b/tools/localization-key-synchronizer/src/JsonHelper.cs new file mode 100644 index 0000000000..2f284d5839 --- /dev/null +++ b/tools/localization-key-synchronizer/src/JsonHelper.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; + +namespace LocalizationKeySynchronizer; + +public static class JsonHelper +{ + public static bool TryDeserialize(string json, out T? result) + { + try + { + result = JsonConvert.DeserializeObject(json); + return true; + } + catch (Exception) + { + result = default; + return false; + } + } + + public static string Serialize(T value) + { + return JsonConvert.SerializeObject(value, Formatting.Indented); + } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/LocalizationHelper.cs b/tools/localization-key-synchronizer/src/LocalizationHelper.cs new file mode 100644 index 0000000000..fbdb8a1aef --- /dev/null +++ b/tools/localization-key-synchronizer/src/LocalizationHelper.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +namespace LocalizationKeySynchronizer; + +public static partial class LocalizationHelper +{ + public static bool TryGetLocalization(string path, out AbpLocalizationInfo? localizationInfo) + { + if (File.Exists(path) == false) + { + localizationInfo = default; + return false; + } + + var json = File.ReadAllTextAsync(path).GetAwaiter().GetResult(); + return AbpLocalizationInfo.TryDeserialize(json, out localizationInfo); + } + + public static List GetLocalizations(IEnumerable paths) + { + var results = new List(); + foreach (var path in paths) + { + if (TryGetLocalization(path, out var localizationInfo)) + { + results.Add(new AbpLocalization(path, localizationInfo!)); + } + } + + return results; + } + + private static Dictionary GetKeysAndArgCount(this AbpLocalizationInfo localizationInfo) + { + return localizationInfo.Texts.ToDictionary(k => k.Key, v => GetArgCount(v.Value)); + } + + private static int GetArgCount(string value) + { + var matches = MyRegex().Matches(value); + return matches.Count; + } + + public static List GetAsynchronousLocalizations(this AbpLocalization defaultLocalization, + IEnumerable otherLocalizations) + { + var results = new List(); + var defaultCultureKeysAndArgCount = defaultLocalization.LocalizationInfo.GetKeysAndArgCount(); + foreach (var localization in otherLocalizations) + { + var keysAndArgCount = localization.LocalizationInfo.GetKeysAndArgCount(); + var asynchronousResource = + new AbpAsyncLocalization(localization, defaultLocalization, new List()); + foreach (var (key, defaultCultureArgCount) in defaultCultureKeysAndArgCount) + { + if (keysAndArgCount.TryGetValue(key, out var value)) + { + if (value != defaultCultureArgCount) + { + asynchronousResource.AsyncKeys.Add(new ArgumentCountMismatch(key, + defaultLocalization.LocalizationInfo.Texts[key], defaultCultureArgCount, value, + localization.LocalizationInfo.Texts[key])); + } + } + else + { + asynchronousResource.AsyncKeys.Add(new MissingKey(key, + defaultLocalization.LocalizationInfo.Texts[key])); + } + } + + if (asynchronousResource.AsyncKeys.Any()) + { + results.Add(asynchronousResource); + } + } + + return results; + } + + public static void DeleteKeysThatDoNotMatchTheNumberOfArguments( + IEnumerable asynchronousResources) + { + foreach (var resource in asynchronousResources) + { + foreach (var key in resource.AsyncKeys.Select(x => x.Key)) + { + resource.Localization.LocalizationInfo.Texts.Remove(key); + } + + File.WriteAllTextAsync(resource.Localization.FilePath, + JsonHelper.Serialize(resource.Localization.LocalizationInfo)).GetAwaiter().GetResult(); + } + } + + public static void ExportKeysThatDoNotMatchTheNumberOfArguments( + IEnumerable asynchronousResources, string? exportPath) + { + Export(asynchronousResources, exportPath); + } + + public static void Export(IEnumerable asynchronousResources, string? exportPath) + where T : AbpAsyncKey + { + var asyncLocalizationViewModels = asynchronousResources.Select(x => + new AbpAsyncLocalizationViewModel(x.Reference.LocalizationInfo.Culture, + x.Localization.LocalizationInfo.Culture, x.Localization.FilePath, + x.AsyncKeys.Where(k => k is T).ToList())).ToList(); + + if (exportPath != null) + { + File.WriteAllTextAsync(exportPath, + JsonHelper.Serialize(asyncLocalizationViewModels)) + .GetAwaiter().GetResult(); + } + } + + public static void ExportMissingKeys(IEnumerable asyncLocalizations, string? exportPath) + { + Export(asyncLocalizations, exportPath); + } + + public static bool ApplyChanges(string path) + { + var json = File.ReadAllTextAsync(path).GetAwaiter().GetResult(); + + if (JsonHelper.TryDeserialize(json, out List? asyncLocalizationViewModels) == + false) + { + return false; + } + + foreach (var asyncLocalizationViewModel in asyncLocalizationViewModels!) + { + if (TryGetLocalization(asyncLocalizationViewModel.Path, out var localizationInfo) == false) + { + return false; + } + + foreach (var asyncKey in asyncLocalizationViewModel.AsyncKeys.Where(asyncKey => + !string.IsNullOrWhiteSpace(asyncKey.NewValue))) + { + localizationInfo!.Texts[asyncKey.Key] = asyncKey.NewValue; + } + + File.WriteAllTextAsync(asyncLocalizationViewModel.Path, + JsonHelper.Serialize(localizationInfo)).GetAwaiter().GetResult(); + } + + return true; + } + + public static void ReplaceKey(string oldKey, string newKey, List localizations) + { + foreach (var localization in localizations) + { + if (!localization.LocalizationInfo.Texts.TryGetValue(oldKey, out var value)) + { + continue; + } + + localization.LocalizationInfo.Texts.Remove(oldKey); + localization.LocalizationInfo.Texts.Add(newKey, value); + File.WriteAllTextAsync(localization.FilePath, + JsonHelper.Serialize(localization.LocalizationInfo)).GetAwaiter().GetResult(); + } + } + + [GeneratedRegex("{(\\d+)}")] + private static partial Regex MyRegex(); +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/LocalizationKeySynchronizer.csproj b/tools/localization-key-synchronizer/src/LocalizationKeySynchronizer.csproj new file mode 100644 index 0000000000..8f566bc239 --- /dev/null +++ b/tools/localization-key-synchronizer/src/LocalizationKeySynchronizer.csproj @@ -0,0 +1,15 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + diff --git a/tools/localization-key-synchronizer/src/MissingKey.cs b/tools/localization-key-synchronizer/src/MissingKey.cs new file mode 100644 index 0000000000..0463f3dd16 --- /dev/null +++ b/tools/localization-key-synchronizer/src/MissingKey.cs @@ -0,0 +1,8 @@ +namespace LocalizationKeySynchronizer; + +public class MissingKey : AbpAsyncKey +{ + public MissingKey(string key, string reference) : base(key, reference) + { + } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/Program.cs b/tools/localization-key-synchronizer/src/Program.cs new file mode 100644 index 0000000000..42c97f0754 --- /dev/null +++ b/tools/localization-key-synchronizer/src/Program.cs @@ -0,0 +1,173 @@ +using System; +using System.IO; +using System.Linq; +using LocalizationKeySynchronizer; +using Spectre.Console; +using static LocalizationKeySynchronizer.Questions; + +try +{ + // Do you want to find asynchronous keys, apply changes in the exported file or replace the keys? + var option = AnsiConsole.Prompt( + new SelectionPrompt() + .Title(_1.Question) + .AddChoices(_1.Options.Find, _1.Options.Apply, _1.Options.Replace)); + + switch (option) + { + case _1.Options.Apply: + { + // Enter the absolute path to the exported file: + var path = AnsiConsole.Ask(_2); + + if (!File.Exists(path)) + { + AnsiConsole.MarkupLine("[red]The file does not exist![/]"); + Exit(); + } + + if (LocalizationHelper.ApplyChanges(path)) + { + AnsiConsole.MarkupLine("[green]The changes have been applied successfully![/]"); + Exit(); + } + + AnsiConsole.MarkupLine("[red]An error occurred while applying changes![/]"); + Exit(); + break; + } + case _1.Options.Find: + { + // The default language path + var path = AnsiConsole.Ask(_3); + + if (!LocalizationHelper.TryGetLocalization(path, out var defaultLocalizationInfo)) + { + AnsiConsole.MarkupLine("[red]The default language path is invalid![/]"); + Exit(); + } + + var defaultCulture = new AbpLocalization(path, defaultLocalizationInfo!); + +// Get others cultures + var paths = Directory.GetFiles(Path.GetDirectoryName(path)!, "*.json", + SearchOption.TopDirectoryOnly); + + var otherCulturePaths = paths.Select(Path.GetFileNameWithoutExtension).Where(x=>!string.IsNullOrWhiteSpace(x)).Select(x=>x!).ToList(); + // select other cultures + paths = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title("Select other cultures") + .PageSize(10).AddChoiceGroup("All", otherCulturePaths)) + .Select(x => Path.Combine(Path.GetDirectoryName(path)!, x + ".json")) + .ToArray(); + + var otherCultures = LocalizationHelper.GetLocalizations(paths); + var asyncLocalizations = defaultCulture.GetAsynchronousLocalizations(otherCultures); + +// Find keys that do not match the number of arguments, find missing keys, or both + + var options = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title(_4.Question) + .PageSize(10) + .AddChoiceGroup("All", _4.Options.ArgumentsCount, _4.Options.MissingKeys)); + +// For arguments +// Find keys that do not match the number of arguments + + string? exportPath = null; + if (options.Contains(_4.Options.ArgumentsCount)) + { + // Should the keys that do not match the number of arguments be deleted, exported or both? + + var options2 = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title(_5.Question) + .PageSize(10) + .AddChoiceGroup("All", _5.Options.Delete, _5.Options.Export)); + + // Delete the keys that do not match the number of arguments + if (options2.Contains(_5.Options.Delete)) + { + LocalizationHelper.DeleteKeysThatDoNotMatchTheNumberOfArguments(asyncLocalizations); + } + + // Ask for the export path and export it + if (options2.Contains(_5.Options.Export)) + { + if (options.Contains(_4.Options.MissingKeys)) + { + exportPath = AnsiConsole.Ask(_8); + LocalizationHelper.Export(asyncLocalizations, exportPath); + } + else + { + exportPath = AnsiConsole.Ask(_6); + LocalizationHelper.ExportKeysThatDoNotMatchTheNumberOfArguments(asyncLocalizations, exportPath); + } + } + } + +// For missing keys +// Export missing keys + if (options.Contains(_4.Options.MissingKeys)) + { + if (string.IsNullOrEmpty(exportPath)) + { + exportPath = AnsiConsole.Ask(_7); + LocalizationHelper.ExportMissingKeys(asyncLocalizations, exportPath); + } + } + + break; + } + case _1.Options.Replace: + { + // The localization folder path + var path = AnsiConsole.Ask(_9); + + // Old key + var oldKey = AnsiConsole.Ask(_10); + + // New key + var newKey = AnsiConsole.Ask(_11); + + // Localization paths + var paths = Directory.GetFiles(path, "*.json", + SearchOption.TopDirectoryOnly); + + // Select localizations + + var localizationPaths = paths.Select(Path.GetFileNameWithoutExtension).Where(x=>!string.IsNullOrWhiteSpace(x)).Select(x=>x!).ToList(); + paths = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title("Select localizations") + .PageSize(10) + .AddChoiceGroup("All", localizationPaths)) + .Select(x => Path.Combine(path, x + ".json")) + .ToArray(); + + var cultures = LocalizationHelper.GetLocalizations(paths); + + // Replace keys + LocalizationHelper.ReplaceKey(oldKey, newKey, cultures); + + AnsiConsole.MarkupLine("[green]The keys have been replaced successfully![/]"); + break; + } + } +} +catch (Exception e) +{ + Console.WriteLine(e); + AnsiConsole.MarkupLine($"[red]{e.Message}[/]"); + Exit(); +} + +void Exit() +{ + AnsiConsole.MarkupLine("[red]Press any key to exit...[/]"); + Console.ReadKey(); + Environment.Exit(0); +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/Questions.cs b/tools/localization-key-synchronizer/src/Questions.cs new file mode 100644 index 0000000000..a284fbaf9b --- /dev/null +++ b/tools/localization-key-synchronizer/src/Questions.cs @@ -0,0 +1,57 @@ +namespace LocalizationKeySynchronizer; + +public static class Questions +{ + public const string _2 = "Enter the absolute path to the exported file:"; + + public const string _3 = "Enter the default language path:"; + + public const string _6 = "Enter the absolute path to export the keys that do not match the number of arguments:"; + + public const string _7 = "Enter the absolute path to export the missing keys:"; + + public const string _8 = "Enter the export path:"; + + public const string _9 = "Enter the localization folder path:"; + + public const string _10 = "Enter the old key:"; + + public const string _11 = "Enter the new key:"; + + public static class _1 + { + public const string Question = + "Do you want to find asynchronous keys, apply changes in the exported file or replace the keys?"; + + public static class Options + { + public const string Find = "Find asynchronous keys"; + public const string Apply = "Apply changes in the exported file"; + public const string Replace = "Replace keys"; + } + } + + public static class _4 + { + public const string Question = + "Find keys that do not match the number of arguments, find missing keys, or both?"; + + public static class Options + { + public const string ArgumentsCount = "Not matching arguments count"; + public const string MissingKeys = "Missing keys"; + } + } + + public static class _5 + { + public const string Question = + "Should the keys that do not match the number of arguments be deleted, exported or both?"; + + public static class Options + { + public const string Delete = "Delete"; + public const string Export = "Export"; + } + } +} \ No newline at end of file