AbpPermissionGroups, AbpPermissions, AbpSettingDefinitions, AbpFeatureGroups, AbpFeatures, it means the Administration DB migrator/seeding didn’t run against that staging database (or it ran against a different connection string/database).Do this in your Azure DevOps release (staging) as a definitive fix:
Run the Administration DbMigrator against the staging database
Restart the Administration service after migrator/seeding
If you use Redis (distributed cache), clear it
Verify you are not accidentally pointing staging to a different DB
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.
src/assets/css and styles) and there is no setting / configuration file (in the solution or in ABP Studio) to change these output folders.No verified source available for this specific query.
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.
Please contact us at https://abp.io/contact.
Your specific questions (changing organization members / reassigning seats after the subscription lapses, whether a newly added developer can successfully abp login, and whether Development-mode online checks will pass for that newly added person) are commercial/account entitlement topics and can only be confirmed definitively by Volosoft for your organization/subscription.
(If you want to reference the general policy text, this is the closest official public summary: https://abp.io/faq#what-happens-when-my-license-period-ends)
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.
/Account/Login).application-configuration and sends it as the __tenant header on API calls. When you are redirected to the MVC login page after session expiration, that tenant context may be lost unless you resolve tenant again via domain/route or explicitly pass __tenant in the login navigation.If you use domain/subdomain per tenant (recommended):
https://{tenantHostName}/Account/Login).If you don’t (or can’t) use tenant domains/routes, then pass tenant to the login redirect using __tenant:
navigateToLogin, including __tenant.SessionStateService to get tenant id and pass it to login):import { AuthService, SessionStateService } from '@abp/ng.core';
constructor(
private authService: AuthService,
private sessionStateService: SessionStateService
) {}
login() {
const tenantId = this.sessionStateService.getTenant().id || null;
if (tenantId) {
this.authService.navigateToLogin({ __tenant: `${tenantId}` });
} else {
this.authService.navigateToLogin();
}
}
CustomLogoutModel runs on the server after sign-out and then redirects to /Account/Login?tenantName=....tenantName query param is not used by ABP Angular tenant resolution by default, so it may still show host context.__tenant (solution #2) rather than tenantName.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.
tenantName=... on the login page. Angular determines the tenant from the application-configuration response and then sends it back to the server as the __tenant header on subsequent requests. So, after an automatic logout, if you redirect to a login URL that doesn’t cause ABP to resolve the tenant again (cookie/header/domain/route), you can easily end up on the host login experience (default/department host) instead of the intended tenant context.tenantName query parameter.Pick one of these approaches:
A) Use Domain-based tenant resolution (best if you already have tenantHostName)
https://{tenantHostName}/Account/Login (or your tenant portal root, which then triggers auth).Update your CustomLogoutModel to redirect to an absolute URL (instead of RedirectToPage, which keeps you on the current host):
public override async Task<IActionResult> OnGetAsync()
{
var tenantHostName = await GetTenantHostNameAsync(CurrentTenant.Id ?? CurrentUser.TenantId);
await base.OnGetAsync(); // sign out
if (!string.IsNullOrWhiteSpace(tenantHostName))
{
var scheme = Request.Scheme; // "https"
var url = $"{scheme}://{tenantHostName}/Account/Login";
return Redirect(url);
}
// fallback: host login
return RedirectToPage("/Account/Login");
}
B) Use Route-based tenant resolution (/{__tenant}/...)
https://yourdomain.com/{tenantName}/..., then redirect to:
/{tenantName}/Account/Login (and make sure route tenant resolution is configured).tenantName=... for tenant resolutionYour current CustomLoginModel uses a non-standard tenantName query param and also strips _tenantHost from ReturnUrl.
That can work for branding/UI, but it won’t automatically make the ABP tenant resolution pipeline select the tenant unless you also implemented a tenant resolver that reads tenantName (ABP default resolvers don’t use tenantName).
So the logout redirect should go to a URL that triggers ABP’s normal tenant resolution again (domain/route/header/cookie).
base.OnGetAsync() signs out, which can clear cookies used by tenant resolution (depending on your setup)./Account/Login on the same host, and tenant resolution falls back to host, so you see the department host login.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 to do
Reference (official docs):
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 a known bug in the IdentityUserAppService update flow (as you diagnosed): UpdateUserRolesBasedOnOrganizationUnits(...) mutates input.OrganizationUnitIds (??=), which unintentionally turns “not provided” (null) into “provided but empty” ([]). Since the caller also has ManageOU, the later block runs and calls SetOrganizationUnitsAsync(user, []), clearing all OU memberships.
Workaround (no framework modification): override UpdateAsync and preserve the original intent (null)
IdentityUserAppService.input.OrganizationUnitIds into a local variable before calling base.null), restore OU memberships (re-apply existing memberships), or run a custom “roles-only” update path.Example approach (restore memberships when the input didn’t specify OUs):
using Volo.Abp.Identity;
using Volo.Abp.Application.Dtos;
public class MyIdentityUserAppService : IdentityUserAppService
{
public MyIdentityUserAppService(
IdentityUserManager userManager,
IIdentityUserRepository userRepository,
IIdentityRoleRepository roleRepository,
IIdentityClaimTypeRepository claimTypeRepository,
IOrganizationUnitRepository organizationUnitRepository)
: base(userManager, userRepository, roleRepository, claimTypeRepository, organizationUnitRepository)
{
}
public override async Task<IdentityUserDto> UpdateAsync(Guid id, IdentityUserUpdateDto input)
{
// Preserve original caller intent
var originalOuIds = input.OrganizationUnitIds;
// If caller didn't send OUs, fetch current memberships so we can restore them
Guid[]? currentOuIds = null;
if (originalOuIds == null)
{
var user = await UserManager.GetByIdAsync(id);
var ous = await UserManager.GetOrganizationUnitsAsync(user);
currentOuIds = ous.Select(x => x.Id).ToArray();
}
var result = await base.UpdateAsync(id, input);
// Restore memberships only when caller didn't touch OUs
if (originalOuIds == null && currentOuIds != null)
{
var user = await UserManager.GetByIdAsync(id);
await UserManager.SetOrganizationUnitsAsync(user, currentOuIds);
}
return result;
}
}
Register your derived service as a replacement (ABP standard DI replacement pattern).
Proper fix (framework side): exactly what you suggested—UpdateUserRolesBasedOnOrganizationUnits should use a local variable instead of mutating input.OrganizationUnitIds. Until that’s released in the framework, the override workaround above prevents OU loss.
If you also control the UI/API contract, a simpler mitigation is: always send OrganizationUnitIds explicitly when the current user has ManageOU (e.g., send the existing OU ids back), but your “roles tab” scenario is valid, so the service-side workaround is usually preferable.
(Verified docs related to Organization Units, for context only:)
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 does not provide a built-in “undo/redo” feature (like an application-level time machine for domain changes). ABP’s built-in features that are closest are Unit of Work (transaction rollback) and Audit Logging / Entity Change History—but those are primarily for tracking, not for reverting.
Important distinction:
SaveChanges/after commit): you need an explicit domain-level design (typically event sourcing / command log / snapshots), because the DB state is already committed.If your undo requirements are “user can revert what they just changed”, and your aggregates are not too complex, the most ABP-native approach is:
What you must handle yourself:
This becomes a compensating action system, not purely “replay EF changes”.
If you have dependent entities, cascading deletes, invariants, external side effects, etc., the robust approach is:
ChangeCustomerAddress, CancelOrder, AddOrderLine).ABP supports clean layering for this (Application Service methods as commands, Domain layer enforcing invariants). Audit logs can still be used for traceability, but not as the only truth for undo.
DbContext lifetime.SaveChanges, EF Core updates “original values” and entity states; it’s not a reliable “diff store” to replay later.So: don’t store EF change tracker output in cache as your undo source for committed operations.
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.
currentTenant.id is always null inside the LowCode background job script by default.jobData.What to do
tenantId when enqueueing (you already do this):var jobId = await jobs.enqueueAsync('SendCustomerSummary', {
customerId: body.customerId,
to: 'marco.susani@nicma.com',
tenantId: currentTenant.id
});
db.get:Conceptually you need the equivalent of ABP’s server-side approach:
using (_currentTenant.Change(tenantId)) { ... }So your job should be structured like:
// PSEUDO (depends on the exact LowCode scripting API functions available in your version)
if (!jobData.tenantId) {
userFriendlyError("tenantId is required for this job.");
}
// switch tenant context first
await currentTenant.changeAsync(jobData.tenantId); // or currentTenant.change(...)/withTenant(...)
// now tenant filter / connection selection will work as expected
var customer = await db.get('Customer', jobData.customerId);
if (!customer) { userFriendlyError('Customer not found.'); }
await email.queueAsync(jobData.to, 'Customer summary', customer.RagioneSociale);
If your LowCode scripting API doesn’t expose a currentTenant.change* (or similar) method, then there is no supported way to have ABP automatically apply tenant context inside that LowCode background job, and you should implement the job in “regular” ABP code (C#) where you can do:
using (_currentTenant.Change(tenantId))
{
// query repositories / DbContext here
}
That is the officially recommended mechanism for tenant-specific work in background workers/jobs.
ICurrentTenant.Change is the way to operate on a specific tenant in a background worker/job)TenantId is set from ICurrentTenant.Id, reinforcing that tenant context must be set for tenant-aware operations)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 ABP 10.6, the C# proxy generator is not a “dependency graph / transitive closure” generator.
When you run abp generate-proxy -t csharp ... -st integration, ABP determines the “models to generate” primarily by namespace grouping (service namespace and related model namespaces), not by recursively walking every referenced DTO/enum type across other namespaces. That’s why you see:
HttpApi.Client package.Recommended/supported approach for independently deployed apps (AppA → NuGet → AppB):
AppA.Integration.Contracts).StatusNumber, NumberType, etc. into something like AppA.Domain.Shared but in an integration-safe namespace/package (or a new AppA.Integration.Shared project).What you’re doing now (moving Integration services + IntegrationDto into a separate namespace) is essentially the intended boundary.
The missing piece is: anything used by that boundary must also live in (or be referenced by) that boundary, otherwise the generated client can’t be self-contained because the generator won’t chase types across your whole solution and emit a perfect closure.
What not to rely on
--without-contracts is for the opposite scenario: it assumes the consumer already references the target service’s *.Application.Contracts package so types are reused (and avoids duplication). It won’t help you make a single self-contained package if you don’t publish those contracts.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.