Hi,
The C# proxy generator picks the DTOs by the namespace prefix of the service class, not by the types your method signatures actually use. That's why DTOs that only share the namespace get generated, and types your contracts depend on but live elsewhere don't. There is no switch to change that today.
So rather than generating the contracts, publish them - that's what our own modules do. Volo.Abp.Identity.HttpApi.Client references the published Volo.Abp.Identity.Application.Contracts and is generated with --without-contracts, so it carries client proxy classes only and nothing is duplicated.
For you that is a small AppA.Integration.Contracts project with the two integration service interfaces and their *IntegrationDto types. It references AppA.Domain.Shared, so StatusNumber and NumberType stay exactly where they are - no move, no second declaration. AppA.Application.Contracts references the new project, and you publish AppA.Domain.Shared and AppA.Integration.Contracts next to AppA.HttpApi.Client.
[DependsOn(
typeof(AppADomainSharedModule),
typeof(AbpDddApplicationContractsModule)
)]
public class AppAIntegrationContractsModule : AbpModule
{
}
In AppA.HttpApi.Client, swap the AppA.Application.Contracts project reference for AppA.Integration.Contracts, replace that one entry in DependsOn and keep the rest, and register the proxies over the contracts assembly:
[DependsOn(
typeof(AppAIntegrationContractsModule), // replaces AppAApplicationContractsModule
typeof(AbpHttpClientModule),
typeof(AbpIdentityHttpApiClientModule), // the other module dependencies stay as they are
// ...
)]
public class AppAHttpApiClientModule : AbpModule
{
public const string RemoteServiceName = "Default";
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddStaticHttpClientProxies(
typeof(AppAIntegrationContractsModule).Assembly,
RemoteServiceName
);
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AppAHttpApiClientModule>();
});
}
}
Remove the previously generated files before you regenerate - generate-proxy overwrites and adds, it never removes, so the 73 old DTOs would stay behind and keep producing CS0436. remove-proxy deletes the whole ClientProxies folder, so move any partial proxy class you have customized out of the way first:
abp remove-proxy -t csharp
abp generate-proxy -t csharp -u https://localhost:44314/ -m app -st integration --without-contracts
You end up with the two client proxy classes and the proxy json, no DTOs. AppB references AppA.HttpApi.Client and gets the contracts and the enums through it, so there is a single declaration of each enum and no dependency on AppA.Application.Contracts.
We'll also improve the generator to emit the types a contract actually depends on instead of relying on the namespace grouping.
Thanks
Hi,
Low-Code background jobs go through ABP's dynamic background job pipeline, and that pipeline doesn't carry the tenant of the request that enqueued the job. So currentTenant.id is null while the job script runs, and db.get('Customer', ...) only looks at the host data. The tenantId you put into the job data is just part of your payload, the framework doesn't read it.
Keep passing tenantId as you already do, and add this class to your project:
using System;
using System.Text.Json;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace AbpSolution4;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBackgroundJobExecuter))]
public class TenantAwareBackgroundJobExecuter : BackgroundJobExecuter
{
public TenantAwareBackgroundJobExecuter(
IOptions<AbpBackgroundJobOptions> options,
ICurrentTenant currentTenant)
: base(options, currentTenant)
{
}
protected override Guid? GetJobArgsTenantId(object jobArgs)
{
if (jobArgs is DynamicBackgroundJobArgs dynamicJobArgs && !dynamicJobArgs.JsonData.IsNullOrWhiteSpace())
{
using var document = JsonDocument.Parse(dynamicJobArgs.JsonData);
if (document.RootElement.ValueKind == JsonValueKind.Object)
{
foreach (var property in document.RootElement.EnumerateObject())
{
if (property.NameEquals("tenantId") &&
property.Value.ValueKind == JsonValueKind.String &&
Guid.TryParse(property.Value.GetString(), out var tenantId))
{
return tenantId;
}
}
}
}
return base.GetJobArgsTenantId(jobArgs);
}
}
Your job script then runs under that tenant and needs no change.
We'll fix this on the framework side so the enqueueing tenant is carried automatically and you won't need the class above. It will be in an upcoming release. Your ticket has been refunded.
Thanks
Hi,
The confirmation popup binds the title and the message with [innerHTML], so HTML that comes inside an error message is rendered as markup. Angular sanitizes it first, so script tags and event handler attributes are dropped and no script runs, but plain tags like <b>, <img> or <a href> still render.
If you want the text shown as-is, replace ConfirmationService (and ToasterService if you also show toasts) and escape the strings before they reach the template:
// text-only-messages.service.ts
import { Injectable } from '@angular/core';
import { LocalizationParam } from '@abp/ng.core';
import { Confirmation, ConfirmationService, Toaster, ToasterService } from '@abp/ng.theme.shared';
import { Observable } from 'rxjs';
export function escapeHtml(value: string): string {
return value
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function escapeParam(param: LocalizationParam): LocalizationParam {
return typeof param === 'string' ? escapeHtml(param) : param;
}
@Injectable({ providedIn: 'root' })
export class TextOnlyConfirmationService extends ConfirmationService {
override show(
message: LocalizationParam,
title: LocalizationParam,
severity?: Confirmation.Severity,
options = {} as Partial<Confirmation.Options>,
): Observable<Confirmation.Status> {
return super.show(escapeParam(message), escapeParam(title), severity, {
...options,
messageLocalizationParams: options.messageLocalizationParams?.map(escapeHtml),
titleLocalizationParams: options.titleLocalizationParams?.map(escapeHtml),
});
}
}
@Injectable({ providedIn: 'root' })
export class TextOnlyToasterService extends ToasterService {
// the toast title is bound with {{ }}, only the message goes through innerHTML
override show(
message: LocalizationParam,
title?: LocalizationParam,
severity: Toaster.Severity = 'neutral',
options = {} as Partial<Toaster.ToastOptions>,
): Toaster.ToasterId {
return super.show(escapeParam(message), title, severity, {
...options,
messageLocalizationParams: options.messageLocalizationParams?.map(escapeHtml),
});
}
}
Then register them in your app providers (app.config.ts, or app.module.ts if you are still on modules):
import { ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { TextOnlyConfirmationService, TextOnlyToasterService } from './text-only-messages.service';
providers: [
{ provide: ConfirmationService, useExisting: TextOnlyConfirmationService },
{ provide: ToasterService, useExisting: TextOnlyToasterService },
// ...
];
Localization keys and {0} parameters keep working. One thing to keep in mind: after this change, HTML that your own code passes to a confirmation or a toast on purpose is shown as text too.
Thanks
Hi,
ABP updates the principal entity when one of its related entities is added, updated or deleted: it stamps the modification audit properties on it even when none of its own properties changed. That's why the entry ends up Modified with only the audit columns dirty. It started in 8.3.0, so you didn't hit it on 8.2.3. It's controlled by the UpdateAggregateRootWhenNavigationChanges option, which is true by default.
Keep the option on and list the entities you want to skip:
Configure<AbpEntityChangeOptions>(options =>
{
options.IgnoredUpdateAggregateRootSelectors.Add("Patient", type => type == typeof(Patient));
options.IgnoredUpdateAggregateRootSelectors.Add("FinancialCounseling", type => type == typeof(FinancialCounseling));
});
AbpEntityChangeOptions is in Volo.Abp.Domain.Entities.Events. The entity updated event is still published for these entities, and the extra ConcurrencyStamp update is skipped as well, so you can drop the guard from your HandlePropertiesBeforeSave override.
There is also a global UpdateAggregateRootWhenNavigationChanges = false, but I wouldn't turn that one off. It applies to every aggregate root in the application, including the ones coming from the ABP modules, so none of them gets its audit properties and concurrency stamp updated anymore when only its child collections change.
There is no reason to replace IAuditPropertySetter here, it doesn't get the EF entry so it can't tell which properties are dirty.
Two related changes in 10.7: unitOfWork.DisableUpdateAggregateRootWhenNavigationChanges() disables it for a single unit of work, and the principal is now updated only when it really has a navigation property for the changed relation. So if nothing but the foreign key ties FinancialCounseling to Patient, 10.7 stops stamping Patient without any configuration.
Thanks
Hi,
Development mode keeps working after the license expires. The online check doesn't fail the way a missing AbpLicenseCode does — it passes and the app starts normally.
What you hit on the fresh machine is a different thing: in Development the check needs that machine to be signed in, and a new machine has no CLI session yet. It reports ABP-LIC-ERROR as well, so the two are identical in the logs, but the cause here is only the missing sign-in. Run abp login there and it's fine — same before and after expiry.
To your three points:
Development after expiry — the check passes. Your license still covers the commercial modules you are using, so existing projects build, run and debug as before.
New machine after expiry — abp login still works on a lapsed account. The per-developer computer limit still applies, and that one is independent of the license state.
On the packages: the commercial template writes your organization API key into the solution's NuGet.Config, so if that file is in your repository a clone brings it along and restore needs no extra setup. The versions you are already on keep restoring after expiry. The Angular packages are on the public npm registry, so npm install for those versions isn't affected.
Production / Staging — you don't need it, keep running Development locally. Switching would cost you the development-only behavior of the template, such as the developer exception page, and it wouldn't help while debugging anyway: an attached debugger runs the online check in any environment.
So the normal build, run and debug loop works on a new machine too, once that machine is signed in. The other post-expiry limits from my previous reply still apply.
Thanks
Hi,
Keep the Program.cs change. This one is the Dockerfile: it packages the dotnet build output, which includes SCV.Litmus.IdentityServer.staticwebassets.runtime.json. Switch it to the dotnet publish output, that file isn't part of it.
The gateway image needs the same change.
Thanks
Hi,
Let's start with the ordering, because it changes the shape of the plan.
ABP 10.x is a .NET 10 release. Volo.Abp.Core and a few low-level packages still multi-target, but everything your app actually references ships net10.0 only — Volo.Abp.AspNetCore.Mvc, Volo.Abp.EntityFrameworkCore, Volo.Abp.EntityFrameworkCore.PostgreSql, every Pro module package, and LeptonX 5.6.0. The 10.0 migration guide says it directly: "you need to move your solutions to .NET 10.0 if you want to use ABP 10.0".
So option (a) is out — a net9.0 project can't reference those packages. Option (b) technically resolves, since a net10.0 project can consume net9.0 assets, but ABP 9.0.4 brings Npgsql 9, which pins Microsoft.EntityFrameworkCore >= 9.0.0 && < 10.0.0. You'd be running on .NET 10 with EF Core 9 and OpenIddict 5, so nothing gets isolated and you still have the whole jump ahead of you. Go with (c) for the backend: ABP 10.6 and net10.0 in one pass.
The part you can stage separately is Angular. It's a separate build and it has to step through majors anyway, so run it on its own track.
1. The customized module source
No tool merges this for you. abp update only rewrites NuGet/NPM versions, get-source only downloads clean source, and there is no abp suite migrate. Use a plain git vendor branch and let git do the three-way merge:
# a branch that only ever holds untouched vendor source
git checkout --orphan abp-vendor
abp get-source Volo.Abp.Identity.Pro -v 9.0.4 -o modules/Volo.Abp.Identity.Pro
git add . && git commit -m "vendor 9.0.4"
# ... your customized copies live on your normal branch, based on that same baseline
# when it's time to upgrade, drop the new release onto the vendor branch
git checkout abp-vendor
rm -rf modules/Volo.Abp.Identity.Pro
abp get-source Volo.Abp.Identity.Pro -v 10.6.0 -o modules/Volo.Abp.Identity.Pro
git add -A && git commit -m "vendor 10.6.0"
git checkout your-branch
git merge abp-vendor
You only get conflicts where you and we touched the same lines. The module names for get-source are Volo.Abp.Identity.Pro, Volo.Abp.Account.Pro, Volo.Abp.AuditLogging.Pro, Volo.Saas, Volo.Abp.PermissionManagement, Volo.Abp.LanguageManagement, Volo.Abp.TextTemplateManagement.
On the size of it — here is what changed upstream between 9.0.4 and 10.6.0, counting only the projects an Angular + EF Core app compiles (Blazor, MudBlazor, MAUI, MongoDB and test projects excluded):
| Module | Files we modified | Files we added | |---|---|---| | Account Pro | 99 | 69 | | Identity Pro | 45 | 48 | | Permission Management | 29 | 33 | | SaaS | 29 | 18 | | Text Template Management | 15 | 6 | | Audit Logging Pro | 12 | 23 | | Language Management | 11 | 4 | | Total | 240 | 201 |
Your merge surface is the intersection of those 240 files with the files you edited, not the ~1,000 files you own.
Structurally it's better news than you expect. No project was renamed or removed, no namespace or folder reorganisation, and the new projects are all MudBlazor / MAUI / bundling variants you don't use. The one real structural change is AutoMapper → Mapperly in 10.0: every module's *AutoMapperProfile.cs is gone and replaced by a Mapperly mapper class, so any custom mappings you added to those profiles need rewriting. Volo.Abp.AutoMapper is still published in 10.6 and mixing both is supported, and if you hold a commercial AutoMapper license there's a drop-in replacement package:
A few renames to expect: MaxUserCountValidator → AbpIdentityMaxUserCountValidator, and in Account MVC IdentitySettingGroupViewComponent → AccountSettingGroupViewComponent.
On moving back to NuGet packages — worth doing, but as a separate project after you're stable on 10.6, not during the upgrade. Extension points cover extra entity properties (including the database column, API and UI), replacing app services, domain services, repositories, controllers and page models, your own endpoints and permissions, and Angular component/form replacement:
What they won't cover: changing the CLR type, inheritance or navigation collections of a module entity, changing the signature of an existing method on a module interface while module code still calls it, editing non-DI internal flows, and rewriting a module's own [DependsOn]. Plan to keep whatever falls in that group as source, and de-vendor the rest.
2. One step or several
For the module source, merge straight from 9.0.4 to 10.6.0. Stopping at 9.3 first means resolving the same conflicts twice — measured across Identity Pro, Account Pro and Permission Management, the two-hop route touches about 30% more files than going direct.
The migration guides are the opposite: apply all of them in order, 9.1 through 10.6. Nothing forces you to actually install and run an intermediate version, and none of these seven modules has a one-time data migrator you'd skip over. Don't pick 10.0 as a stopping point either — ABP 10.0.0 shipped against Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0-rc.1, and that only moved to the stable provider in 10.0.3.
3. .NET 10 readiness
10.6 runs on EF Core 10.0.9, Npgsql EF Core 10.0.0 (stable), OpenIddict 7.5.0, Hangfire 1.8.21. Pro module packages, LeptonX 5.6.0, ABP Suite and ABP Studio are all on net10.0 today. Nothing in EF Core 10 or ASP.NET Core 10 needs a workaround on your side.
One thing to plan around, on the Angular side: @abp/ng.permission-management, @abp/ng.setting-management and @volo/abp.ng.audit-logging 10.6.0 declare a peer dependency on @angular/aria ~21.2.0, while the 10.6 template installs @angular/aria ~22.0.2. With npm's strict peer resolution that fails; yarn treats it as a warning. Use --legacy-peer-deps if you're on npm, and keep it on the list to revisit. Don't drop Angular back to 21 over it.
4. Migration guides
5. Database
There's no single schema-delta document, so here's what the modules changed between 9.0 and 10.6:
| Table | Change |
|---|---|
| AbpUserPasswordHistories | new (10.1) |
| AbpUserPasskeys | new (10.1) |
| AbpResourcePermissionGrants | new (10.1) |
| AbpPermissions | GroupName now nullable, ResourceName and ManagementPermissionName added, unique index moved from Name to (ResourceName, Name) (10.1) |
| AbpUserInvitations | new, host database only (10.2) |
| AbpAuditLogs | EntityTypeFullName 128 → 512, PropertyTypeFullName 64 → 512 (10.2) |
| AbpAuditLogExcelFiles | new (9.3) |
| AbpOpenIddictTokens | Type max length 50 → 150 (10.0, from OpenIddict 7) |
| AbpBackgroundJobs | ApplicationName added (9.2), CompletionTime added (10.6) |
SaaS, Language Management, Text Template Management and Database Blob Storing have no table changes.
Two of these need your DbContext touched by hand before you generate the migration. Add the new DbSet if your context implements IIdentityProDbContext:
public DbSet<UserInvitation> UserInvitations { get; set; }
Then generate the migration from your own model snapshot — don't copy one out of a fresh template.
The one place an auto-generated migration will lose data is the distributed event inbox, if you have it enabled. IncomingEventRecord.Processed and ProcessedTime became Status, HandledTime, RetryCount and NextRetryTime. EF will emit a drop-and-add and throw away the processed state, so map it yourself in the migration:
UPDATE "AbpEventInbox" SET "Status" = CASE WHEN "Processed" THEN 2 ELSE 0 END;
UPDATE "AbpEventInbox" SET "HandledTime" = "ProcessedTime";
Status is Pending = 0, Discarded = 1, Processed = 2.
Also worth a look before you go live: 10.4 split Text Template Management's edit permission and added EditNonSandboxedContents. Roles that need to keep editing Razor templates have to be granted it explicitly, so don't bulk-copy the old grants.
Nothing PostgreSQL-specific beyond that.
6. Angular
10.6 needs Angular 22.0.2 and TypeScript 6.0.3. Angular only supports one major per ng update, so from 18.1 you go through 19, 20, 21, 22. The ABP and LeptonX versions that pair with each step:
| ABP | Angular | LeptonX | |---|---|---| | 9.0 | 18.1 | 4.0.x | | 9.1 | 19.1 | 4.1.x | | 9.3 | 20.0 | 4.3.x | | 10.1 | 21.0 | 5.1.x | | 10.6 | 22.0 | 5.6.0 |
abp update moves the npm versions but doesn't run Angular's migrations, and there's no ABP schematic that upgrades versions, so drive the majors yourself:
ng update @angular/core@19 @angular/cli@19
ng update @angular/core@20 @angular/cli@20
ng update @angular/core@21 @angular/cli@21
ng update @angular/core@22 @angular/cli@22
Angular 20 needs Node 20.19.0 or newer. The 10.6 guide covers the Angular 22 work, including section 5 which is specifically about forked module UI:
https://abp.io/docs/10.6/release-info/migration-guides/abp-10-6-angular-22
The parts that will hit your customized identity/account/audit-logging screens hardest: strictTemplates is on by default in 22, change detection for components without an explicit strategy changed so list/modal/loading state needs to move to signals, and upload progress needs provideHttpClient(withFetch(), withXhr()).
Two more that aren't in the guide. @abp/ng.oauth no longer stores tokens in local storage by default — non-SSR apps now get an in-memory store, so any code reading the access token out of localStorage directly has to go through AuthService / OAuthService instead. And LeptonX moved some internals between 4.x and 5.6, side-menu-layout among them, so deep imports into theme paths will need fixing. Public entry points are unchanged.
7. OpenIddict
You cross two majors: 5.8 → 6.0 in ABP 9.1, 6.x → 7.2 in ABP 10.0.
Permissions.Endpoints.Logout → EndSession and Permissions.Endpoints.Device → DeviceAuthorization: https://abp.io/docs/10.6/release-info/migration-guides/openiddict5-to-6Type column length: https://documentation.openiddict.com/guides/migration/60-to-70.htmlAbpProfilePictureOptions if you need other image types.client_id on the interactive cookie and access token forwarding for authenticated client requests.The authorization code / PKCE contract your Angular SPA uses is unchanged. Since you've customized the account flows, regression-test the paths you actually use — external login, impersonation, link-user and 2FA — rather than just password login.
8. Tooling
Latest ABP Studio CLI:
dotnet tool update -g Volo.Abp.Studio.Cli
Then, per solution:
abp update -v 10.6.0 -lv 5.6.0
abp get-source <module> -v 10.6.0 -o <folder>
Use ABP Suite 10.6 to match. Pin the version on abp update — without it you'll land on the 10.7 RC.
9. Effort and risk
I'd rather not put a number on the effort without seeing the actual patch set, but the three things most likely to cost you time are, in order: the Account/Identity/OpenIddict customizations landing on top of the 10.3–10.6 security changes, merging your own EF migrations with the new model (the inbox mapping above in particular), and four Angular majors plus LeptonX plus the token storage change all hitting your customized pages at once.
No reason to wait for a later release. 10.6.0 is the current stable, 10.7 is still RC, and the whole dependency chain you care about is on stable releases.
Before you plan the whole thing, take one module through the merge end to end and measure it. Language Management is the smallest — 11 modified files upstream — so it's cheap to try, and it will tell you how much of your patch set actually collides. Scale from that number rather than from the file count you own.
Thanks
Hi,
Thanks, that's enough to see it. In the identity host's Program.cs the two ABP calls aren't awaited:
builder.AddApplicationAsync<LitmusIdentityServerModule>();
var app = builder.Build();
app.InitializeApplicationAsync();
app.Run();
InitializeApplicationAsync is where OnApplicationInitialization runs, so it's where UseAuthentication, UseConfiguredEndpoints and the rest get added. Without the await, app.Run() starts the host straight away, and the host builds the request pipeline at that moment. If module initialization hasn't reached your module yet, nothing is in the pipeline and every request falls through to that 404. It's a timing thing, which is why it works on your machine and not on the task.
Make Main async and await both calls, the same way the template does:
public static async Task<int> Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// ... same as now ...
await builder.AddApplicationAsync<LitmusIdentityServerModule>();
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();
return 0;
}
The HttpApi.Host Program.cs has the same two lines, change it there as well. The Startup.cs files aren't used by these Program.cs at all, you can delete them.
Thanks
Hi,
Not for this one. The discovery request isn't reaching OpenIddict at all, so nothing is read from those tables yet. They come into play once the endpoints respond: the discovery document lists the scopes from OpenIddictScopes, and OpenIddictApplications is checked on the authorize/token calls of your Angular client. Leave them as they are for now.
Thanks