Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into dev

pull/9633/head
Alper Ebicoglu 5 years ago
parent
commit
c15c221014
  1. 28
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json
  2. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/it.json
  3. 71
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json
  4. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/it.json
  5. 80
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json
  6. 10
      docs/en/Apps/VoloDocs.md
  7. 26
      docs/en/Multi-Tenancy.md
  8. BIN
      docs/en/images/docs-create-project-v4.4.0.png
  9. 7
      framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostingHostBuilderExtensions.cs
  10. 1
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json
  11. 2
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/it.json
  12. 30
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json
  13. 2
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/it.json
  14. 1
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/it.json
  15. 7
      modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/it.json
  16. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json
  17. 2
      modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/it.json
  18. 22
      npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts
  19. 5
      npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts
  20. 1
      npm/ng-packs/packages/account/src/lib/proxy/account/models.ts
  21. 1219
      npm/ng-packs/packages/account/src/lib/proxy/generate-proxy.json
  22. 2
      npm/ng-packs/packages/core/src/lib/core.module.ts
  23. 8
      npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts
  24. 6
      npm/ng-packs/packages/core/src/lib/models/common.ts
  25. 3
      npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts
  26. 9
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  27. 44
      npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts
  28. 5
      npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts
  29. 6
      npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts
  30. 6
      npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts
  31. 2
      npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts
  32. 32
      npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts
  33. 1
      npm/ng-packs/packages/core/src/lib/tokens/index.ts
  34. 3
      npm/ng-packs/packages/core/src/lib/tokens/tenant-key.token.ts
  35. 60
      npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts
  36. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Program.cs
  37. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Program.cs
  38. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/Program.cs
  39. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs
  40. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/Program.cs
  41. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/Program.cs
  42. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/Program.cs
  43. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs
  44. 5
      templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/Program.cs
  45. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Program.cs
  46. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs
  47. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Program.cs
  48. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/Program.cs
  49. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Program.cs
  50. 5
      templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/Program.cs

28
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json

@ -277,6 +277,32 @@
"ThisExtensionIsNotAllowed": "Questa estensione non è consentita.",
"TheFileIsTooLarge": "Il file è troppo grande!",
"ArticleDeletionConfirmationMessage": "Sei sicuro di voler eliminare definitivamente questo articolo?",
"ChooseCoverImage": "Scegli un'immagine di copertina..."
"ChooseCoverImage": "Scegli un'immagine di copertina...",
"Menu:Quotation": "Quotation",
"Menu:Invoice": "Fattura",
"Menu:PaymentRequests": "Richieste Pagamento",
"Permission:PaymentRequests": "Richieste Pagamento",
"PaymentRequests": "Richieste Pagamento",
"Creator": "Creatore",
"ExtraProperties": "Proprietà Extra",
"Organization": "Organizzazione",
"Waiting": "In attesa",
"Completed": "Completato",
"Failed": "Fallito",
"PaymentRequestDeletionWarningMessage": "Questa richiesta pagamento sarà cancellata. Confermi?",
"Payment": "Pagamento",
"AddPayment": "Aggiungi Pagamento",
"Enum:PurchaseType:1": "Nuova Licenza",
"Enum:PurchaseType:2": "Estendi Licenza",
"Enum:PurchaseType:3": "Aggiornamento Licenza",
"Enum:PurchaseType:4": "Sviluppatore Aggiuntivo",
"LicenceType": "Tipo Licenza",
"PurchaseType": "Tipo Acquisto",
"ReceiptNo": "Ricevuta n.",
"PaymentTime": "Tipo Pagamento",
"ProductPrice": "Prezzo Prodotto",
"AdditionalDeveloper": "Sviluppatore aggiuntivo",
"ThisPaymentHasBeenAlreadyUsed": "Questo pagamento è già stato utilizzato",
"PaymentTimeCannotBeFutureTime": "Orario di pagamento non può essere nel futuro"
}
}

5
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/it.json

@ -32,6 +32,9 @@
"SeeDocuments": "Vedi Documenti",
"Samples": "Campioni",
"Framework": "Framework",
"Support": "Supporto"
"Support": "Supporto",
"FreeDDDBook": "DDD E-Book Gratuito",
"New": "Nuovo",
"Volo.AbpIo.Domain:020005": "Estensione Licenza non può essere inferiore a {MinExtendLicenseYear} anno e maggiore di {MaxExtendLicenseYear} anni "
}
}

71
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json

