Skip to content

Remove all the store resolvers and mark all the assemblies as trimming/Native AOT-compatible#2278

Merged
kevinchalet merged 1 commit intoopeniddict:devfrom
kevinchalet:native_aot
Mar 22, 2025
Merged

Remove all the store resolvers and mark all the assemblies as trimming/Native AOT-compatible#2278
kevinchalet merged 1 commit intoopeniddict:devfrom
kevinchalet:native_aot

Conversation

@kevinchalet
Copy link
Member

@kevinchalet kevinchalet commented Mar 22, 2025

Fixes #1346.

This PR introduces trimming and Native AOT support for all the OpenIddict stacks: while there are still rough edges that make using Native AOT in real world applications quite complicated in practice (e.g I haven't been able to make EF Core 9 work on Native AOT and it doesn't seem ASP.NET Core Identity supports it yet), it has incredible advantages - like massively reduced startup times or reduced memory footprint - that make it a highly desirable option for some scenarios. Using Native AOT is also required in some cases (e.g modernized UWP applications converted to .NET 9.0 and published to the Microsoft Store), so it is essential that OpenIddict be compatible with it.

While making the OpenIddict client, server and validation stacks trimming and AOT-compatible didn't require any complicated change (beyond moving to source-generated JSON serialization), the core stack needed a lot more attention due the store resolver model not being trimming/AOT-friendly at all (it uses reflection in a way that can't be analyzed statically).

The following changes were required to make OpenIddict AOT-friendly:

  • The store resolver interfaces (IOpenIddict*StoreResolver) and all their implementations have been removed and the managers have been updated to now directly take an IOpenIddict*Store<T> argument instead of an IOpenIddict*StoreResolver.
  • All the OpenIddictCoreOptions.Default*Type options (e.g DefaultApplicationType) have been removed and the untyped managers (IOpenIddict*Manager) no longer use options to determine the actual entity type at runtime. Instead, each store integration is now responsible for replacing the IOpenIddict*Manager services with a service descriptor pointing to the generic OpenIddict*Manager<T> implementation with the correct T argument: by default, the default entity types provided by the store are used, but the managers can be re-registered with a different type when the user decides to use different models (e.g via options.UseEntityFrameworkCore().ReplaceDefaultModels<...>()).
  • All the managers/store/store resolvers registration APIs offered by OpenIddictCoreBuilder have been removed: while they were very powerful and easy-to-use (e.g the Replace*Manager methods supported both open and closed generic types and were able to determine the entity type from the base type definition), they weren't AOT-compatible.
  • New AOT-friendly Replace*Store() and Replace*Manager() APIs have been introduced in OpenIddictCoreBuilder. The Replace*Manager() have two overloads that can be used depending on whether you need to register a closed or open generic type:
options.ReplaceApplicationManager<
    /* TApplication: */ OpenIddictEntityFrameworkCoreApplication,
    /* TManager: */ CustomApplicationManager<OpenIddictEntityFrameworkCoreApplication>>();
options.ReplaceApplicationManager(typeof(CustomApplicationManager<>));
  • While they are currently not functional on Native AOT due to EF Core not supporting interpreted LINQ expressions yet, the EF Core stores package has been updated to be ready for AOT: as part of this change, the signature of all the stores has been updated to remove the TContext generic argument from the definition. Similarly, the MongoDB C# driver isn't AOT (or even trimming) compatible yet, but the stores have been updated to ensure we only use statically-analyzable patterns.
  • A new IOpenIddictEntityFrameworkCoreContext interface containing a single ValueTask<DbContext> GetDbContextAsync(CancellationToken cancellationToken) method (similar to what's currently used in the MongoDB integration) has been introduced to allow each to resolve the DbContext to use. A default implementation named OpenIddictEntityFrameworkCoreContext<TContext> is used by the OpenIddictEntityFrameworkCoreBuilder.UseDbContext<TContext>() API to resolve the TContext type specified by the user.
  • The OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities<...> API has been preserved - including the overload accepting a single TKey parameter but no longer use options internally. Instead, they re-register the untyped IOpenIddict*Manager to point to the correct OpenIddict*Manager<T> instances depending on the generic types set by the user.
  • For consistency, the EF 6.x stores adopted the same pattern, tho' it's extremely unlikely EF 6.x will ever be AOT-compatible.

The changes introduced in the core are quite massive but shouldn't impact most users. Library or framework authors on the other hand (e.g ABP Framework, Orchard Core or Squidex) are much more likely to be affected. I'll work with these partners in the next few weeks to ensure these changes didn't break any scenario.

Note: I haven't tested the OpenIddict server and validation stacks in AOTed ASP.NET Core apps yet, but the results seen with the OpenIddict client and its system integration in a console application (with the token storage feature disabled, since EF Core 9 doesn't work well currently) are really promising: it's able to launch an interactive authorization flow in the blink of an eye, even on extremely modest hardware! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update the code base to support assembly trimming and NativeAOT

1 participant