ABP Framework 3.3 to 4.0 Migration Guide

This document introduces the breaking changes done in the ABP Framework 4.0 and explains how to fix your 3.x based solutions while upgrading to the ABP Framework 4.0.

See the blog post to learn what's new with the ABP Framework 4.0. This document only focuses on the breaking changes.

Overall

Here, the overall list of the changes;

  • Upgraded to the .NET 5.0 (#6118).
  • Moved from Newtonsoft.Json to System.Text.Json (#1198).
  • Upgraded to the Identity Server 4.1.1 (#4461).
  • Switched to kebab-case for conventional URLs for the auto API controller routes (#5325).
  • Removed Retry for the Dynamic HTTP Client Proxies (#6090).
  • Creation audit properties of the entities made read-only (#6020).
  • Changed type of the IHasExtraProperties.ExtraProperties (#3751).
  • Use IBrandingProvider in the Volo.Abp.UI package and remove the one in the Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared (#5375).
  • Removed the Angular Account Module Public UI (login, register... pages) since they are not being used in the default (authorization code) flow (#5652).
  • Removed the SessionState in the @abp/ng.core package (#5606).
  • Made some API revisions & startup template changes for the Blazor UI.

Upgraded to .NET 5.0

ABP Framework has been moved to .NET 5.0. So, if you want to upgrade to the ABP Framework 4.0, you also need to upgrade to .NET 5.0.

See the Migrate from ASP.NET Core 3.1 to 5.0 document to learn how to upgrade your solution to .NET 5.0.

Moved to System.Text.Json

ABP Framework 4.0 uses the System.Text.Json by default as the JSON serialization library. It, actually, using a hybrid approach: Continues to use the Newtonsoft.Json when it needs to use features not supported by the System.Text.Json.

Unsupported Types

If you want to use the Newtonsoft.Json to serialize/deserialize for some specific types, you can configure the AbpSystemTextJsonSerializerOptions in your module's ConfigureServices method.

Example: Use Newtonsoft.Json for MySpecialClass

Configure<AbpSystemTextJsonSerializerOptions>(options =>
{
    options.UnsupportedTypes.AddIfNotContains(typeof(MySpecialClass));
});

Always Use the Newtonsoft.Json

If you want to continue to use the Newtonsoft.Json library for all the types, you can set UseHybridSerializer to false in the PreConfigureServices method of your module class:

PreConfigure<AbpJsonOptions>(options =>
{
    options.UseHybridSerializer = false;
});

Upgraded to Identity Server 4.1.1

ABP Framework upgrades the IdentityServer4 library from 3.x to 4.1.1 with the ABP Framework version 4.0. IdentityServer 4.x has a lot of changes. Some of them are breaking changes in the data structure.

Entity Changes

Entity changes don't directly affect your application; however, it is good to know.

ApiScope

As the most critical breaking change; Identity Server 4.x defines the ApiScope as an independent aggregate root. Previously, it was the child entity of the ApiResource. This change requires manual operation. See the Database Changes section.

Also, added Enabled(string) and Description(bool,true) properties.

ApiResource

  • Added AllowedAccessTokenSigningAlgorithms (string) and ShowInDiscoveryDocument(bool, default: true) properties

Client

  • Added RequireRequestObject <bool> and AllowedIdentityTokenSigningAlgorithms <string> properties.
  • Changed the default value of RequireConsent from true to false.
  • Changed the default value of RequirePkce from false to true.

DeviceFlowCodes

  • Added SessionId <string> and Description <string> properties.

PersistedGrant

  • Added SessionId <string>, Description <string> and ConsumedTime <DateTime?> properties

Database Changes

Attention: Please backup your database before the migration!

If you are upgrading from 3.x, then there are some steps should be done in your database.

Database Schema Migration

If you are using Entity Framework Core, you need to add a new database migration, using the Add-Migration command, and apply changes to the database. Please review the migration script and read the sections below to understand if it affects your existing data. Otherwise, you may lose some of your configuration, which may not be easy to remember and re-configure.

Seed Code

If you haven't customized the IdentityServerDataSeedContributor and haven't customized the initial data inside the IdentityServer* tables;

  1. Update IdentityServerDataSeedContributor class by comparing to the latest code. You probably only need to add the CreateApiScopesAsync method and the code related to it.
  2. Then you can simply clear all the data in these tables then execute the DbMigrator application to fill it with the new configuration.

Migrating the Configuration Data

If you've customized your IdentityServer configuration in the database or in the seed data, you should understand the changes and upgrade your code/data accordingly. Especially, the following changes will affect your application:

  • IdentityServerApiScopes table's Enabled field is dropped and re-created. So, you need to enable the API scopes again manually.
  • IdentityServerApiResourceScopes table is dropped and recreated. So, you need to backup and move your current data to the new table.
  • IdentityServerIdentityResourceClaims table is dropped and recreated. So, you need to backup and move your current data to the new table.

You may need to perform additional steps based on how much you made custom configurations.

Other IdentityServer Changes

IdentityServer has removed the public origin option. It was resolving HTTP/HTTPS conversion issues, but they decided to leave this to the developer. This is especially needed if you use a reverse proxy where your external protocol is HTTPS but internal protocol is HTTP.

One simple solution is to add such a middleware at the begingning of your ASP.NET Core pipeline.

app.Use((httpContext, next) =>
{
    httpContext.Request.Scheme = "https";
    return next();
});

This sample is obtained from the ASP.NET Core documentation. You can use it if you always use HTTPS in all environments.

Related Resources

Auto API Controller Route Changes

The route calculation for the Auto API Controllers is changing with the ABP Framework version 4.0 (#5325). Before v4.0 the route paths were camelCase. After version 4.0, it's changed to kebab-case route paths where it is possible.

A typical auto API before v4.0

route-before-4

camelCase route parts become kebab-case with 4.0

route-4

How to Fix?

You may not take any action for the MVC & Blazor UI projects.

For the Angular UI, this change may effect your client UI. If you have used the ABP CLI Service Proxy Generation, you can run the server side and re-generate the service proxies. If you haven't used this tool, you should manually update the related URLs in your application.

If there are other type of clients (e.g. 3rd-party companies) using your APIs, they also need to update the URLs.

Use the v3.x style URLs

If it is hard to change it in your application, you can still to use the version 3.x route strategy, by following one of the approaches;

  • Set UseV3UrlStyle to true in the options of the options.ConventionalControllers.Create(...) method. Example:
options.ConventionalControllers
    .Create(typeof(BookStoreApplicationModule).Assembly, opts =>
        {
            opts.UseV3UrlStyle = true;
        });

This approach affects only the controllers for the BookStoreApplicationModule.

  • Set UseV3UrlStyle to true for the AbpConventionalControllerOptions to set it globally. Example:
Configure<AbpConventionalControllerOptions>(options =>
{
    options.UseV3UrlStyle = true;
});

Setting it globally affects all the modules in a modular application.

Removed Retry for the Dynamic HTTP Client Proxies

Dynamic C# HTTP Client Proxies were trying up to 3 times if a request fails using the Polly library. Starting from the version 4.0, this logic has been removed. If you need it, you should configure it in your own application, by configuring the AbpHttpClientBuilderOptions in the PreConfigureServices method of your module.

Example: Retry 3 times on failure by incremental waiting between tries

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    PreConfigure<AbpHttpClientBuilderOptions>(options =>
    {
        options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
        {
            clientBuilder.AddTransientHttpErrorPolicy(
                policyBuilder => policyBuilder
                    .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))
            );
        });
    });
}

This example uses the Microsoft.Extensions.Http.Polly NuGet package.

If you create a new solution, you can find the same configuration in the .HttpApi.Client.ConsoleTestApp project's module class, as an example.

Creation Audit Properties Made Read-Only

Removed setters from the IHasCreationTime.CreationTime, IMustHaveCreator.CreatorId and IMayHaveCreator.CreatorId properties to accidently set the creation properties while updating an existing entity.

Since the ABP Framework automatically sets these properties, you normally don't need to directly set them. If you want to set them, as a best practice, it is suggested to make it in the constructor to not provide a way to change it later.

These properties implemented with protected set in the Entity and AggregateRoot base classes. That means you can still set in a derived class, if you need it. Alternatively, you can use reflection to set them (Or use ObjectHelper.TrySetProperty which internally uses reflection) out of the class if you have to do.

Changed type of the IHasExtraProperties.ExtraProperties

IHasExtraProperties.ExtraProperties was a regular Dictionary<string, object>. With the version 4.0, it is replaced with ExtraPropertyDictionary class which inherits the Dictionary<string, object>.

Most of the applications don't be affected by this change. If you've directly implemented this interface, replace the standard dictionary to the ExtraPropertyDictionary.

Other Changes

IdentityOptions Usage

Previously, when you inject IOptions<IdentityOptions>, you get a dynamically overridden options value. For example, when you get IdentityOptions.Password.RequiredLength, the value is being changed based on the setting (IdentitySettingNames.Password.RequiredLength) of the current tenant. That means IdentityOptions changes per tenant. However, this caused an issue and we had to change the usage.

With the version 4.0, you need to inject IOptions<IdentityOptions> and call the new SetAsync method before using it, to be able to override the options by the settings. Otherwise, you get the default (statically configured) values of the options.

Example usage:

public class MyService : ITransientDependency
{
    private readonly IOptions<IdentityOptions> _options;

    public MyService(IOptions<IdentityOptions> options)
    {
        _options = options;
    }

    public async Task DoItAsync()
    {
        await _options.SetAsync();

        var requiredLength = _options.Value.Password.RequiredLength;
    }
}

Pre-built modules already handles this. However, if you have used IdentityOptions directly in your code, you also need to follow this new pattern. Please make sure that the injected IOptions<IdentityOptions> service and the service consuming it are in the same scope of dependency injection container.

LDAP module full async

In order to solve the problem of async over sync, ILdapManager uses async method instead of sync. And use ldap4net to replace Novell.Directory.Ldap.NETStandard package.

Dynamic external login provider system

You need to change the WithDynamicOptions method and pass the Handler class of the external login provider. Use the goto definition function in Visual Studio or Rider to check Handler in the extension method like AddGoogle.

- WithDynamicOptions<GoogleOptions>()
+ WithDynamicOptions<GoogleOptions, GoogleHandler>()

ASP.NET Core MVC / Razor Pages UI

See the ASP.NET Core MVC / Razor Pages UI Migration Guide.

Angular UI

See the Angular UI Migration Guide.

Blazor UI

See the Blazor UI Migration Guide.

In this document