Thank you Linch, what problem you been solving by rewriting angular domain resolver? As i understand it supposed to work out of the box without rewriting it?
Hi, kirotech, I just updated the code of multi-tenancy-utils.ts
, because it is work for datdv1 https://support.abp.io/QA/Questions/5650#answer-3a0d82ab-0969-e910-4824-cab59dc00e91
Maybe you can try this to solve your problem. Hope it is work for you.
import {
ABP,
CORE_OPTIONS,
Environment,
EnvironmentService,
createTokenParser,
getRemoteEnv,
} from '@abp/ng.core';
import { Injector } from '@angular/core';
import clone from 'just-clone';
const tenancyPlaceholder = '{0}';
function getCurrentTenancyName(appBaseUrl: string): string {
if (appBaseUrl.charAt(appBaseUrl.length - 1) !== '/') appBaseUrl += '/';
const parseTokens = createTokenParser(appBaseUrl);
const token = tenancyPlaceholder.replace(/[}{]/g, '');
return parseTokens(window.location.href)[token]?.[0];
}
export function cleanPlaceholderFromHostUrl(injector: Injector) {
const fn = async () => {
const environmentService = injector.get(EnvironmentService);
const options = injector.get(CORE_OPTIONS) as ABP.Root;
environmentService.setState(options.environment as Environment);
await getRemoteEnv(injector, options.environment);
const baseUrl =
environmentService.getEnvironment()['appBaseUrl'] ||
environmentService.getEnvironment().application?.baseUrl ||
'';
const tenancyName = getCurrentTenancyName(baseUrl);
if (!tenancyName) {
/**
* If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests.
*/
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.');
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder);
}
return Promise.resolve();
};
return fn;
}
function replaceTenantNameWithinEnvironment(
injector: Injector,
tenancyName: string,
placeholder = tenancyPlaceholder
) {
const environmentService = injector.get(EnvironmentService);
const environment = clone(environmentService.getEnvironment()) as Environment;
if (environment.application.baseUrl) {
environment.application.baseUrl = environment.application.baseUrl.replace(
placeholder,
tenancyName
);
}
if (environment.oAuthConfig?.redirectUri) {
environment.oAuthConfig.redirectUri = environment.oAuthConfig.redirectUri.replace(
placeholder,
tenancyName
);
}
if (!environment.oAuthConfig) {
environment.oAuthConfig = {};
}
environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(
placeholder,
tenancyName
);
environment.oAuthConfig.clientId = (environment.oAuthConfig.clientId || '').replace(
placeholder,
tenancyName
);
Object.keys(environment.apis).forEach(api => {
Object.keys(environment.apis[api]).forEach(key => {
environment.apis[api][key] = (environment.apis[api][key] || '').replace(
placeholder,
tenancyName
);
});
});
return environmentService.setState(environment);
}
Hello Anjali_Musmade, Thanks a lot, this works.
Hi, ABP team:
Could you kindly send me the latest source code of @volo/abp.commercial.ng.ui
package?
Hello alper, article helped to solve redirect_uri issue, but now I face angular problem where if you go to https://testtenant.ng.abp.net:4200 angular doesn't load because if he thinks that user is not authenticated no matter authserver returned valid token and issuer matching angular issuer environment config.
I do have all this abp commercial microservices tenant subdomain mapping config in separate test repository which im ok to share with you if needed.
I still need help with finishing this abp commercial microservices feature work.
Hi, kirotech, if you have problem at the angular side, you can try the following steps, maybe it will work:
multi-tenancy-utils.ts
file, this will be override the default implementation of ABP framework:import {
ABP,
CORE_OPTIONS,
Environment,
EnvironmentService,
createTokenParser,
getRemoteEnv,
} from '@abp/ng.core';
import { Injector } from '@angular/core';
import clone from 'just-clone';
const tenancyPlaceholder = '{0}';
function getCurrentTenancyName(appBaseUrl: string): string {
if (appBaseUrl.charAt(appBaseUrl.length - 1) !== '/') appBaseUrl += '/';
const parseTokens = createTokenParser(appBaseUrl);
const token = tenancyPlaceholder.replace(/[}{]/g, '');
return parseTokens(window.location.href)[token]?.[0];
}
export function cleanPlaceholderFromHostUrl(injector: Injector) {
const fn = async () => {
const environmentService = injector.get(EnvironmentService);
const options = injector.get(CORE_OPTIONS) as ABP.Root;
environmentService.setState(options.environment as Environment);
await getRemoteEnv(injector, options.environment);
const baseUrl =
environmentService.getEnvironment()['appBaseUrl'] ||
environmentService.getEnvironment().application?.baseUrl ||
'';
const tenancyName = getCurrentTenancyName(baseUrl);
if (!tenancyName) {
/**
* If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests.
*/
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.');
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder);
}
return Promise.resolve();
};
return fn;
}
function replaceTenantNameWithinEnvironment(
injector: Injector,
tenancyName: string,
placeholder = tenancyPlaceholder
) {
const environmentService = injector.get(EnvironmentService);
const environment = clone(environmentService.getEnvironment()) as Environment;
if (environment.application.baseUrl) {
environment.application.baseUrl = environment.application.baseUrl.replace(
placeholder,
tenancyName
);
}
if (environment.oAuthConfig?.redirectUri) {
environment.oAuthConfig.redirectUri = environment.oAuthConfig.redirectUri.replace(
placeholder,
tenancyName
);
}
if (!environment.oAuthConfig) {
environment.oAuthConfig = {};
}
environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(
placeholder,
tenancyName
);
environment.oAuthConfig.clientId = (environment.oAuthConfig.clientId || '').replace(
placeholder,
tenancyName
);
Object.keys(environment.apis).forEach(api => {
Object.keys(environment.apis[api]).forEach(key => {
environment.apis[api][key] = (environment.apis[api][key] || '').replace(
placeholder,
tenancyName
);
});
});
return environmentService.setState(environment);
}
domain-resolver.module.ts
file:import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
import { cleanPlaceholderFromHostUrl } from './multi-tenancy-utils';
@NgModule({
imports: [CommonModule, HttpClientModule],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
deps: [Injector],
useFactory: cleanPlaceholderFromHostUrl,
},
],
})
export class DomainResolverModule {}
DomainResolverModule
in your AppModule
:import { CoreModule } from '@abp/ng.core';
import { GdprConfigModule } from '@volo/abp.ng.gdpr/config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management/config';
import { HTTP_ERROR_HANDLER, ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CommercialUiConfigModule } from '@volo/abp.commercial.ng.ui/config';
import { AccountAdminConfigModule } from '@volo/abp.ng.account/admin/config';
import { AccountPublicConfigModule } from '@volo/abp.ng.account/public/config';
import { AuditLoggingConfigModule } from '@volo/abp.ng.audit-logging/config';
import { IdentityConfigModule } from '@volo/abp.ng.identity/config';
import { LanguageManagementConfigModule } from '@volo/abp.ng.language-management/config';
import { registerLocale } from '@volo/abp.ng.language-management/locale';
import { SaasConfigModule } from '@volo/abp.ng.saas/config';
import { TextTemplateManagementConfigModule } from '@volo/abp.ng.text-template-management/config';
import { HttpErrorComponent, ThemeLeptonModule } from '@volo/abp.ng.theme.lepton';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider';
import { OpeniddictproConfigModule } from '@volo/abp.ng.openiddictpro/config';
import { FeatureManagementModule } from '@abp/ng.feature-management';
import { AbpOAuthModule } from '@abp/ng.oauth';
import { ServiceWorkerModule } from '@angular/service-worker';
import { APP_VALIDATION_PROVIDER } from './validation/app.validation.provider';
import { DomainResolverModule } from './customization';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
DomainResolverModule,
CoreModule.forRoot({
environment,
registerLocaleFn: registerLocale(),
}),
AbpOAuthModule.forRoot(),
ThemeSharedModule.forRoot({
httpErrorConfig: {
errorScreen: {
component: HttpErrorComponent,
forWhichErrors: [401, 403, 404, 500],
hideCloseIcon: true,
},
},
}),
// Other import statements
],
providers: [
APP_ROUTE_PROVIDER,
APP_VALIDATION_PROVIDER
],
bootstrap: [AppComponent],
})
export class AppModule {}
Hi, I came across this problem months ago.
After some research, I fixed it with the following steps:
In AuthServer Module.ts :
public override void PreConfigureServices(ServiceConfigurationContext context)
{
// other codes.
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{
options.AddDevelopmentEncryptionAndSigningCertificate = false;
});
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration));
if (!hostingEnvironment.IsProduction())
{
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
}
});
PreConfigure<AbpOpenIddictWildcardDomainOptions>(options =>
{
options.EnableWildcardDomainSupport = true;
options.WildcardDomainsFormat.Add(configuration["App:AngularWildcardUrl"]);
});
PreConfigure<OpenIddictServerOptions>(options =>
{
options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
options.TokenValidationParameters.ValidIssuers = new[]
{
configuration["AuthServer:Authority"].EnsureEndsWith('/'),
configuration["AuthServer:WildcardAuthority"].EnsureEndsWith('/')
};
});
}
Note:
TokenWildcardIssuerValidator.IssuerValidator
is from package<PackageReference Include="Owl.TokenWildcardIssuerValidator" Version="1.0.0" />
In HttpApi.Host module ts:
public override void ConfigureServices(ServiceConfigurationContext context)
{
// other codes
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.Audience = "PayloadLimitPfmNext";
if (hostingEnvironment.IsProduction())
{
options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
options.TokenValidationParameters.ValidIssuers = new[]
{
configuration["AuthServer:Authority"].EnsureEndsWith('/'),
configuration["AuthServer:WildcardAuthority"].EnsureEndsWith('/')
};
}
});
}
Note:
TokenWildcardIssuerValidator.IssuerValidator
is from package<PackageReference Include="Owl.TokenWildcardIssuerValidator" Version="1.0.0" />
In Angular:
multi-tenancy-utils.ts
file, this will be override the default implementation of ABP framework:import {
ABP,
CORE_OPTIONS,
Environment,
EnvironmentService,
createTokenParser,
getRemoteEnv,
} from '@abp/ng.core';
import { Injector } from '@angular/core';
import clone from 'just-clone';
const tenancyPlaceholder = '{0}';
function getCurrentTenancyName(appBaseUrl: string): string {
if (appBaseUrl.charAt(appBaseUrl.length - 1) !== '/') appBaseUrl += '/';
const parseTokens = createTokenParser(appBaseUrl);
const token = tenancyPlaceholder.replace(/[}{]/g, '');
return parseTokens(window.location.href)[token]?.[0];
}
export function cleanPlaceholderFromHostUrl(injector: Injector) {
const fn = async () => {
const environmentService = injector.get(EnvironmentService);
const options = injector.get(CORE_OPTIONS) as ABP.Root;
environmentService.setState(options.environment as Environment);
await getRemoteEnv(injector, options.environment);
const baseUrl =
environmentService.getEnvironment()['appBaseUrl'] ||
environmentService.getEnvironment().application?.baseUrl ||
'';
const tenancyName = getCurrentTenancyName(baseUrl);
if (!tenancyName) {
/**
* If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests.
*/
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.');
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder);
}
return Promise.resolve();
};
return fn;
}
function replaceTenantNameWithinEnvironment(
injector: Injector,
tenancyName: string,
placeholder = tenancyPlaceholder
) {
const environmentService = injector.get(EnvironmentService);
const environment = clone(environmentService.getEnvironment()) as Environment;
if (environment.application.baseUrl) {
environment.application.baseUrl = environment.application.baseUrl.replace(
placeholder,
tenancyName
);
}
if (environment.oAuthConfig?.redirectUri) {
environment.oAuthConfig.redirectUri = environment.oAuthConfig.redirectUri.replace(
placeholder,
tenancyName
);
}
if (!environment.oAuthConfig) {
environment.oAuthConfig = {};
}
environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(
placeholder,
tenancyName
);
environment.oAuthConfig.clientId = (environment.oAuthConfig.clientId || '').replace(
placeholder,
tenancyName
);
Object.keys(environment.apis).forEach(api => {
Object.keys(environment.apis[api]).forEach(key => {
environment.apis[api][key] = (environment.apis[api][key] || '').replace(
placeholder,
tenancyName
);
});
});
return environmentService.setState(environment);
}
domain-resolver.module.ts
file:import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
import { cleanPlaceholderFromHostUrl } from './multi-tenancy-utils';
@NgModule({
imports: [CommonModule, HttpClientModule],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
deps: [Injector],
useFactory: cleanPlaceholderFromHostUrl,
},
],
})
export class DomainResolverModule {}
DomainResolverModule
in your AppModule
:import { CoreModule } from '@abp/ng.core';
import { GdprConfigModule } from '@volo/abp.ng.gdpr/config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management/config';
import { HTTP_ERROR_HANDLER, ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CommercialUiConfigModule } from '@volo/abp.commercial.ng.ui/config';
import { AccountAdminConfigModule } from '@volo/abp.ng.account/admin/config';
import { AccountPublicConfigModule } from '@volo/abp.ng.account/public/config';
import { AuditLoggingConfigModule } from '@volo/abp.ng.audit-logging/config';
import { IdentityConfigModule } from '@volo/abp.ng.identity/config';
import { LanguageManagementConfigModule } from '@volo/abp.ng.language-management/config';
import { registerLocale } from '@volo/abp.ng.language-management/locale';
import { SaasConfigModule } from '@volo/abp.ng.saas/config';
import { TextTemplateManagementConfigModule } from '@volo/abp.ng.text-template-management/config';
import { HttpErrorComponent, ThemeLeptonModule } from '@volo/abp.ng.theme.lepton';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider';
import { OpeniddictproConfigModule } from '@volo/abp.ng.openiddictpro/config';
import { FeatureManagementModule } from '@abp/ng.feature-management';
import { AbpOAuthModule } from '@abp/ng.oauth';
import { ServiceWorkerModule } from '@angular/service-worker';
import { APP_VALIDATION_PROVIDER } from './validation/app.validation.provider';
import { DomainResolverModule } from './customization';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
DomainResolverModule,
CoreModule.forRoot({
environment,
registerLocaleFn: registerLocale(),
}),
AbpOAuthModule.forRoot(),
ThemeSharedModule.forRoot({
httpErrorConfig: {
errorScreen: {
component: HttpErrorComponent,
forWhichErrors: [401, 403, 404, 500],
hideCloseIcon: true,
},
},
}),
// Other import statements
],
providers: [
APP_ROUTE_PROVIDER,
APP_VALIDATION_PROVIDER
],
bootstrap: [AppComponent],
})
export class AppModule {}
After this, if the website wildcard is web{0}.sample.com
, the web.sample.com
will be host, webtenant1.sample.com
will be tenant tenant1
.
Hi, ABP teams:
Name
property.Validators.Required
validator of name
in the buildForm
function.Name
input blank. Click the save button
I got the below errors:
Error detail not sent by server.
the server return the validation errors as follow:
The expected error message should something like The Name field is required.
If you're creating a bug/problem report, please include followings:
Hello,
What is your angular version?
Is the defaultProject property exists in your
angular.json
?We know defaultProject deprecated with angular 14. And we fixed proxy generation. The fix will be available in 6.0.2
Same error here, abp version 6.0.1, prompt enter target Angular project to place the generated code. when execute
generate-proxy -t ng
for the Application Module project type.
So what's the time for releasing 6.0.2?That does not solve the problem!
Dear Support team, any update on the original issue?
Open your eyes widely please! I am not the abp team member, I am just came across the same errors as you. Just provide a additional evidence that abp 6.0.1 also has this bug. so it absolutely can not solve your problem. So see clearly what the other person said next time before your next reply, is that ok?
Hello,
What is your angular version?
Is the defaultProject property exists in your
angular.json
?We know defaultProject deprecated with angular 14. And we fixed proxy generation. The fix will be available in 6.0.2
Same error here, abp version 6.0.1, prompt enter target Angular project to place the generated code. when execute generate-proxy -t ng
for the Application Module project type.
So what's the time for releasing 6.0.2?