@ -98,7 +98,7 @@
"Details": "Dettagli",
"LeptonTheme": "Tema Lepton",
"ProfessionalModernUIThemes": "Temi dell'interfaccia utente moderni e professionali",
"LeptonThemeExplanation": "Lepton fornisce una gamma di temi di amministrazione Bootstrap che fungono da solida base per qualsiasi progetto che richieda dashboard di amministrazione.",
"LeptonThemeExplanation": "Lepton fornisce una gamma di temi di amministrazione Bootstrap che fungono da solida base per qualsiasi progetto che richieda cruscotto di amministrazione.",
"DefaultTheme": "Tema predefinito",
"MaterialTheme": "Tema Material",
"Default2Theme": "Tema predefinito 2",
@ -291,6 +291,73 @@
"PayViaAmexCard": "Come posso pagare tramite la mia carta AMEX?",
"PayViaAmexCardDescription": "Il gateway di pagamento predefinito \"Iyzico\" potrebbe rifiutare alcune carte di credito AMEX a causa delle misure di sicurezza. In questo caso, puoi pagare tramite il gateway di pagamento alternativo \"2Checkout\".",
"ThankYou": "Grazie",
"InvalidReCaptchaErrorMessage": "Si è verificato un errore durante la verifica di reCAPTCHA. Per favore riprova."
"InvalidReCaptchaErrorMessage": "Si è verificato un errore durante la verifica di reCAPTCHA. Per favore riprova.",
"CompanyName": "Nome Azienda",
"YourCompanyName": "Il nome della tua azienda",
"FirstName": "Nome",
"LastName": "Cognome",
"Optional": "Facoltativo",
"YourFirstName": "Il tuo nome",
"YourLastName": "Il tuo cognome",
"SpecialOffer": "Offerta Speciale",
"SpecialOfferMessage": "Fai presto! I prezzi sono validi per un periodo limitato.",
"DiscountRequest": "Richiesta Sconto",
"DiscountRequestDescribeCustomerQuestion": "Quale delle seguenti ti descrive?",
"DiscountRequestStudentEmailMessage": "L'indirizzo email deve contenere 'edu'.",
"DiscountRequestDeveloperCount": "Quanti sviluppatori ci sono?",
"DiscountRequestDeveloperCountExceedMessage": "Non concediamo sconti per le aziende con più di {0} sviluppatori.",
"DiscountRequestOrganizationName": "Nome Azienda/Organizzazione/Istituto Scolastico",
"Website": "Sito web",
"GithubUsername": "Nome utente GitHub",
"PhoneNumber": "Numero Telefono",
"Country": "Nazione",
"DescribeABPCommercialUsage": "Descrivi il progetto che stai pianificando di sviluppare su ABP Commercial",
"DiscountRequestCertifyInformationMessage": "Dichiaro che tutte le informazioni fornite corrispondono al vero e sono corrette",
"DiscountRequestReceived": "Abbiamo ricevuto la tua richiesta di sconto.",
"DiscountRequestStatusMessage": "Risponderemo dopo aver verificato le informazioni fornite.",
"MVCOrRazorPages": "MVC (Razor Pages)",
"Angular": "Angular",
"Blazor": "Blazor",
"New": "Nuovo",
"MongoDB": "MongoDB",
"EBookDDD": "E-Book Domain Driven Design",
"PracticalGuideForImplementingDDD": "Questo libro è una guida pratica per implementare il Domain Driven Design con ABP Framework.",
"IntroducingDDD": "Introduzione al Domain Driven Design",
"DDDLayersAndCleanArchitecture": "DDD Strati e Architettura Pulita",
"LayeringOfADotnetSolution": "Stratificazione di una Soluzione .NET",
"ImplementingDDDBuildingBlocks": "Implementazione delle Fondamenta del DDD",
"DomainVsApplicationLogic": "Logica di Dominio contro Logica Applicazione",
"SamplesAndDiscussions": "Esempi e Discussioni",
"Free": "Gratuito",
"Download": "Download",
"DDDEBook": "DDD E-book",
"ImplementingDDD": "Implementazione Domain Driven Design",
"DDDBookExplanation": "Una guida pratica per implementare il Domain Driven Design con ABP Framework.",
"Overview": "Panoramica",
"DDDBookPracticalGuide": "Questa a una guida pratica per implementare Domain Driven Design (DDD). Mentre i dettagli per l'implementazione sono basati sull'infrastruttura di ABP Framework, i concetti base, principi e modelli possono essere applicati a qualsiasi soluzione, anche se non è una soluzione .NET.",
"TableOfContents": "Sommario",
"IntroductionToImplementingDDD": "Introduzione all'Implementazione Domain Driven Design",
"WhatIsDDD": "Cos'è il Domain Driven Design?",
"Implementation": "Implementazione",
"TheBigPicture": "Il Quadro Generale",
"TheBuildingBlock": "Le Fondamenta",
"ExampleUseCase": "Esempio d'uso",
"DomainAndApplicationLogic": "Logica di Dominio e Logica Applicazione",
"Author": "Autore",
"PublishedOn": "Pubblicato il",
"Page": "Pagina",
"FreeEBook": "E-Book Gratuito",
"EBookSignInForDownload": "Accedi per scaricare l'e-book",
"SignIn": "Accedi",
"Or": "O",
"TellUsAboutYourself": "Dicci qualcosa di te",
"Surname": "Cognome",
"DoYouAgreePrivacyPolicy": "Acconsento ai <a href=\"/TermsConditions\">Termini e Condizioni<a/> ed alla <a href=\"/Privacy\">Politica Privacy</a>.",
"VolosoftMarketingInformationMessage": "Vorrei informazioni, suggerimenti e offerte sulle Soluzioni per Aziende e Organizzazioni e altri prodotti e servizi Volosoft",
"VolosoftSharingInformationMessage": "Vorrei che Volosoft condividesse le mie informazioni con partner selezionati in modo da poter ricevere informazioni rilevanti sui loro prodotti e servizi",
"WeWillSendYouADownloadLink": "Un link per scaricare il libro e-book è stato spedito a {0}.<br/> Controlla nella posta in arrivo / indesiderata / spam!",
"InvalidFormInputs": "Per favore, digita le informazioni valide specificate nel modulo",
"DDDBookEmailBody": "Grazie. <br /> Per scaricare il tuo libro, <a href=\"{0}\">clicca qui</a>.",
"FreeDDDEBook": "E-Book Gratuito DDD"
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/it.json

