Root cause: In ABP v9.2+ Angular UI packages were migrated to standalone. If your app still uses NgModule-based components, ABP Angular elements like abp-button (emitting abpClick) and abp-modal (content projection, submit wiring) will only work if their standalone imports/providers are added to your module (or component) explicitly. Using (click) works because it’s plain Angular, but abpClick is an ABP output from the ButtonComponent and requires importing the ABP button component/directives into the module where it’s used.
Fix: Import the ABP Angular standalone components into your module via the provided module-level re-export (for non-standalone apps), or import the components directly in a standalone component’s imports.
For a non-standalone component declared in an NgModule:
// app.module.ts (or a feature module where your modal component is declared)
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// Bring ABP Angular shared directives/components into module-based apps:
import { AbpThemeSharedModule } from '@abp/ng.theme.shared'; // re-exported compatibility module
import { MyFeatureComponent } from './my-feature.component';
@NgModule({
declarations: [MyFeatureComponent],
imports: [
CommonModule,
AbpThemeSharedModule, // IMPORTANT: provides abp-button, abp-modal, directives like abpClose, abpPermission, etc.
],
})
export class MyFeatureModule {}
If you don’t have AbpThemeSharedModule (depending on version), import the specific components into your module via their modules (compatibility packages) or switch your component to standalone and import the components there:
// my-feature.component.ts (standalone variant)
import { Component } from '@angular/core';
import { ButtonComponent } from '@abp/ng.theme.shared/components/button';
import { ModalComponent } from '@abp/ng.theme.shared/components/modal';
@Component({
selector: 'app-my-feature',
standalone: true,
imports: [ButtonComponent, ModalComponent],
template: `
<abp-modal [(visible)]="isOpen">
<ng-template #abpHeader>Title</ng-template>
<ng-template #abpBody>Body</ng-template>
<ng-template #abpFooter>
<abp-button (abpClick)="onSubmit()">Submit</abp-button>
</ng-template>
</abp-modal>
`,
})
export class MyFeatureComponent {
isOpen = true;
onSubmit() { /* ... */ }
}
<!-- inside modal footer/body -->
<abp-button (abpClick)="onSubmit()" [buttonType]="'button'">
Save
</abp-button>
Double-check that there isn’t an overlay or disabled state blocking clicks:
If you are wiring form submit:
Summary checklist for module-based app:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
ABP 9.x targets .NET 9. The official migration guide states that if you plan to use ABP 9.0, you need to migrate your solutions to .NET 9.0. Therefore, ABP 9.0/9.1 packages (for example, Volo.Saas.Host.Application 9.0.0) are not compatible with net8.0 and will produce NU1202 (exactly like in your screenshot), because those packages include net9.0 TFMs.
What you can do:
Minimal steps for Option B (upgrade to .NET 9 + ABP 9.x):
Why your error happens:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Current behavior (ABP v9.1.1, Angular): Idle Session Timeout works per-tab. The built-in Account Idle Session Timeout feature does not synchronize timers across multiple open tabs/windows by default. Each tab runs its own activity watcher and countdown. This is why you observe inconsistent behavior across tabs.
Implement multi‑tab sync (Angular) using storage events:
Example implementation outline:
// src/app/core/idle-sync.service.ts
import { Injectable, NgZone } from '@angular/core';
const CHANNEL_KEY = 'abp-idle-sync';
type IdleSyncMessage =
| { t: 'activity'; ts: number }
| { t: 'show-warning'; deadline: number }
| { t: 'cancel-warning' }
| { t: 'timeout' };
@Injectable({ providedIn: 'root' })
export class IdleSyncService {
constructor(private zone: NgZone) {
window.addEventListener('storage', (e) => {
if (e.key !== CHANNEL_KEY || !e.newValue) return;
const msg = JSON.parse(e.newValue) as IdleSyncMessage;
this.zone.run(() => this.onMessage?.(msg));
});
}
onMessage?: (msg: IdleSyncMessage) => void;
notify(msg: IdleSyncMessage) {
// write-then-remove to trigger storage event in other tabs
localStorage.setItem(CHANNEL_KEY, JSON.stringify(msg));
localStorage.removeItem(CHANNEL_KEY);
}
}
Example:
// src/app/app.component.ts
import { Component, HostListener, OnInit } from '@angular/core';
import { IdleSyncService } from './core/idle-sync.service';
import { AccountIdleSessionTimeoutService } from '@abp/ng.account/config'; // service used internally by ABP Account Angular UI
@Component({ selector: 'app-root', template: '<router-outlet></router-outlet>' })
export class AppComponent implements OnInit {
constructor(
private idleSync: IdleSyncService,
private idleService: AccountIdleSessionTimeoutService
) {}
ngOnInit() {
// 1) When local idle feature is about to show warning, broadcast to others
this.idleService.onShowWarning$.subscribe((deadlineUtcMs) => {
this.idleSync.notify({ t: 'show-warning', deadline: deadlineUtcMs });
});
// 2) When local warning is cancelled (user activity), broadcast
this.idleService.onCancelWarning$.subscribe(() => {
this.idleSync.notify({ t: 'cancel-warning' });
});
// 3) When local timeout happens (logout), broadcast
this.idleService.onTimeout$.subscribe(() => {
this.idleSync.notify({ t: 'timeout' });
});
// 4) React to messages from other tabs
this.idleSync.onMessage = (msg) => {
switch (msg.t) {
case 'activity':
this.idleService.resetTimerFromExternalActivity(); // custom helper; see below
break;
case 'show-warning':
this.idleService.showWarningExternally(msg.deadline);
break;
case 'cancel-warning':
this.idleService.cancelWarningExternally();
break;
case 'timeout':
this.idleService.triggerTimeoutExternally();
break;
}
};
}
// Broadcast user activity from this tab
@HostListener('document:mousemove')
@HostListener('document:keydown')
@HostListener('document:click')
onActivity() {
this.idleSync.notify({ t: 'activity', ts: Date.now() });
}
}
If the ABP Angular Account idle service you use doesn’t expose such methods, wrap/extend it where you already subscribe to its events (warning start/cancel, timeout). The idea is to:
Notes:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
ABP’s Idle Session Timeout feature already works across multiple tabs of the same browser profile. It uses browser storage and a single activity monitor to keep all tabs in sync: user activity in any tab resets the idle timer, and when the warning/timeout is reached, all tabs react consistently.
To enable and configure it in Angular (v9.1.1):
If you want to customize behavior:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
A) Keep using the header, but allow underscores through your ingress
For NGINX Ingress Controller:
For plain NGINX (not ingress), ensure:
underscores_in_headers on;
and don’t use ignore_invalid_headers on; (or set off)
Reference config snippet: server { listen 443 ssl; server_name auth.mycompany.com;
underscores_in_headers on;
# ignore_invalid_headers off; # if you have it on, headers with underscore are dropped
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://auth-server-svc;
}
}
Ensure your ABP app still has the header resolver enabled (it is by default): Configure<AbpTenantResolveOptions>(options => { options.AddHeaderTenantResolver(); // default uses "__tenant" });
Then you can request tokens with the header set: POST https://auth.mycompany.com/connect/token Headers: __tenant: my-tenant Content-Type: application/x-www-form-urlencoded Body: grant_type=password&client_id=...&client_secret=...&username=...&password=...
B) Avoid the header; use domain or query string based tenant resolution If your ingress cannot be changed, switch to a resolver that does not use underscores.
Subdomain/domain based resolution:
Query string fallback (for non-browser/token scenarios):
Important ABP middleware order (for the token endpoint and all APIs)
Quick verification checklist
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Fix it by verifying and applying the steps below:
Confirm the RemoteRefreshUrl and BaseUrl
Put MultiTenancy and Authentication in correct middleware order (server apps)
Check proxy/gateway and CORS
Kubernetes/NGINX specifics
Increase/override the distributed HTTP client timeout (optional)
Configure<AbpHttpClientBuilderOptions>(options =>
{
options.ProxyClientBuildActions.Add((remoteServiceName, client) =>
{
if (remoteServiceName == "Default" || remoteServiceName == "AuthServer")
{
client.Timeout = TimeSpan.FromSeconds(300);
}
});
});
This should not replace fixing connectivity, but helps if your auth service occasionally takes longer.
Configure<AbpClaimsPrincipalFactoryOptions>(opts =>
{
opts.IsDynamicClaimsEnabled = false;
});
Alternatively, provide your own IAbpDynamicClaimsPrincipalContributor that doesn’t call remote. Only use as a diagnostic step.
Blazor WASM specifics
React Native specifics
If the above is correctly configured and you still see refresh timeouts:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
public class MyAppService : ApplicationService
{
private readonly IBackgroundJobManager _backgroundJobManager;
public MyAppService(IBackgroundJobManager backgroundJobManager)
{
_backgroundJobManager = backgroundJobManager;
}
[UnitOfWork]
public virtual async Task DoSomethingAsync()
{
// 1) Perform your data changes here
// await _repository.InsertAsync(entity);
// 2) Register callback to run only if the UoW commits
CurrentUnitOfWork.OnCompleted(async () =>
{
await _backgroundJobManager.EnqueueAsync(
new MyJobArgs { /* ... */ }
);
});
}
}
EnqueueAsync runs after the transaction has been committed. If the UoW rolls back, the callback isn’t executed, so no Hangfire job is created.
Use domain events + transactional outbox (for cross-context/microservices)
Notes:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
What you observe is by design in ABP Framework versions before 8.3. In AbpDbContext.PublishEventsForTrackedEntity, ABP intentionally skips “updated” entity events and ABP concept application when only foreign key properties are modified:
Reason: This optimization existed to avoid firing update logic when only relationship fixups (FK changes) happen and the entity’s own scalar properties didn’t change.
Starting from ABP 8.3, this behavior was changed: ABP applies auditing/concurrency updates when a navigation property changes. That means LastModificationTime, LastModifierId, ConcurrencyStamp and EntityVersion are updated when navigation (and thus FK) changes are detected.
Your project is on ABP 9.1.3, which already includes that change. To ensure it’s active:
Configure<AbpEntityChangeOptions>(o =>
{
o.PublishEntityUpdatedEventWhenNavigationChanges = true; // default
});
Configure<AbpAuditingOptions>(options =>
{
options.EntityHistorySelectors.AddAllEntities();
});
Note: Even without entity history, the LastModificationTime should be set by ApplyAbpConcepts once navigation/FK changes are considered updates.
If you must keep older behavior (pre-8.3) or selectively control it:
Summary action items for your case:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Notes
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.