Activities of "LinchArnold"

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:

  1. Create a 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);
}

  1. Create 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 {}
  1. Import the 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:

  1. Create a 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);
}
  1. Create 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 {}
  1. Import the 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.

Thanks. @masum.ulu

Hi, ABP teams:

  1. I use the newest v7.2.2 suite to generate Angular UI/ Tiered Project with Lepton Theme.
  2. Create new Foobar entity with Required Name property.
  3. For angular side, I removed the Validators.Required validator of name in the buildForm function.
  4. Start the projects and angular, add new foobar with the 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:

  • ABP Framework version: v7.2.2
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes

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?

Hi, muhammedaltug: I had update my angular version to 14. and update the angular packages to version 14 either.

And this is result of yarn why @abp/ng.schematics:

Showing 1 to 10 of 27 entries
Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11