@ -2,7 +2,7 @@
"culture": "it",
"texts": {
"Permission:CommunityArticle": "Articolo della comunità",
"Permission:Edit": "modifica",
"Permission:Edit": "Modifica",
"Waiting": "In attesa",
"Approved": "Approvato",
"Rejected": "Respinto",
@ -80,7 +80,7 @@
"LatestGithubAnnouncements": "Ultimi annunci su GitHub",
"SeeAllAnnouncements": "Vedi tutti gli annunci",
"LatestBlogPost": "Ultimo post sul blog",
"Edit": "modifica",
"Edit": "Modifica",
"ProfileImageChange": "Cambia l'immagine del profilo",
"BlogItemErrorMessage": "Impossibile ottenere i dettagli più recenti del post del blog da ABP.",
"PlannedReleaseDate": "Data di rilascio prevista",

80
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json

@ -117,7 +117,7 @@
"ABPCLIExplanation": "ABP CLI (Command Line Interface) è uno strumento a riga di comando per eseguire alcune operazioni comuni per soluzioni basate su ABP.",
"ModularityExplanation": "ABP fornisce un'infrastruttura completa per creare i propri moduli applicativi che possono avere entità, servizi, integrazione di database, API, componenti dell'interfaccia utente e così via.",
"MultiTenancyExplanation": "Il framework ABP non solo supporta lo sviluppo di applicazioni multi-tenant, ma rende anche il tuo codice per lo più inconsapevole del multi-tenancy.",
"MultiTenancyExplanation2": "Può determinare automaticamente il tenant corrente, isolare i dati di diversi inquilini l'uno dall'altro.",
"MultiTenancyExplanation2": "Può determinare automaticamente il tenant corrente, isolare i dati dei diversi tenant l'uno dall'altro.",
"MultiTenancyExplanation3": "Supporta database singolo, database per tenant e approcci ibridi.",
"MultiTenancyExplanation4": "Ti concentri sul tuo codice aziendale e lasci che il framework gestisca il multi-tenancy per tuo conto.",
"BootstrapTagHelpersExplanation": "Invece di scrivere manualmente i dettagli ripetuti dei componenti bootstrap, usa gli helper tag di ABP per semplificarlo e sfruttare intellisense. Puoi sicuramente usare Bootstrap ogni volta che ne hai bisogno.",
@ -197,6 +197,82 @@
"SelectLanguage": "Seleziona la lingua",
"LatestArticleOnCommunity": "Ultimo articolo sulla comunità ABP",
"Register": "Registrati",
"IsDownloadable": "È scaricabile"
"IsDownloadable": "È scaricabile",
"DatabaseOptions": "Opzioni Database",
"BackToPackagesPage": "Torna alla Pagina Pacchetti",
"HowToInstall": "Come Installare",
"SeeOnNpm": "Vedi su NPM",
"SeeOnNuget": "Vedi su Nuget",
"MVCGulpCommandExplanation": "Se stai usando UI MVC (Razor Pages), esegui il comando \"gulp\" dopo l'installazione del pacchetto.",
"UsingABPCLI": "Usa <a href=\"https://docs.abp.io/en/abp/latest/CLI\">Abp CLI</a>",
"WithoutABPCLI": "Senza ABP CLI",
"ABPCLIModuleDependency": "ABP CLI aggiunge automaticamente le dipendenze del modulo.",
"AddModuleDependency": "Quindi aggiunge le dipendenze del modulo",
"Packages": "Pacchetti",
"NugetPackages": "Pacchetti Nuget",
"NPMPackages": "Pacchetti NPM",
"SeeDocs": "Vedi Documentazione",
"None": "Nessuno",
"Application": "Applicazione",
"Module": "Moduli",
"PackageName": "Nome Pacchetto",
"LicenseURL": "URL Licenza",
"License": "Licenza",
"ProjectCreationSuccessMessage": "Il tuo progetto è stato creato con successo",
"HowToRunSolution": "Come Eseguire la Tua Soluzione?",
"GettingStartedMessage": "Guarda la documentazione su come iniziare per imparare a configurare ed eseguire la tua soluzione.",
"WebAppDevTutorial": "Tutorial Sviluppo Web App",
"WebAppDevTutorialMessage": "Guarda tutorial sviluppo applicazione web per un esempio di sviluppo passo passo.",
"CommunityArticles": "Articoli Comunità",
"CommunityArticleMessage": "Verifica la piattaforma Comunità ABP per leggere articoli utili su ABP Framework.",
"InvestigateSolutionDetails": "Vedi Dettagli della Soluzione",
"StartupTemplateDocumentationMessage": "Guarda il modello di avvio dell'applicazione per imparare l'architettura e la struttura della tua soluzione.",
"ClientSideDevelopment": "Sviluppo Lato Client",
"ClientSideDevelopmentDocumentationMessage": "Guarda il documento {0} per imparare i punti chiave dello sviluppo dell'interfaccia utente (lato client).",
"DatabaseProviderDocumentationMessage": "Guarda il documento {0} per imparare i punti chiave dello sviluppo dello strato database",
"ABPCommercialExplanationMessage": "ABP Commercial fornisce moduli premium, temi, strumenti e supporto per ABP Framework.",
"ImplementingDDD": "Implementazione Domain Driven Design",
"DDDBookExplanation": "Una guida pratica per l'implementazione del Domain Driven Design con ABP Framework.",
"Overview": "Panoramica",
"DDDBookPracticalGuide": "Questa a una guida pratica per implementare Domain Driven Design (DDD). Mentre i dettagli per l'implementazione sono basati sull'infrastruttura di ABP Framework, i concetti base, principi e modelli possono essere applicati a qualsiasi soluzione, anche se non è una soluzione .NET.",
"TableOfContents": "Sommario",
"IntroductionToImplementingDDD": "Introduzione all'implementazione del Domain Driven Design",
"WhatIsDDD": "Cos'è il Domain Driven Design?",
"Implementation": "Implementazione",
"TheBigPicture": "Il Quadro Generale",
"TheBuildingBlock": "Le Fondamenta",
"ExampleUseCase": "Esempio d'uso",
"DomainAndApplicationLogic": "Logica di Dominio e Logica Applicazione",
"Author": "Autore",
"Pages": "Pagine",
"PublishedOn": "Pubblicato il",
"FreeEBook": "E-Book Gratuito",
"Download": "Scarica",
"EBookSignInForDownload": "Accedi per scaricare l'e-book",
"SignIn": "Accedi",
"Or": "O",
"TellUsAboutYourself": "Dicci qualcosa di te",
"Name": "Nome",
"Surname": "Cognome",
"CompanyName": "Nome Azienda",
"DoYouAgreePrivacyPolicy": "Acconsento ai <a href=\"https://account.abp.io/Account/TermsConditions\">Termini e Condizioni</a> ed alla <a href=\"https://account.abp.io/Account/Privacy\">Politica Privacy</a>.",
"Free": "Gratuito",
"DDDEBook": "DDD E-Book",
"PracticalGuideForImplementingDDD": "Questo libro è una guida pratica per l'implementazione del Domain Driven Design con ABP Framework.",
"IntroducingDDD": "Introduzione al Domain Driven Design",
"DDDLayersAndCleanArchitecture": "DDD Strati e Architettura Pulita",
"LayeringOfADotnetSolution": "Stratificazione di una Soluzione .NET",
"ImplementingDDDBuildingBlocks": "Implementazione delle Fondamenta del DDD",
"DomainVsApplicationLogic": "Logica di Dominio contro Logica Applicazione",
"SamplesAndDiscussions": "Esempi e Discussioni",
"EmailNotValid": "Per favore inserisci un indirizzo email valido.",
"WeWillSendYouADownloadLink": "Un link per scaricare il libro e-book è stato spedito a {0}.<br/> Controlla nella posta in arrivo, indesiderata o spam!",
"GoHome": "Vai alla Home",
"InvalidFormInputs": "Per favore, digita le informazioni valide specificate nel modulo",
"DDDBookEmailBody": "Grazie. <br /> Per scaricare il tuo libro, <a href=\"{0}\">clicca qui</a>.",
"SubscribeToNewsletter": "Sottiscrivi la nostra newsletter per ricevere informazione su cosa accade nella Piattaforma ABP.IO, come nuove versioni, articoli, offerte ed altro ancora.",
"FirstEdition": "Prima Edizione",
"ThankYou": "Grazie!",
"CheckboxMandatory": "Devi mettere la spunta su questo per procedere!"
}
}

