ABP Version 10.7 Migration Guide
This document is a guide for upgrading ABP v10.6 solutions to ABP v10.7. This version includes explicitly marked migration-impacting changes for specific customization scenarios, while most applications can upgrade with no additional action beyond the notes below.
Package Version Changes: Before upgrading, review the Package Version Changes document to see version changes on dependent NuGet and NPM packages and align your project with ABP's internal package versions.
Open-Source (Framework)
This version contains the following changes on the open-source side:
Constructor Changes
Who is affected
- Applications that derive from the services below, replace them with a derived class, or construct them manually.
What changed
These services take new constructor dependencies. There is no overload with the previous signature, so a derived class does not compile until its constructor is updated:
IdentityUserManagertakes a newIUnitOfWorkManager.EfCoreIdentitySessionRepositoryandMongoIdentitySessionRepositorytake a newIClock.BlazorServerCurrentApplicationConfigurationCacheResetServicetakes a newApplicationConfigurationChangedService.
IdentityUserManager also writes the user's last sign-in time as a best-effort update in its own unit of work now, so the value is saved after the sign-in unit of work completes instead of within it.
What to do
Add the new parameters to your derived constructors and pass them to the base constructor.
See #25905 for details.
Blazor Antiforgery Middleware Order
Who is affected
- Blazor applications that call
UseAntiforgery()beforeUseAuthorization(). This covers the solutions generated by the classic templates before v10.7 and the solutions generated by the current modern solution templates.
What changed
- ASP.NET Core requires
UseAntiforgery()to be called afterUseAuthentication()andUseAuthorization(). - The classic Blazor startup templates now use that order. Existing solutions keep the middleware order they were generated with, so they are not updated by the package upgrade.
What to do
Check the OnApplicationInitialization method of your Blazor module and move the calls into this order:
app.UseAuthentication();
// ...
app.UseAuthorization();
app.UseAntiforgery();
See #25874 for details.
Angular Resource API Proxies
Who is affected
- Angular applications that regenerate their service proxies with the new
--resource-apioption.
What changed
- With
--resource-api, every generatedGETmember returns anrxResource-basedResourceRefinstead of anObservable, and takes its parameters as a singleSignal. A parameterless endpoint has no signal parameter, and the optional request configuration stays a normal argument. The other HTTP methods are unchanged. - The option is off by default, so a regular proxy regeneration keeps the current output.
What to do
Only pass --resource-api when the components that consume the GET methods are ready for the resource form. It requires Angular v22 or later.
See the Angular Service Proxies document and #25761 for details.
BLOB Encryption and Content Pipeline
Who is affected
- Applications that enable BLOB encryption for a container that already stores plaintext BLOBs.
- Applications that add, remove, or reorder transforming BLOB pipeline contributors for a container that already stores BLOBs.
What changed
- ABP v10.7 adds opt-in BLOB encryption at rest and a configurable content pipeline.
- Encryption and pipeline contributors are disabled by default, so existing containers are not affected unless you enable or configure them.
- Encrypted BLOBs require the same passphrase that was used to write them.
- A transforming pipeline contributor becomes part of the stored BLOB format.
What to do
No action is required if you do not enable encryption or configure transforming pipeline contributors.
If you enable encryption for a container that already contains plaintext BLOBs:
- Enable encryption with
allowLegacyPlainText: trueand configure a passphrase, either for the container or globally withAbpBlobStoringEncryptionOptions.DefaultPassPhrase. - Re-save every existing BLOB through the container.
- Remove
allowLegacyPlainTextafter the migration, so reads fail closed for plaintext content.
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
container.UseEncryption(allowLegacyPlainText: true);
});
});
If you change transforming pipeline contributors or an encryption passphrase, read and export the existing BLOBs while the old configuration is active, apply the new configuration, and save the content back. Do not change an encryption passphrase in place before migrating the existing BLOBs.
See the BLOB Encryption and BLOB Content Pipeline documents and #25836 for details.
Aggregate Root Update on Foreign Key Only Relations
Who is affected
- Applications with a relation that has no navigation property on the principal side, such as
HasOne<T>().WithMany(), where the principal entity is tracked while one of its dependents is added, modified or deleted.
What changed
- ABP treated any dependent change as a navigation change of the principal, so the principal was updated (
ConcurrencyStamp, audit properties) and its entity updated event was published, even if the principal had no navigation for that relation. Concurrent changes on such dependents conflicted on the principal row and failed withAbpDbConcurrencyException. - The principal is now updated only when it really has a navigation property for the changed relation. Relations with navigation properties keep their current behavior.
IUnitOfWork.DisableUpdateAggregateRootWhenNavigationChanges()is added to disable updating the aggregate root for a unit of work, without disabling its entity updated event. It overrides theAbpEntityChangeOptions.UpdateAggregateRootWhenNavigationChangesoption.
What to do
Add a navigation property to the principal entity if you rely on the previous behavior, or update the principal in your own code.
See #25937 for details.
Identity Token Providers Moved to the Domain Layer
Who is affected
- Solutions where password reset, email confirmation or change email tokens are generated on one host and validated on another, such as a separate authentication server or a microservice solution.
- Hosts that load only the Identity domain layer and relied on the ASP.NET Core Identity providers for the
Default,Email,PhoneorAbpLinkUserkeys. - Applications that derive from
AbpSingleActiveTokenProvider,AbpTwoFactorTokenProvideror from one of the ABP token providers, or that callIdentityUserManagerSingleActiveTokenExtensions. - Applications that treat one of the ABP token provider options classes as a
DataProtectionTokenProviderOptions.
What changed
- The ABP token providers and their options moved from the
Volo.Abp.Identity.AspNetCoreassembly toVolo.Abp.Identity.Domain, out of theVolo.Abp.Identity.AspNetCorenamespace and intoVolo.Abp.Identity, and are now registered byAbpIdentityDomainModuleinstead ofAbpIdentityAspNetCoreModule. An assembly compiled against an earlier version and not rebuilt cannot resolve them. The types that stay in the ASP.NET Core layer, such asAbpSignInManagerandAbpIdentityAspNetCoreOptions, keep their namespace. - Every host that loads
AbpIdentityDomainModuletherefore resolves the same providers, unless it registers something else itself. Previously the providers were only registered on hosts loadingAbpIdentityAspNetCoreModule, so a host that generated a token could end up on a different provider than the host that validated it, and the link was rejected as an invalid token. AbpSingleActiveTokenProviderno longer derives from ASP.NET Core'sDataProtectorTokenProvider<IdentityUser>; it implementsIUserTwoFactorTokenProvider<IdentityUser>and re-implements the same protected payload, so the token format is unchanged. Code that casts a provider toDataProtectorTokenProvider<IdentityUser>or uses it as a generic constraint no longer compiles, and theLoggerproperty the old base class exposed publicly is now protected.- The provider options classes derive from
AbpDataProtectionTokenProviderOptionsinstead ofDataProtectionTokenProviderOptions. TheNameandTokenLifespanproperties are unchanged, but code that assigns one of them toDataProtectionTokenProviderOptions, passes it to a method taking that type, returns it, or uses it as a generic constraint no longer compiles. IdentityUserManagerSingleActiveTokenExtensionsmoved with the providers, and itsRemove*TokenAsynchelpers changed in two ways. They now follow the provider's optionsNameinstead of the key it is registered under, so an application that renamed a provider gets the hash it actually wrote removed. And they throw anAbpExceptioninstead of reporting success when the key is not served by anAbpSingleActiveTokenProvider, which is the case once the ABP providers are turned off: there is no stored hash to remove then, and a token that was never single-active cannot be revoked this way.- The constructors changed accordingly.
AbpSingleActiveTokenProvidertakesIOptions<AbpDataProtectionTokenProviderOptions>, which each provider satisfies with its own concrete options class, andILogger<AbpSingleActiveTokenProvider>instead ofIOptions<DataProtectionTokenProviderOptions>andILogger<DataProtectorTokenProvider<IdentityUser>>. The five DataProtector-based providers (AbpDefaultTokenProvider,AbpPasswordResetTokenProvider,AbpEmailConfirmationTokenProvider,AbpChangeEmailTokenProvider,LinkUserTokenProvider) take the new logger type as well. The email and phone 2FA providers moved unchanged. Constructing a provider by hand also behaves differently at the edges: a null options now throws instead of falling back to the ASP.NET Core defaults, which carry the wrong provider name, and a null logger falls back toNullLoggerinstead of throwing. AbpIdentityDomainModulecallsAddDataProtection(), becauseUserManagerinstantiates every provider inTokens.ProviderMapwhen it is resolved and the DataProtector-based providers needIDataProtectionProvider. Hosts that never issue a token, such as a DbMigrator console application, do not register it themselves. Data Protection registers a hosted service that loads the key ring when the host starts and creates one if the store is empty, so such a host now does that too. That is a side effect for such a host, not a reason to configure it: only a host that generates or validates a token needs the same key ring andSetApplicationNameas the rest of the solution.
What to do
- Replace
using Volo.Abp.Identity.AspNetCore;withusing Volo.Abp.Identity;where you use one of the moved types, or add the second one when the file also uses a type that stayed behind. - Rebuild the solution. No further action is required for the common case: configuring the options and the provider names works exactly as before, and the compiler points at the source level changes below. The assembly move is not one of them, so a package you depend on that was built against the old assembly has to be rebuilt and republished against v10.7 as well.
- If you derive from one of the five DataProtector-based providers, change the logger parameter to
ILogger<AbpSingleActiveTokenProvider>. - If you derive from
AbpSingleActiveTokenProviderdirectly, give your provider its own options class deriving fromAbpDataProtectionTokenProviderOptionsand injectIOptions<YourTokenProviderOptions>, the way the built-in providers do.IOptions<T>is covariant, so it satisfies the base constructor. Do not injectIOptions<AbpDataProtectionTokenProviderOptions>: the base options class is abstract and the options system cannot create it. - If a host of yours registered these providers by hand, you can drop that code.
IdentityBuilder.AddAbpTokenProviders()is public if you want to call it explicitly. - Remove an
AddDefaultTokenProviders()call a domain-only host made for itself, unless you turn the ABP providers off everywhere. Application actions run after the framework registration, so that call now takes theDefault,EmailandPhonekeys back to the ASP.NET Core providers while the other keys stay on the ABP ones, and a host that keeps it no longer agrees with one that does not. - Tell the users of a domain-only host to request a new link. The tokens it issued through the stock providers before the upgrade are rejected afterwards.
- Move a
Configure<DataProtectionTokenProviderOptions>a domain-only host relied on to the corresponding ABP options class, on every host that validates a token. It stops applying there, the way it stopped applying on hosts loadingAbpIdentityAspNetCoreModulein v10.2. - Give a domain-only host write access to the shared identity database. It now stores a user token for every token it generates through these providers. The
Authenticatorprovider is unchanged and stores nothing. - To keep the ASP.NET Core Identity providers instead, turn the ABP ones off and register them yourself, on every host that generates or validates a token:
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpIdentityTokenProviderOptions>(options =>
{
options.UseAbpTokenProviders = false;
});
PreConfigure<IdentityBuilder>(builder =>
{
builder.AddDefaultTokenProviders();
});
}
Both have to be PreConfigure, not Configure. The flag turns off the registration itself on every host alike, so the Identity module registers no token provider at all and a flow whose key has no provider throws NotSupportedException on the first call. AddDefaultTokenProviders() covers the Default, Email, Phone and Authenticator keys, and nothing covers the AbpLinkUser key either way.
See the Identity Token Providers document for details.
OpenIddict Client Assertion Audience Validation
Who is affected
- Applications that authenticate OpenIddict clients with client assertions, that is
private_key_jwtorclient_secret_jwt, and use the same client signing key on more than one OpenIddict authorization server.
What changed
- OpenIddict is upgraded from 7.5.0 to 7.7.0, which fixes GHSA-925x-4h4v-2792. The server stack compared only the path of the
audclaim with the issuer of the authorization server, so a client assertion minted for one server was accepted by another one that shared the client's signing key. - The audience of a client assertion still has to be the issuer URI of the server that receives it, with or without the trailing slash, so an assertion that was already addressed to the right server keeps working. A correctly configured client needs no change.
- A single authorization server instance, or several instances with their own cryptographic material, was never affected.
What to do
Upgrade the packages. There is no configuration change.
Until you can upgrade, give every authorization server its own client signing key, so an assertion cannot be replayed across them.
Tenant of Dynamic Background Jobs and Events
Who is affected
- Applications that enqueue jobs through
IDynamicBackgroundJobManagerwith a runtime registered handler. - Applications that publish string named dynamic events and handle them after an outbox or a message broker.
- Applications that derive from
DefaultDynamicBackgroundJobManager, or that overrideDistributedEventBusBase.AddToInboxAsyncorAzureDistributedEventBus.PublishAsync(string, object).
What changed
- A dynamic handler job now runs under the tenant that enqueued it.
DynamicBackgroundJobArgsimplementsIMultiTenantandDefaultDynamicBackgroundJobManagerfills it fromICurrentTenant, so the existingBackgroundJobExecuterrestores the tenant before the handler runs. Previously the transport args carried no tenant and the job ran under the host, because the worker thread has none. - A string named dynamic event now runs under the tenant that published it. Its payload is arbitrary user data with no place for a tenant, so the tenant travels on the transport as the
X-Tenant-Idheader, next to the correlation id, and the message body is unchanged. Typed events keep carrying their tenant in the event data itself and are not touched: no header is written for them and their handlers behave exactly as before. DefaultDynamicBackgroundJobManagertakes a newICurrentTenantdependency, andAddToInboxAsyncand the AzurePublishAsync(string, object)take a new optional tenant parameter. A derived class does not compile until its constructor or its override is updated.- A tenant header that is present but is not a GUID fails the message instead of handling it under the host.
- Dapr is unchanged, because it does not support dynamic event subscriptions.
What to do
- Add the new parameters to your derived constructors and overrides.
- Review the dynamic handlers you register at runtime. A handler that reads host wide data while the job is enqueued from a tenant request now sees that tenant's data. Wrap such a handler in
using (CurrentTenant.Change(null))if it has to run under the host, or enqueue the job inside the same scope. - Drain or re-enqueue the jobs and the events that are still waiting from before the upgrade. They carry no tenant and keep running under the host.
- Upgrade the consumers before the producers in a tiered or microservice solution. A message published by an upgraded producer loses its tenant on a consumer that is not upgraded yet, the same way it did before the upgrade.
See #26148 for details.
Dependency Updates
Who is affected
- Applications that pin any of the packages below in their own
.csprojfiles. The startup templates pin several of them, so a solution generated before v10.7 has them. - Applications that use EF Core with MySQL, or directly reference MudBlazor or Blazorise.
What changed
- The
Microsoft.AspNetCore.*,Microsoft.EntityFrameworkCore.*,Microsoft.Extensions.*andSystem.*packages are upgraded from 10.0.9 to 10.0.11, and theMicrosoft.IdentityModel.*packages andSystem.IdentityModel.Tokens.Jwtfrom 8.19.1 to 8.19.2. OpenIddict 7.7.0 requires these versions. Microsoft.AspNetCore.DataProtectionis added as a direct dependency.- MudBlazor is upgraded from 9.4.0 to 9.7.0, and the Blazorise packages from 2.2.1 to 2.3.0.
MySql.EntityFrameworkCoreis upgraded from 10.0.1 to 10.0.9.Npgsql.EntityFrameworkCore.PostgreSQLis upgraded from 10.0.0 to 10.0.3.MongoDB.Driveris upgraded from 3.10.0 to 3.11.2, andAWSSDK.S3andAWSSDK.SecurityTokento their current versions.- ABP maps
Guid[]query parameters through its own type mapping plugin on MySQL, and storesIdentityUserPasskey.Dataas a serializedjsoncolumn, because the MySQL providers support neither EF Core JSON columns nor primitive collections. The column name and the stored content stay the same.
What to do
Upgrading the ABP packages does not move the versions you pin yourself, and abp update rewrites Volo.* references only. A reference that stays below what ABP requires fails the restore, so align those versions in your own .csproj files.
These are the ones a solution generated by the startup templates hits:
| Package | Align to |
|---|---|
| Microsoft.AspNetCore.Authentication.OpenIdConnect | 10.0.11 |
| Microsoft.AspNetCore.Components.WebAssembly | 10.0.11 |
| Microsoft.AspNetCore.Components.WebAssembly.Authentication | 10.0.11 |
| Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation | 10.0.11 |
| Microsoft.EntityFrameworkCore | 10.0.11 |
| Microsoft.Extensions.FileProviders.Embedded | 10.0.11 |
| System.IdentityModel.Tokens.Jwt | 8.19.2 |
A solution that pins other packages of these families can hit the same error on them, so see the Package Version Changes document for the full list and align the rest as well, to keep your solution on one set of versions.
Review direct MudBlazor and Blazorise references, align them with ABP's package versions, and re-test customized components and forms after upgrading.
If you use the MySQL provider, align your own MySql.EntityFrameworkCore reference with ABP's version.
Pro
This version contains the following changes on the PRO side:
Identity Pro
Who is affected
- Applications that derive from
Volo.Abp.Identity.IdentityProUserManager, replace it with a derived class, or construct it manually. - Applications that use Identity Pro with MySQL.
What changed
IdentityProUserManagertakes the newIUnitOfWorkManagerdependency, like its base class.UserInvitation.AssignedRolesis stored as a serializedjsoncolumn on MySQL, because the MySQL providers do not support primitive collections. The column name and the stored content stay the same.
What to do
Add the parameter to your derived constructor and pass it to the base constructor.
AI Management Web Page Data Sources
Who is affected
- Applications that use the AI Management module with Entity Framework Core.
- Applications that implement
IWorkspaceDataSourceAppServiceorIWorkspaceAppService, or derive fromWorkspaceDataSourceAppServiceorWorkspaceAppService.
What changed
- Workspace data sources can be created from a web page URL, so the
WorkspaceDataSourceentity has the newSourceType,SourceUrl,SourceTitle, andFetchedAtproperties. IWorkspaceDataSourceAppServicehas the newCreateFromUrlAsyncandRefreshFromUrlAsyncmethods, andIWorkspaceAppServicehas the newGetModelsAsyncoverloads.WorkspaceDataSourceAppServicetakes the newIWebPageMarkdownConverterdependency andWorkspaceAppServicetakes the newIEnumerable<IAIProviderModelCatalog>dependency.
What to do
Create a new EF Core migration and apply it to your database after upgrading.
If you implement these interfaces yourself, implement the new methods. If you derive from the application services, add the new parameters to your constructors.
ABP Suite Extensibility
Who is affected
- Applications or tools that implement
ICodeFormatter, or that derive from, replace, or manually constructCrudPageGenerator.
What changed
ICodeFormatterhas the newFormatTypescriptCodeAsyncmember.CrudPageGeneratortakes the newReactUiGenerateCommanddependency.
What to do
Implement the new formatter method in your own ICodeFormatter, and pass the new parameter to the CrudPageGenerator constructor.