@ng-bootstrap/ng-bootstrap version being installed/resolved in your Angular app.
@volo/abp.ng.account, @volo/abp.ng.identity, etc.) are compiled against an ng-bootstrap version that exports module symbols like NgbNavModule, NgbDropdownModule, NgbPopoverModule, NgbDatepickerModule, NgbTooltipModule.NgbModule?”). This typically happens when yarn resolves a different major of @ng-bootstrap/ng-bootstrap than what the ABP Angular packages expect.Force a compatible ng-bootstrap version for ABP 10.0.2 by pinning it in your angular/package.json and/or using Yarn resolutions.
In angular/package.json:
{
"dependencies": {
"@ng-bootstrap/ng-bootstrap": "<PIN_TO_ABP_COMPATIBLE_VERSION>"
},
"resolutions": {
"@ng-bootstrap/ng-bootstrap": "<PIN_TO_ABP_COMPATIBLE_VERSION>"
}
}
Then:
node_modulesyarn.lockyarnyarn startyarn why @ng-bootstrap/ng-bootstrap
.d.ts files are under:
node_modules/@volo/abp.ng.account/...node_modules/@volo/abp.ng.identity/node_modules/@ng-bootstrap/ng-bootstrap/...node_modules/@volo/... (it will be overwritten and isn’t the root cause). The correct fix is to normalize/pin frontend package versions so only the compatible ng-bootstrap version is used.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.
If you are using the redirect (code flow) login (most common): customize the backend login page
If you want to customize the Angular login UI itself: use ABP Angular “component replacement” for Account components
AddReplaceableComponent and eAccountComponents.* keys.Example (replace logo / tenant box / login form):
import { AddReplaceableComponent } from '@abp/ng.core';
import { Component } from '@angular/core';
import { Store } from '@ngxs/store';
import { eAccountComponents } from '@volo/abp.ng.account';
@Component({ template: `<div>MY CUSTOM LOGO</div>` })
class CustomLogoComponent {}
@Component({ template: `<div>MY CUSTOM TENANT BOX</div>` })
class CustomTenantBoxComponent {}
@Component({ template: `<div>MY CUSTOM LOGIN FORM</div>` })
class CustomLoginComponent {}
@Component({
selector: 'app-root',
template: `
<abp-loader-bar></abp-loader-bar>
<abp-dynamic-layout></abp-dynamic-layout>
`,
})
export class AppComponent {
constructor(store: Store) {
store.dispatch(
new AddReplaceableComponent({ component: CustomLogoComponent, key: eAccountComponents.Logo }),
);
store.dispatch(
new AddReplaceableComponent({ component: CustomTenantBoxComponent, key: eAccountComponents.TenantBox }),
);
store.dispatch(
new AddReplaceableComponent({ component: CustomLoginComponent, key: eAccountComponents.Login }),
);
}
}
Sources (verified):
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 typically happens because ABP Studio still tries to launch Visual Studio via the old (VS 2022) registration/path, and after uninstalling VS 2022 there is no longer a valid “devenv”/shell association to open the solution.
Fix it by re-establishing the Windows “open with Visual Studio” registration:
.sln files with Visual Studio 2026 (important even if VS opens manually)
*.sln file → Open with → Choose another app → pick Visual Studio 2026 → check Always use this app.If it still doesn’t work, the remaining reliable workaround is:
.sln (after the association above), or open VS first and use File → Open → Project/Solution.Links (verified):
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.
Nothing in ABP v10 “intentionally” changes an expired/invalid access token response from 401 to 400.
The payload you posted is an ABP validation error (it’s coming from model binding/validation, not from authentication). The key clue is:
validationErrors→ member:"id"→"The value 'Error' is not valid."
That means the request is reaching the action and ASP.NET Core is trying to bind a parameter/route value named id (likely Guid/int). Instead of a valid id, the literal string "Error" is being sent.
When the token expires, your client is no longer calling the real endpoint URL, but a URL that contains "Error" in the {id} segment (or it is sending "Error" as a query/body field named id). Common causes after upgrades are:
"Error" and that value gets used as the id for the next request,id.So ABP returns 400 because the request is malformed (invalid id), before/independently of auth.
Client-side: ensure you don’t build URLs with an error string
{id} segment (or id parameter) is still a real Guid/int when you get the expired-token case.Client-side: handle 401 properly
Server-side verification
/api/.../Error/... or ?id=Error.If after confirming the actual requested URL you still see ABP returning 400 while the request URL and route params are correct, then it would be something different—but with the error payload you posted, it’s a request validation/model-binding problem, not an auth-status-code change.
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.
/signalr-hubs/chat request, and Kestrel can surface it as an IOException: The client reset the request stream if your debugger is configured to break on user-unhandled exceptions.What to do (recommended)
System.IO.IOException/signalr-hubs/chat/{**catch-all} (not only /api/chat/...), otherwise the client will constantly retry/connect-fail and you may see more noise/exceptions.If you’re on a microservice / gateway setup (ABP Suite installed Chat)
Links (verified):
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 see is expected: they are different UIs backed by different implementations.
currentUser info and typically displays userName.Fixing the long username UI break / truncation (Angular):
userName.
<strong>{{ (currentUser$ | async)?.userName }}</strong>
- Replace it with name/surname (with fallback), e.g.:
<strong>
{{
((currentUser$ | async)?.name || '') + ' ' + ((currentUser$ | async)?.surname || '')
|| (currentUser$ | async)?.userName
}}
</strong>
- If your template doesn’t have `name`/`surname` available in the `currentUser` object, you can still solve the UI break with CSS (next step) or extend what you load into `currentUser` (project-specific).
.my-user-display-name {
max-width: 180px; /* adjust */
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
}
And wrap the displayed text:
<strong class="my-user-display-name">{{ (currentUser$ | async)?.userName }}</strong>
userName, name, surname, etc. in a consistent way).Sources:
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 is not an ABP Suite “wrong import” problem; it’s a TypeScript paths + Angular new application builder resolution problem that shows up only in modular/module-development scenario (where you reference module UI libs via tsconfig.paths to their projects/.../src/public-api.ts), and it can manifest as:
NG3004: Unable to import symbol PermissionDirective. The symbol is not exported from ... @abp/ng.core/index.d.tsPageComponent, etc.)Why it happens
node_modules (directly or indirectly) is stricter and local library builds can end up being resolved in a way that mixes two “copies” of ABP Angular packages (one from the main app’s node_modules, another from the module library build context)..d.ts from one place but expects exports consistent with another resolution graph, you get “symbol declared here but not exported from index.d.ts” style errors (like your PermissionDirective case).Definitive workaround (recommended by ABP community guidance): use symlinks instead of relying on paths to source projects
tsconfig.json / tsconfig.base.json path mappings that try to “redirect” packages to node_modules (or that cause the library project to resolve ABP dependencies differently than the app).node_modules as the main Angular app by symlinking dependencies into each library project.node_modules folder under each angular/projects/<your-module-lib> if it doesn’t exist@abp/*, @angular/*, rxjs, etc.) from the main app angular/node_modules into the library’s node_modulesThis matches the behavior you observed: when you bypass paths and consume the built output via file: dependency, everything works because module resolution becomes consistent again.
What to do in ABP v10.x
file: (your current working approach), avoiding paths redirection.Sources (validated)
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.
In an ABP Angular (separated Auth Server) solution, a 20–30s “first response” delay is almost always server cold-start + first-time caches/build steps, not Angular rendering itself. The Angular app blocks on backend calls like OIDC discovery/token and especially GET /api/abp/application-configuration (ABP must load permissions, localization resources, settings, etc.). After that, subsequent navigations become fast because the server and caches are warm.
Use this checklist (highest impact first) to reduce first-load and first-visit-per-module slowness:
Run everything in Release mode (backend)
Avoid expensive “run on startup” operations
Warm up the app (solve cold start)
/api/abp/application-configuration once after deployment)Check Redis / distributed cache health & latency (if enabled)
Reduce payload and work in /api/abp/application-configuration
Angular-side: serve production build
ng build --configuration production (and serve the built assets), not dev server.Practical way to pinpoint the exact cause (fastest confirmation):
application-configuration).Sources (verified)
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.
CMS Kit Pages (Dynamic Pages) do not support storing multiple language versions of the same page out-of-the-box.
The built-in Pages feature stores a single content (Markdown/HTML) per page/slug. There isn’t a built-in PageTranslation (or similar) entity in the CMS Kit module to automatically resolve page content by the current UI culture.
Recommended approach to implement KB page localization (practical and ABP-aligned):
/en/how-to-reset-password/de/wie-passwort-zuruecksetzenKnowledgeBasePage (your aggregate) that contains shared metadata (e.g., “group id”, canonical key, ordering, tags), and store per-language content in a child table like KnowledgeBasePageTranslation { Language, Title, Slug, Content }.KbPageLocalizationMap { GroupId, Language, CmsPageId }).fr missing, fall back to en) in your own application service./en/...) and maintain a stable “canonical key” per article in your custom model.Why this is the safe way with CMS Kit today:
If you want a ready pattern for “multi-lingual entity” in ABP (for your KB pages/translations tables), you can follow the approach shown in the community article and adapt it from “Book + BookTranslation” to “KbPage + KbPageTranslation”.
Links (verified):
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.
Implementing 2.2 (add the claim value to the principal for client_credentials) is best done by plugging into the OpenIddict Server event pipeline at token issuance time:
/connect/token is called with grant_type=client_credentials, you read the current client_id, load its application record, get the extra property, then add a claim to the principal used to mint the access token.IAbpOpenIddictClaimsPrincipalHandler (step 2.1) then ensures that claim is actually included in the access token via destinations.Below is a concrete, working pattern.
Create a class like this in your *.AuthServer (or wherever OpenIddict Server runs):
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using Volo.Abp.DependencyInjection;
public class TourOperatorClientCredentialsClaimsHandler :
IOpenIddictServerHandler<OpenIddictServerEvents.HandleTokenRequestContext>,
ITransientDependency
{
private readonly IOpenIddictApplicationManager _applicationManager;
public TourOperatorClientCredentialsClaimsHandler(
IOpenIddictApplicationManager applicationManager)
{
_applicationManager = applicationManager;
}
public async ValueTask HandleAsync(OpenIddictServerEvents.HandleTokenRequestContext context)
{
// Only for client_credentials
if (!context.Request.IsClientCredentialsGrantType())
{
return;
}
// Load application by client_id
var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId!, context.CancellationToken);
if (application == null)
{
return;
}
// Read your extra property from the OpenIddict application "Properties" JSON
var tourOperatorCode = await _applicationManager.GetPropertyAsync<string>(
application,
ExtraPropertyConsts.TourOperatorCodePropertyName,
context.CancellationToken
);
if (string.IsNullOrWhiteSpace(tourOperatorCode))
{
return;
}
// IMPORTANT:
// In client_credentials, OpenIddict typically uses a principal representing the *client*.
// Add your claim to the principal that will be used to issue tokens.
// (Depending on pipeline stage, this can be context.Principal or context.Identity.)
var identity = context.Principal?.Identity as ClaimsIdentity;
if (identity == null)
{
// Create one if needed
identity = new ClaimsIdentity(
authenticationType: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
nameType: OpenIddictConstants.Claims.Name,
roleType: OpenIddictConstants.Claims.Role);
context.Principal = new ClaimsPrincipal(identity);
}
identity.AddClaim(new Claim(ExtraPropertyConsts.TourOperatorCodePropertyName, tourOperatorCode));
}
}
In your AuthServer module:
using OpenIddict.Server;
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.AddEventHandler<OpenIddictServerEvents.HandleTokenRequestContext>(options =>
{
options.UseScopedHandler<TourOperatorClientCredentialsClaimsHandler>();
});
});
}
At this point:
IAbpOpenIddictClaimsPrincipalHandler) should set it to AccessToken so it actually appears in the JWT.Your TourOperatorClaimDestinationsHandler is still needed so the claim is not dropped and is placed into the access token:
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
Once the claim is in the token, you can implement a custom data filter keyed by TourOperatorCode and enable it by default for your entity/repository logic (so you don’t forget to apply it in each query).
(Your referenced ABP data filtering doc is exactly the right direction.)
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.