10
docs/en/Apps/VoloDocs.md

@ -49,7 +49,7 @@ When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `M
- ### Configuring Website
Go to `Web` folder in your VoloDocs directory. Open `appsettings.json` in your text editor. Set your connection string (same as in the `Migrator`'s `appsettings.json`). Set `title` of your website. This will be written on the left-upper corner of your website. That's it! Now you can publish your website.
Go to `Web` folder in your VoloDocs directory. Open `appsettings.json` in your text editor. Set your connection string (same as in the `Migrator`'s `appsettings.json`). That's it! Now you can publish your website.
If you want to run
@ -127,6 +127,8 @@ When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `M
* **Navigation document name**: `docs-nav.json` ([see the sample navigation](https://github.com/abpframework/abp/blob/master/docs/en/docs-nav.json))
* **Parameters Document Name**: `docs-params.json` ([see the sample parameters](https://github.com/abpframework/abp/blob/dev/docs/en/docs-params.json))
* **Minimum version**: *leave empty* *(hides the previous versions)*
* **Main web site URL**: `/`
@ -139,7 +141,11 @@ When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `M
* **GitHub user agent**: [see how to learn your GitHub username](#learn-your-github-username)
![Creating a new project](../images/docs-create-project.jpg)
* **GitHub version provider source**: `Releases` (other option is `Branches`)
* **Version branch prefix**: leave empty ()
![Creating a new project](../images/docs-create-project-v4.4.0.png)
##### Retrieving GitHub Access Token

26
docs/en/Multi-Tenancy.md

@ -296,6 +296,32 @@ services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
});
````
If you change the `TenantKey`, make sure to pass it to `CoreModule` in the Angular client as follows:
```js
@NgModule({
imports: [
CoreModule.forRoot({
// ...
tenantKey: 'MyTenantKey'
}),
],
// ...
})
export class AppModule {}
```
If you need to access it, you can inject it as follows:
```js
import { Inject } from '@angular/core';
import { TENANT_KEY } from '@abp/ng.core';
class SomeComponent {
constructor(@Inject(TENANT_KEY) private tenantKey: string) {}
}
```
> 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.
##### Domain/Subdomain Tenant Resolver

BIN
docs/en/images/docs-create-project-v4.4.0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

7
framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/Hosting/AbpAspNetCoreHostBuilderExtensions.cs → framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostingHostBuilderExtensions.cs

@ -1,9 +1,8 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.Hosting
{
public static class AbpAspNetCoreHostBuilderExtensions
public static class AbpHostingHostBuilderExtensions
{
public const string AppSettingsSecretJsonPath = "appsettings.secrets.json";
@ -13,9 +12,9 @@ namespace Microsoft.Extensions.Hosting
bool reloadOnChange = true,
string path = AppSettingsSecretJsonPath)
{
return hostBuilder.ConfigureAppConfiguration(appConfig =>
return hostBuilder.ConfigureAppConfiguration((_, builder) =>
{
appConfig.AddJsonFile(
builder.AddJsonFile(
path: AppSettingsSecretJsonPath,
optional: optional,
reloadOnChange: reloadOnChange

1
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json

@ -13,6 +13,7 @@
"DefaultErrorMessage404": "Risorsa non trovata!",
"DefaultErrorMessage404Detail": "La risorsa richiesta non è stata trovata sul server!",
"EntityNotFoundErrorMessage": "Non esiste un'entità {0} con id = {1}!",
"AbpDbConcurrencyErrorMessage": "I dati che hai inviato sono già stati modificati da un altro utente/cliente. Per favore scarta le modifiche che hai fatto e riprova dall'inizio.",
"Error": "Errore",
"UnhandledException": "Eccezione non gestita!",
"401Message": "Non autorizzato",

2
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/it.json

@ -4,7 +4,7 @@
"UserName": "Nome utente",
"EmailAddress": "Indirizzo e-mail",
"UserNameOrEmailAddress": "Username o Indirizzo Email",
"Password": "Parola d'ordine",
"Password": "Password",
"RememberMe": "Ricordati di me",
"UseAnotherServiceToLogin": "Usa un altro servizio per accedere",
"UserLockedOutMessage": "L'account utente è stato bloccato a causa di tentativi di accesso non validi. Attendi qualche istante e riprova.",

30
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json

@ -1,6 +1,8 @@
{
"culture": "it",
"texts": {
"AddSubMenuItem": "Aggiungi voce sottomenu",
"AreYouSure": "Sei Sicuro?",
"BlogDeletionConfirmationMessage": "Il blog '{0}' verrà eliminato. Sei sicuro?",
"BlogFeatureNotAvailable": "Questa funzione non è ora disponibile. Abilita con 'GlobalFeatureManager' per usarlo.",
"BlogId": "Blog",
@ -36,29 +38,42 @@
"Delete": "Elimina",
"Detail": "Dettaglio",
"Details": "Dettagli",
"DisplayName": "Nome Visualizzato",
"DoYouPreferAdditionalEmails": "Preferisci email aggiuntive?",
"Edit": "modifica",
"Edit": "Modifica",
"EndDate": "Data di fine",
"EntityId": "Id Entità",
"EntityType": "Tipo di Entità",
"ExportCSV": "Esporta CSV",
"Features": "Funzionalità",
"GenericDeletionConfirmationMessage": "Sei sicuro di eliminare '{0}'?",
"IsActive": "Attivo",
"LastModification": "Ultima modifica",
"LastModificationTime": "Ora ultima modifica",
"LoginToAddComment": "Accedi per aggiungere un commento",
"LoginToRate": "Accedi per votare",
"LoginToReact": "Accedi per reagire",
"LoginToReply": "Accedi per rispondere",
"MainMenu": "Menu Principale",
"MakeMainMenu": "Rendi Menu Principale",
"Menu:CMS": "CMS",
"Menus": "I menu",
"MenuDeletionConfirmationMessage": "Il menu '{0}' sarà cancellato. Sei sicuro?",
"MenuItemDeletionConfirmationMessage": "Sei sicuro di cancellare questa voce dal menu?",
"MenuItemMoveConfirmMessage": "Sei sicuro di voler spostare '{0}' sotto '{1}'?",
"MenuItems": "Voci Menu",
"Message": "Messaggio",
"MessageDeletionConfirmationMessage": "Questo commento verrà completamente cancellato.",
"NewBlog": "Nuovo blog",
"NewBlogPost": "Nuovo post sul blog",
"NewMenu": "Nuovo Menu",
"NewMenuItem": "Nuova Voce Menu Principale",
"NewPage": "Nuova pagina",
"NewTag": "Nuovo tag",
"NoMenuItems": "Non c'è ancora nessuna voce di menu!",
"OK": "OK",
"PageDeletionConfirmationMessage": "Sei sicuro di cancellare questa pagina?",
"PageId": "Pagina",
"Pages": "Pagine",
"PageSlugInformation": "Lo slug viene utilizzato sull'URL. Il tuo URL sarà '/pages/{{slug}}'.",
"Permission:BlogManagement": "Gestione del blog",
@ -80,6 +95,18 @@
"Permission:MediaDescriptorManagement": "Gestione dei media",
"Permission:MediaDescriptorManagement:Create": "Crea",
"Permission:MediaDescriptorManagement:Delete": "Elimina",
"Permission:MenuItemManagement": "Gestione Voci Menu",
"Permission:MenuItemManagement.Create": "Crea",
"Permission:MenuItemManagement.Delete": "Cancella",
"Permission:MenuItemManagement.Update": "Aggiorna",
"Permission:MenuManagement": "Gestione Menu",
"Permission:MenuManagement.Create": "Crea",
"Permission:MenuManagement.Delete": "Cancella",
"Permission:MenuManagement.Update": "Aggiorna",
"Permission:Menus": "Gestione Menu",
"Permission:Menus.Create": "Crea",
"Permission:Menus.Delete": "Cancella",
"Permission:Menus.Update": "Aggiorna",
"Permission:PageManagement": "Gestione della pagina",
"Permission:PageManagement:Create": "Crea",
"Permission:PageManagement:Delete": "Elimina",
@ -123,6 +150,7 @@
"Update": "Aggiorna",
"UpdatePreferenceSuccessMessage": "Le tue preferenze sono state salvate.",
"UpdateYourEmailPreferences": "Aggiorna le tue preferenze email",
"UnMakeMainMenu": "Annulla Menu Principale",
"UploadFailedMessage": "Caricamento fallito.",
"UserId": "ID utente",
"Username": "Nome utente",

2
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/it.json

@ -28,7 +28,7 @@
"DisplayName:MinimumVersion": "Versione minima",
"DisplayName:MainWebsiteUrl": "URL del sito web principale",
"DisplayName:LatestVersionBranchName": "Nome del ramo della versione più recente",
"DisplayName:GitHubRootUrl": "URL radice di GitHub",
"DisplayName:GitHubRootUrl": "URL principale di GitHub",
"DisplayName:GitHubAccessToken": "Token di accesso GitHub",
"DisplayName:GitHubUserAgent": "GitHub user agent",
"DisplayName:GithubVersionProviderSource": "GitHub version provider source",

1
modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/it.json

@ -9,6 +9,7 @@
"InvalidUserNameOrPassword": "Username o password non validi!",
"LoginIsNotAllowed": "Non sei autorizzato ad accedere! Devi confermare la tua email/numero di telefono.",
"InvalidUsername": "Username o password non validi!",
"InvalidAuthenticatorCode": "Codice autenticatore non valido!",
"TheTargetUserIsNotLinkedToYou": "L'utente di destinazione non è collegato a te!"
}
}

7
modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/it.json

@ -9,11 +9,14 @@
"SmtpHost": "Host",
"SmtpPort": "Porta",
"SmtpUserName": "Nome utente",
"SmtpPassword": "Parola d'ordine",
"SmtpPassword": "Password",
"SmtpDomain": "Dominio",
"SmtpEnableSsl": "Abilita SSL",
"SmtpUseDefaultCredentials": "Usa credenziali predefinite",
"DefaultFromAddress": "Indirizzo mittente predefinito",
"DefaultFromDisplayName": "Nome mittente visualizzato predefinito"
"DefaultFromDisplayName": "Nome mittente visualizzato predefinito",
"Feature:SettingManagementGroup": "Gestione Impostazioni",
"Feature:SettingManagementEnable": "Abilita gestione impostazioni",
"Feature:SettingManagementEnableDescription": "Abilita sistema gestione impostazioni nell'applicazione"
}
}

2
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json

@ -11,7 +11,7 @@
"DisplayName:DefaultConnectionString": "Stringa di connessione predefinita",
"DisplayName:UseSharedDatabase": "Usa il database condiviso",
"ManageHostFeatures": "Gestisci le funzionalità dell'host",
"Permission:TenantManagement": "Gestione inquilini",
"Permission:TenantManagement": "Gestione Tenant",
"Permission:Create": "Crea",
"Permission:Edit": "Modifica",
"Permission:Delete": "Elimina",

2
modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/it.json

@ -8,7 +8,7 @@
"VirtualFileName": "Nome del file virtuale",
"FileContent": "Contenuto del file",
"Size": "Dimensione",
"BackToRoot": "Torna alla radice",
"BackToRoot": "Torna al principale",
"EmptyFileInfoList": "Non ci sono file virtuali"
}
}

22
npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts

@ -17,14 +17,12 @@ export class AuthWrapperService {
tenantBoxKey = 'Account.TenantBoxComponent';
route: ActivatedRoute;
private _tenantBoxVisible = true;
private setTenantBoxVisibility = () => {
this._tenantBoxVisible = this.route.snapshot.firstChild.data.tenantBoxVisible ?? true;
};
get isTenantBoxVisibleForCurrentRoute() {
return this.getMostInnerChild().data.tenantBoxVisible ?? true;
}
get isTenantBoxVisible() {
return this._tenantBoxVisible && this.multiTenancy.isTenantBoxVisible;
return this.isTenantBoxVisibleForCurrentRoute && this.multiTenancy.isTenantBoxVisible;
}
constructor(
@ -33,6 +31,16 @@ export class AuthWrapperService {
injector: Injector,
) {
this.route = injector.get(ActivatedRoute);
this.setTenantBoxVisibility();
}
private getMostInnerChild() {
let child = this.route.snapshot;
let depth = 0;
const depthLimit = 10;
while (child.firstChild && depth < depthLimit) {
child = child.firstChild;
depth++;
}
return child;
}
}

5
npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts

@ -18,7 +18,6 @@ export class ResetPasswordComponent implements OnInit {
inProgress = false;
isPasswordReset = false;
tenantId = '';
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
@ -35,8 +34,7 @@ export class ResetPasswordComponent implements OnInit {
) {}
ngOnInit(): void {
this.route.queryParams.subscribe(({ userId, resetToken, tenantId }) => {
this.tenantId = tenantId;
this.route.queryParams.subscribe(({ userId, resetToken }) => {
if (!userId || !resetToken) this.router.navigateByUrl('/account/login');
this.form = this.fb.group(
@ -63,7 +61,6 @@ export class ResetPasswordComponent implements OnInit {
userId: this.form.get('userId').value,
resetToken: this.form.get('resetToken').value,
password: this.form.get('password').value,
tenantId: this.tenantId || undefined, // if this.tenantId is empty, we should not send it at all
})
.pipe(finalize(() => (this.inProgress = false)))
.subscribe(() => {

1
npm/ng-packs/packages/account/src/lib/proxy/account/models.ts

@ -9,7 +9,6 @@ export interface RegisterDto extends ExtensibleObject {
export interface ResetPasswordDto {
userId?: string;
tenantId?: string;
resetToken: string;
password: string;
}

1219
npm/ng-packs/packages/account/src/lib/proxy/generate-proxy.json

File diff suppressed because it is too large

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

@ -33,6 +33,7 @@ import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token';
import { noop } from './utils/common-utils';
import './utils/date-extensions';
import { getInitialData, localeInitializer } from './utils/initial-utils';
import { TENANT_KEY } from './tokens/tenant-key.token';
export function storageFactory(): OAuthStorage {
return oAuthStorage;
@ -178,6 +179,7 @@ export class CoreModule {
useFactory: noop,
},
{ provide: OAuthStorage, useFactory: storageFactory },
{ provide: TENANT_KEY, useValue: options.tenantKey || '__tenant' },
],
};
}

8
npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts

@ -1,9 +1,10 @@
import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Injectable, Inject } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { finalize } from 'rxjs/operators';
import { SessionStateService } from '../services/session-state.service';
import { HttpWaitService } from '../services/http-wait.service';
import { TENANT_KEY } from '../tokens/tenant-key.token';
@Injectable({
providedIn: 'root',
@ -13,6 +14,7 @@ export class ApiInterceptor implements HttpInterceptor {
private oAuthService: OAuthService,
private sessionState: SessionStateService,
private httpWaitService: HttpWaitService,
@Inject(TENANT_KEY) private tenantKey: string,
) {}
intercept(request: HttpRequest<any>, next: HttpHandler) {
@ -40,8 +42,8 @@ export class ApiInterceptor implements HttpInterceptor {
}
const tenant = this.sessionState.getTenant();
if (!existingHeaders?.has('__tenant') && tenant?.id) {
headers['__tenant'] = tenant.id;
if (!existingHeaders?.has(this.tenantKey) && tenant?.id) {
headers[this.tenantKey] = tenant.id;
}
return headers;

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

@ -10,6 +10,7 @@ export namespace ABP {
registerLocaleFn: (locale: string) => Promise<any>;
skipGetAppConfiguration?: boolean;
sendNullsAsQueryParam?: boolean;
tenantKey?: string;
}
export interface HasPolicy {
@ -74,7 +75,6 @@ export namespace ABP {
[key: string]: T;
}
export type ExtractFromOutput<
T extends EventEmitter<any> | Subject<any>
> = T extends EventEmitter<infer X> ? X : T extends Subject<infer Y> ? Y : never;
export type ExtractFromOutput<T extends EventEmitter<any> | Subject<any>> =
T extends EventEmitter<infer X> ? X : T extends Subject<infer Y> ? Y : never;
}

3
npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts

@ -8,11 +8,12 @@ import type { FindTenantResultDto } from '../../../volo/abp/asp-net-core/mvc/mul
export class AbpTenantService {
apiName = 'abp';
findTenantById = (id: string) =>
findTenantById = (id: string, headers: Record<string, string>) =>
this.restService.request<any, FindTenantResultDto>(
{
method: 'GET',
url: `/api/abp/multi-tenancy/tenants/by-id/${id}`,
headers,
},
{ apiName: this.apiName },
);

9
npm/ng-packs/packages/core/src/lib/services/config-state.service.ts

@ -1,8 +1,9 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { map, tap } from 'rxjs/operators';
import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models';
import { InternalStore } from '../utils/internal-store-utils';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
@Injectable({
providedIn: 'root',
@ -14,10 +15,16 @@ export class ConfigStateService {
return this.store.sliceUpdate;
}
constructor(private abpConfigService: AbpApplicationConfigurationService) {}
setState(state: ApplicationConfigurationDto) {
this.store.set(state);
}
refreshAppState() {
return this.abpConfigService.get().pipe(tap(res => this.setState(res)));
}
getOne$(key: string) {
return this.store.sliceState(state => state[key]);
}

44
npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts

@ -1,31 +1,37 @@
import { Injectable } from '@angular/core';
import { Injectable, Inject } from '@angular/core';
import { switchMap, map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ABP } from '../models/common';
import {
CurrentTenantDto,
FindTenantResultDto,
CurrentTenantDto,
} from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models';
import { RestService } from './rest.service';
import { AbpTenantService } from '../proxy/pages/abp/multi-tenancy';
import { ConfigStateService } from './config-state.service';
import { SessionStateService } from './session-state.service';
import { TENANT_KEY } from '../tokens/tenant-key.token';
@Injectable({ providedIn: 'root' })
export class MultiTenancyService {
private _domainTenant: CurrentTenantDto = null;
set domainTenant(value: CurrentTenantDto) {
this._domainTenant = value;
this.sessionState.setTenant(value);
}
get domainTenant() {
return this._domainTenant;
}
domainTenant: CurrentTenantDto = null;
isTenantBoxVisible = true;
apiName = 'abp';
constructor(private restService: RestService, private sessionState: SessionStateService) {}
private setTenantToState = (tenant: FindTenantResultDto) => {
this.sessionState.setTenant({ id: tenant.tenantId, name: tenant.name, isAvailable: true });
return this.configStateService.refreshAppState().pipe(map(_ => tenant));
};
constructor(
private restService: RestService,
private sessionState: SessionStateService,
private tenantService: AbpTenantService,
private configStateService: ConfigStateService,
@Inject(TENANT_KEY) public tenantKey: string,
) {}
/**
* @deprecated Use AbpTenantService.findTenantByName method instead. To be deleted in v5.0.
@ -50,4 +56,16 @@ export class MultiTenancyService {
{ apiName: this.apiName },
);
}
setTenantByName(tenantName: string) {
return this.tenantService
.findTenantByName(tenantName, { [this.tenantKey]: '' })
.pipe(switchMap(this.setTenantToState));
}
setTenantById(tenantId: string) {
return this.tenantService
.findTenantById(tenantId, { [this.tenantKey]: '' })
.pipe(switchMap(this.setTenantToState));
}
}

5
npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts

@ -19,6 +19,7 @@ import { EnvironmentService } from '../services/environment.service';
import { SessionStateService } from '../services/session-state.service';
import { removeRememberMe, setRememberMe } from '../utils/auth-utils';
import { noop } from '../utils/common-utils';
import { TENANT_KEY } from '../tokens/tenant-key.token';
export const oAuthStorage = localStorage;
@ -32,6 +33,7 @@ export abstract class AuthFlowStrategy {
protected oAuthConfig: AuthConfig;
protected sessionState: SessionStateService;
protected appConfigService: AbpApplicationConfigurationService;
protected tenantKey: string;
abstract checkIfInternalAuth(queryParams?: Params): boolean;
abstract navigateToLogin(queryParams?: Params): void;
@ -48,6 +50,7 @@ export abstract class AuthFlowStrategy {
this.appConfigService = injector.get(AbpApplicationConfigurationService);
this.sessionState = injector.get(SessionStateService);
this.oAuthConfig = this.environment.getEnvironment().oAuthConfig;
this.tenantKey = injector.get(TENANT_KEY);
this.listenToOauthErrors();
}
@ -176,7 +179,7 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy {
this.oAuthService.fetchTokenUsingPasswordFlow(
params.username,
params.password,
new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }),
new HttpHeaders({ ...(tenant && tenant.id && { [this.tenantKey]: tenant.id }) }),
),
).pipe(this.pipeToLogin(params));
}

6
npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts

@ -5,6 +5,7 @@ import { OAuthService } from 'angular-oauth2-oidc';
import { Subject, timer } from 'rxjs';
import { ApiInterceptor } from '../interceptors';
import { HttpWaitService, SessionStateService } from '../services';
import { TENANT_KEY } from '../tokens/tenant-key.token';
describe('ApiInterceptor', () => {
let spectator: SpectatorService<ApiInterceptor>;
@ -13,9 +14,12 @@ describe('ApiInterceptor', () => {
let sessionState: SpyObject<SessionStateService>;
let httpWaitService: SpyObject<HttpWaitService>;
const testTenantKey = 'TEST_TENANT_KEY';
const createService = createServiceFactory({
service: ApiInterceptor,
mocks: [OAuthService, SessionStateService],
providers: [{ provide: TENANT_KEY, useValue: testTenantKey }],
});
beforeEach(() => {
@ -38,7 +42,7 @@ describe('ApiInterceptor', () => {
handle: (req: HttpRequest<any>) => {
expect(req.headers.get('Authorization')).toEqual('Bearer ey892mkwa8^2jk');
expect(req.headers.get('Accept-Language')).toEqual('tr');
expect(req.headers.get('__tenant')).toEqual('Volosoft');
expect(req.headers.get(testTenantKey)).toEqual('Volosoft');
done();
return handleRes$;
},

6
npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts

@ -5,8 +5,9 @@ import {
CurrentUserDto,
} from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models';
import { ConfigStateService } from '../services';
import { CoreTestingModule } from '@abp/ng.core/testing';
export const CONFIG_STATE_DATA = ({
export const CONFIG_STATE_DATA = {
environment: {
production: false,
application: {
@ -97,7 +98,7 @@ export const CONFIG_STATE_DATA = ({
},
},
registerLocaleFn: () => Promise.resolve(),
} as any) as ApplicationConfigurationDto;
} as any as ApplicationConfigurationDto;
describe('ConfigState', () => {
let spectator: SpectatorService<ConfigStateService>;
@ -105,6 +106,7 @@ describe('ConfigState', () => {
const createService = createServiceFactory({
service: ConfigStateService,
imports: [CoreTestingModule.withConfig()],
});
beforeEach(() => {

2
npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts

@ -15,7 +15,7 @@ describe('Date Utils', () => {
let config: ConfigStateService;
beforeEach(() => {
config = new ConfigStateService();
config = new ConfigStateService(null);
});
describe('#getShortDateFormat', () => {

32
npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts

@ -1,14 +1,15 @@
import { Component } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import clone from 'just-clone';
import { BehaviorSubject } from 'rxjs';
import { AbpTenantService } from '../proxy/pages/abp/multi-tenancy/abp-tenant.service';
import { of } from 'rxjs';
import {
CurrentTenantDto,
FindTenantResultDto,
} from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models';
import { EnvironmentService, MultiTenancyService } from '../services';
import { parseTenantFromUrl } from '../utils';
import { TENANT_KEY } from '../tokens';
const environment = {
production: false,
@ -45,6 +46,8 @@ const setHref = url => {
});
};
const testTenantKey = 'TEST_TENANT_KEY';
@Component({
selector: 'abp-dummy',
template: '',
@ -56,7 +59,7 @@ describe('MultiTenancyUtils', () => {
const createComponent = createComponentFactory({
component: DummyComponent,
mocks: [EnvironmentService, MultiTenancyService],
providers: [{ provide: AbpTenantService, useValue: { findTenantByName: () => {} } }],
providers: [{ provide: TENANT_KEY, useValue: testTenantKey }],
});
beforeEach(() => (spectator = createComponent()));
@ -65,27 +68,30 @@ describe('MultiTenancyUtils', () => {
test('should get the tenancyName, set replaced environment and call the findTenantByName method of AbpTenantService', async () => {
const environmentService = spectator.inject(EnvironmentService);
const multiTenancyService = spectator.inject(MultiTenancyService);
const abpTenantService = spectator.inject(AbpTenantService);
const findTenantByNameSpy = jest.spyOn(abpTenantService, 'findTenantByName');
const setTenantByName = jest.spyOn(multiTenancyService, 'setTenantByName');
const getEnvironmentSpy = jest.spyOn(environmentService, 'getEnvironment');
const setStateSpy = jest.spyOn(environmentService, 'setState');
getEnvironmentSpy.mockReturnValue(clone(environment));
const testTenant: FindTenantResultDto = {
name: 'abp',
tenantId: '1',
isActive: true,
success: true,
};
setHref('https://abp.volosoft.com/');
findTenantByNameSpy.mockReturnValue(
new BehaviorSubject({ name: 'abp', tenantId: '1', success: true } as FindTenantResultDto),
);
setTenantByName.mockReturnValue(of(testTenant));
const mockInjector = {
get: arg => {
if (arg === EnvironmentService) return environmentService;
if (arg === AbpTenantService) return abpTenantService;
if (arg === MultiTenancyService) return multiTenancyService;
},
};
parseTenantFromUrl(mockInjector);
await parseTenantFromUrl(mockInjector);
const replacedEnv = {
...environment,
@ -106,10 +112,10 @@ describe('MultiTenancyUtils', () => {
};
expect(setStateSpy).toHaveBeenCalledWith(replacedEnv);
expect(findTenantByNameSpy).toHaveBeenCalledWith('abp', { __tenant: '' });
expect(multiTenancyService.domainTenant).toEqual({
id: '1',
name: 'abp',
id: testTenant.tenantId,
name: testTenant.name,
isAvailable: true,
} as CurrentTenantDto);
});
});

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

@ -3,3 +3,4 @@ export * from './lodaer-delay.token';
export * from './manage-profile.token';
export * from './options.token';
export * from './app-config.token';
export * from './tenant-key.token';

3
npm/ng-packs/packages/core/src/lib/tokens/tenant-key.token.ts

@ -0,0 +1,3 @@
import { InjectionToken } from '@angular/core';
export const TENANT_KEY = new InjectionToken<string>('TENANT_KEY');

60
npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts

@ -1,10 +1,10 @@
import { Injector } from '@angular/core';
import clone from 'just-clone';
import { of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import { Environment } from '../models/environment';
import { AbpTenantService } from '../proxy/pages/abp/multi-tenancy/abp-tenant.service';
import { CurrentTenantDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models';
import { FindTenantResultDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models';
import { EnvironmentService } from '../services/environment.service';
import { MultiTenancyService } from '../services/multi-tenancy.service';
import { createTokenParser } from './string-utils';
@ -19,39 +19,61 @@ function getCurrentTenancyName(appBaseUrl: string): string {
return parseTokens(window.location.href)[token]?.[0];
}
function getCurrentTenancyNameFromUrl(tenantKey: string) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(tenantKey);
}
export async function parseTenantFromUrl(injector: Injector) {
const environmentService = injector.get(EnvironmentService);
const multiTenancyService = injector.get(MultiTenancyService);
const abpTenantService = injector.get(AbpTenantService);
const baseUrl = environmentService.getEnvironment()?.application?.baseUrl || '';
const tenancyName = getCurrentTenancyName(baseUrl);
if (tenancyName) {
const hideTenantBox = () => {
multiTenancyService.isTenantBoxVisible = false;
setEnvironment(injector, tenancyName);
return of(null)
.pipe(
switchMap(() => abpTenantService.findTenantByName(tenancyName, { __tenant: '' })),
tap(res => {
multiTenancyService.domainTenant = res.success
? ({ id: res.tenantId, name: res.name } as CurrentTenantDto)
: null;
}),
)
};
const setDomainTenant = (tenant: FindTenantResultDto) => {
multiTenancyService.domainTenant = {
id: tenant.tenantId,
name: tenant.name,
isAvailable: true,
};
};
const setEnvironmentWithDomainTenant = (tenant: FindTenantResultDto) => {
hideTenantBox();
setDomainTenant(tenant);
replaceTenantNameWithinEnvironment(injector, tenant.name);
};
if (tenancyName) {
return multiTenancyService
.setTenantByName(tenancyName)
.pipe(tap(setEnvironmentWithDomainTenant))
.toPromise();
} else {
/**
* If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests.
*/
setEnvironment(injector, '', tenancyPlaceholder + '.');
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.');
const tenantIdFromQueryParams = getCurrentTenancyNameFromUrl(multiTenancyService.tenantKey);
if (tenantIdFromQueryParams) {
return multiTenancyService.setTenantById(tenantIdFromQueryParams).toPromise();
}
}
return Promise.resolve();
}
function setEnvironment(injector: Injector, tenancyName: string, placeholder = tenancyPlaceholder) {
function replaceTenantNameWithinEnvironment(
injector: Injector,
tenancyName: string,
placeholder = tenancyPlaceholder,
) {
const environmentService = injector.get(EnvironmentService);
const environment = clone(environmentService.getEnvironment()) as Environment;

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Tiered
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/Program.cs

@ -32,10 +32,7 @@ namespace MyCompanyName.MyProjectName.DbMigrator
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureLogging((context, logging) => logging.ClearProviders())
.ConfigureServices((hostContext, services) =>
{

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName.Web
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName.Web
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/Program.cs

@ -14,10 +14,7 @@ namespace MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ConsoleTestAppHostedService>();

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host
public static IHostBuilder CreateHostBuilder(string[] args) =>
Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/Program.cs

@ -45,10 +45,7 @@ namespace MyCompanyName.MyProjectName
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Program.cs

@ -41,10 +41,7 @@ namespace MyCompanyName.MyProjectName
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();

5
templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/Program.cs

@ -14,10 +14,7 @@ namespace MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddJsonFile("appsettings.secrets.json", optional: true);
})
.AddAppSettingsSecretsJson()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ConsoleTestAppHostedService>();

Loading…
Cancel
Save