Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to .NET 6 with EF Core 6 #1122

Merged
merged 29 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d2e0d7b
Update to .NET 6 with EF Core 6
Dec 2, 2021
3d1c590
Adapt to changes in nullability annotations
Dec 2, 2021
03e321a
Adapt for breaking changes in PostgreSQL provider for EF Core 6
Dec 2, 2021
90bb92c
Cleanup tests for handling special characters
Dec 2, 2021
03c323f
Removed workaround for https://github.com/dotnet/efcore/issues/21026
Dec 2, 2021
9d8fb86
Removed workaround for https://github.com/dotnet/aspnetcore/issues/33394
Dec 2, 2021
e4eff9c
Removed workaround for https://github.com/dotnet/aspnetcore/issues/32097
Dec 2, 2021
d120a35
Removed workaround for https://github.com/dotnet/efcore/issues/21234
Dec 2, 2021
a61a051
Updated to latest Resharper version and removed workarounds for earli…
Dec 2, 2021
b28a464
Applied new Resharper suggestions
Dec 2, 2021
e7d4a49
Package updates
Dec 2, 2021
2403861
Renamed MSBuild variables
Dec 3, 2021
f623d00
Inlined MSBuild variables that are used only once
Dec 3, 2021
a1b7824
Removed .BeCloseTo, now that fakers truncate time to whole millisecon…
Dec 3, 2021
9140377
Narrow service scope lifetime
Dec 3, 2021
ea1247c
Enable registered services to dispose asynchronously, where possible
Dec 3, 2021
562bfd3
Workaround for bug in cleanupcode
Dec 4, 2021
44d7fef
Fixed detection of implicit many-to-many join entity in EF Core 6
Dec 6, 2021
13ae34b
Activate implicit usings
Dec 3, 2021
96db764
Switched to file-scoped namespaces
Dec 4, 2021
537c101
Reformat solution
Dec 4, 2021
0f020dc
Added [NoResource] to suppress startup warning
Dec 6, 2021
7d51dab
Use Minimal Hosting APIs
Dec 6, 2021
181c7e5
Removed duplicate code
Dec 6, 2021
33fd3a2
Corrected terminology for generic type usage
Dec 6, 2021
ea995db
Fixed warning: Type 'KnownResource' does not contain any attributes
Dec 6, 2021
c79da9e
Updated roadmap and version table
Dec 7, 2021
85908ef
Fixed: Override IIdentifiable.Id with custom capabilities no longer w…
Dec 7, 2021
f4b4bff
Review feedback
Dec 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ public void ConfigureServiceContainer(ICollection<Type> dbContextTypes)

foreach (Type dbContextType in dbContextTypes)
{
Type dbContextResolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType);
_services.AddScoped(typeof(IDbContextResolver), dbContextResolverType);
Type dbContextResolverClosedType = typeof(DbContextResolver<>).MakeGenericType(dbContextType);
_services.AddScoped(typeof(IDbContextResolver), dbContextResolverClosedType);
}

_services.AddScoped<IOperationsTransactionFactory, EntityFrameworkCoreTransactionFactory>();
Expand Down Expand Up @@ -182,29 +182,29 @@ private void AddMiddlewareLayer()

private void AddResourceLayer()
{
RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ResourceDefinitionInterfaces, typeof(JsonApiResourceDefinition<,>));
RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ResourceDefinitionOpenInterfaces, typeof(JsonApiResourceDefinition<,>));
maurei marked this conversation as resolved.
Show resolved Hide resolved

_services.AddScoped<IResourceDefinitionAccessor, ResourceDefinitionAccessor>();
_services.AddScoped<IResourceFactory, ResourceFactory>();
}

private void AddRepositoryLayer()
{
RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.RepositoryInterfaces, typeof(EntityFrameworkCoreRepository<,>));
RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.RepositoryOpenInterfaces, typeof(EntityFrameworkCoreRepository<,>));
maurei marked this conversation as resolved.
Show resolved Hide resolved

_services.AddScoped<IResourceRepositoryAccessor, ResourceRepositoryAccessor>();
}

private void AddServiceLayer()
{
RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ServiceInterfaces, typeof(JsonApiResourceService<,>));
RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ServiceOpenInterfaces, typeof(JsonApiResourceService<,>));
maurei marked this conversation as resolved.
Show resolved Hide resolved
}

private void RegisterImplementationForOpenInterfaces(HashSet<Type> openGenericInterfaces, Type implementationType)
private void RegisterImplementationForOpenInterfaces(HashSet<Type> openInterfaces, Type implementationType)
maurei marked this conversation as resolved.
Show resolved Hide resolved
{
foreach (Type openGenericInterface in openGenericInterfaces)
foreach (Type openInterface in openInterfaces)
{
_services.TryAddScoped(openGenericInterface, implementationType);
_services.TryAddScoped(openInterface, implementationType);
}
}

Expand Down
20 changes: 10 additions & 10 deletions src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static IServiceCollection AddResourceService<TService>(this IServiceColle
{
ArgumentGuard.NotNull(services, nameof(services));

RegisterForConstructedType(services, typeof(TService), ServiceDiscoveryFacade.ServiceInterfaces);
RegisterTypeForOpenInterfaces(services, typeof(TService), ServiceDiscoveryFacade.ServiceOpenInterfaces);

return services;
}
Expand All @@ -72,7 +72,7 @@ public static IServiceCollection AddResourceRepository<TRepository>(this IServic
{
ArgumentGuard.NotNull(services, nameof(services));

RegisterForConstructedType(services, typeof(TRepository), ServiceDiscoveryFacade.RepositoryInterfaces);
RegisterTypeForOpenInterfaces(services, typeof(TRepository), ServiceDiscoveryFacade.RepositoryOpenInterfaces);

return services;
}
Expand All @@ -85,25 +85,25 @@ public static IServiceCollection AddResourceDefinition<TResourceDefinition>(this
{
ArgumentGuard.NotNull(services, nameof(services));

RegisterForConstructedType(services, typeof(TResourceDefinition), ServiceDiscoveryFacade.ResourceDefinitionInterfaces);
RegisterTypeForOpenInterfaces(services, typeof(TResourceDefinition), ServiceDiscoveryFacade.ResourceDefinitionOpenInterfaces);

return services;
}

private static void RegisterForConstructedType(IServiceCollection services, Type implementationType, IEnumerable<Type> openGenericInterfaces)
private static void RegisterTypeForOpenInterfaces(IServiceCollection serviceCollection, Type implementationType, IEnumerable<Type> openInterfaces)
maurei marked this conversation as resolved.
Show resolved Hide resolved
{
bool seenCompatibleInterface = false;
ResourceDescriptor? resourceDescriptor = ResolveResourceTypeFromServiceImplementation(implementationType);

if (resourceDescriptor != null)
{
foreach (Type openGenericInterface in openGenericInterfaces)
foreach (Type openInterface in openInterfaces)
{
Type constructedType = openGenericInterface.MakeGenericType(resourceDescriptor.ResourceClrType, resourceDescriptor.IdClrType);
Type closedInterface = openInterface.MakeGenericType(resourceDescriptor.ResourceClrType, resourceDescriptor.IdClrType);

if (constructedType.IsAssignableFrom(implementationType))
if (closedInterface.IsAssignableFrom(implementationType))
{
services.AddScoped(constructedType, implementationType);
serviceCollection.AddScoped(closedInterface, implementationType);
seenCompatibleInterface = true;
}
}
Expand All @@ -121,8 +121,8 @@ private static void RegisterForConstructedType(IServiceCollection services, Type
{
foreach (Type @interface in serviceType.GetInterfaces())
{
Type? firstGenericArgument = @interface.IsGenericType ? @interface.GenericTypeArguments.First() : null;
ResourceDescriptor? resourceDescriptor = TypeLocator.ResolveResourceDescriptor(firstGenericArgument);
Type? firstTypeArgument = @interface.IsGenericType ? @interface.GenericTypeArguments.First() : null;
ResourceDescriptor? resourceDescriptor = TypeLocator.ResolveResourceDescriptor(firstTypeArgument);

if (resourceDescriptor != null)
{
Expand Down
24 changes: 12 additions & 12 deletions src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace JsonApiDotNetCore.Configuration;
[PublicAPI]
public sealed class ServiceDiscoveryFacade
{
internal static readonly HashSet<Type> ServiceInterfaces = new()
internal static readonly HashSet<Type> ServiceOpenInterfaces = new()
maurei marked this conversation as resolved.
Show resolved Hide resolved
{
typeof(IResourceService<,>),
typeof(IResourceCommandService<,>),
Expand All @@ -32,14 +32,14 @@ public sealed class ServiceDiscoveryFacade
typeof(IRemoveFromRelationshipService<,>)
};

internal static readonly HashSet<Type> RepositoryInterfaces = new()
internal static readonly HashSet<Type> RepositoryOpenInterfaces = new()
maurei marked this conversation as resolved.
Show resolved Hide resolved
{
typeof(IResourceRepository<,>),
typeof(IResourceWriteRepository<,>),
typeof(IResourceReadRepository<,>)
};

internal static readonly HashSet<Type> ResourceDefinitionInterfaces = new()
internal static readonly HashSet<Type> ResourceDefinitionOpenInterfaces = new()
maurei marked this conversation as resolved.
Show resolved Hide resolved
{
typeof(IResourceDefinition<,>)
};
Expand Down Expand Up @@ -118,8 +118,8 @@ private void AddDbContextResolvers(Assembly assembly)

foreach (Type dbContextType in dbContextTypes)
{
Type dbContextResolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType);
_services.AddScoped(typeof(IDbContextResolver), dbContextResolverType);
Type dbContextResolverClosedType = typeof(DbContextResolver<>).MakeGenericType(dbContextType);
_services.AddScoped(typeof(IDbContextResolver), dbContextResolverClosedType);
}
}

Expand All @@ -130,40 +130,40 @@ private void AddResource(ResourceDescriptor resourceDescriptor)

private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor)
{
foreach (Type serviceInterface in ServiceInterfaces)
foreach (Type serviceInterface in ServiceOpenInterfaces)
{
RegisterImplementations(assembly, serviceInterface, resourceDescriptor);
}
}

private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor)
{
foreach (Type repositoryInterface in RepositoryInterfaces)
foreach (Type repositoryInterface in RepositoryOpenInterfaces)
{
RegisterImplementations(assembly, repositoryInterface, resourceDescriptor);
}
}

private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resourceDescriptor)
{
foreach (Type resourceDefinitionInterface in ResourceDefinitionInterfaces)
foreach (Type resourceDefinitionInterface in ResourceDefinitionOpenInterfaces)
{
RegisterImplementations(assembly, resourceDefinitionInterface, resourceDescriptor);
}
}

private void RegisterImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
{
Type[] genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2
Type[] typeArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2
? ArrayFactory.Create(resourceDescriptor.ResourceClrType, resourceDescriptor.IdClrType)
: ArrayFactory.Create(resourceDescriptor.ResourceClrType);

(Type implementation, Type registrationInterface)? result = _typeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments);
(Type implementationType, Type serviceInterface)? result = _typeLocator.GetContainerRegistrationFromAssembly(assembly, interfaceType, typeArguments);

if (result != null)
{
(Type implementation, Type registrationInterface) = result.Value;
_services.AddScoped(registrationInterface, implementation);
(Type implementationType, Type serviceInterface) = result.Value;
_services.AddScoped(serviceInterface, implementationType);
}
}
}
98 changes: 53 additions & 45 deletions src/JsonApiDotNetCore/Configuration/TypeLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ namespace JsonApiDotNetCore.Configuration;
/// </summary>
internal sealed class TypeLocator
{
// As a reminder, the following terminology is used for generic types:
// non-generic string
// generic
// unbound Dictionary<,>
// constructed
// open Dictionary<TKey,TValue>
// closed Dictionary<string,int>

/// <summary>
/// Attempts to lookup the ID type of the specified resource type. Returns <c>null</c> if it does not implement <see cref="IIdentifiable{TId}" />.
/// </summary>
public Type? LookupIdType(Type? resourceClrType)
{
Type? identifiableInterface = resourceClrType?.GetInterfaces().FirstOrDefault(@interface =>
Type? identifiableClosedInterface = resourceClrType?.GetInterfaces().FirstOrDefault(@interface =>
@interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IIdentifiable<>));

return identifiableInterface?.GetGenericArguments()[0];
return identifiableClosedInterface?.GetGenericArguments()[0];
}

/// <summary>
Expand All @@ -38,62 +46,62 @@ internal sealed class TypeLocator
}

/// <summary>
/// Gets all implementations of a generic interface.
/// Gets the implementation type with service interface (to be registered in the IoC container) for the specified open interface and its type arguments,
/// by scanning for types in the specified assembly that match the signature.
/// </summary>
/// <param name="assembly">
/// The assembly to search in.
/// The assembly to search for matching types.
/// </param>
/// <param name="openGenericInterface">
/// The open generic interface.
/// <param name="openInterface">
maurei marked this conversation as resolved.
Show resolved Hide resolved
/// The open generic interface to match against.
/// </param>
/// <param name="interfaceGenericTypeArguments">
/// Generic type parameters to construct the generic interface.
/// <param name="interfaceTypeArguments">
/// Generic type arguments to construct <paramref name="openInterface" />.
/// </param>
/// <example>
/// <code><![CDATA[
/// GetGenericInterfaceImplementation(assembly, typeof(IResourceService<,>), typeof(Article), typeof(Guid));
/// GetContainerRegistrationFromAssembly(assembly, typeof(IResourceService<,>), typeof(Article), typeof(Guid));
/// ]]></code>
/// </example>
public (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface,
params Type[] interfaceGenericTypeArguments)
public (Type implementationType, Type serviceInterface)? GetContainerRegistrationFromAssembly(Assembly assembly, Type openInterface,
params Type[] interfaceTypeArguments)
{
ArgumentGuard.NotNull(assembly, nameof(assembly));
ArgumentGuard.NotNull(openGenericInterface, nameof(openGenericInterface));
ArgumentGuard.NotNull(interfaceGenericTypeArguments, nameof(interfaceGenericTypeArguments));
ArgumentGuard.NotNull(openInterface, nameof(openInterface));
ArgumentGuard.NotNull(interfaceTypeArguments, nameof(interfaceTypeArguments));

if (!openGenericInterface.IsInterface || !openGenericInterface.IsGenericType || openGenericInterface != openGenericInterface.GetGenericTypeDefinition())
if (!openInterface.IsInterface || !openInterface.IsGenericType || openInterface != openInterface.GetGenericTypeDefinition())
{
throw new ArgumentException($"Specified type '{openGenericInterface.FullName}' is not an open generic interface.", nameof(openGenericInterface));
throw new ArgumentException($"Specified type '{openInterface.FullName}' is not an open generic interface.", nameof(openInterface));
}

if (interfaceGenericTypeArguments.Length != openGenericInterface.GetGenericArguments().Length)
if (interfaceTypeArguments.Length != openInterface.GetGenericArguments().Length)
{
throw new ArgumentException(
$"Interface '{openGenericInterface.FullName}' requires {openGenericInterface.GetGenericArguments().Length} type parameters " +
$"instead of {interfaceGenericTypeArguments.Length}.", nameof(interfaceGenericTypeArguments));
$"Interface '{openInterface.FullName}' requires {openInterface.GetGenericArguments().Length} type arguments " +
$"instead of {interfaceTypeArguments.Length}.", nameof(interfaceTypeArguments));
}

return assembly.GetTypes().Select(type => FindGenericInterfaceImplementationForType(type, openGenericInterface, interfaceGenericTypeArguments))
return assembly.GetTypes().Select(type => GetContainerRegistrationFromType(type, openInterface, interfaceTypeArguments))
.FirstOrDefault(result => result != null);
}

private static (Type implementation, Type registrationInterface)? FindGenericInterfaceImplementationForType(Type nextType, Type openGenericInterface,
Type[] interfaceGenericTypeArguments)
private static (Type implementationType, Type serviceInterface)? GetContainerRegistrationFromType(Type nextType, Type openInterface,
Type[] interfaceTypeArguments)
{
if (!nextType.IsNested)
{
foreach (Type nextGenericInterface in nextType.GetInterfaces().Where(type => type.IsGenericType))
foreach (Type nextConstructedInterface in nextType.GetInterfaces().Where(type => type.IsGenericType))
{
Type nextOpenGenericInterface = nextGenericInterface.GetGenericTypeDefinition();
Type nextOpenInterface = nextConstructedInterface.GetGenericTypeDefinition();

if (nextOpenGenericInterface == openGenericInterface)
if (nextOpenInterface == openInterface)
{
Type[] nextGenericArguments = nextGenericInterface.GetGenericArguments();
Type[] nextTypeArguments = nextConstructedInterface.GetGenericArguments();

if (nextGenericArguments.Length == interfaceGenericTypeArguments.Length &&
nextGenericArguments.SequenceEqual(interfaceGenericTypeArguments))
if (nextTypeArguments.Length == interfaceTypeArguments.Length && nextTypeArguments.SequenceEqual(interfaceTypeArguments))
{
return (nextType, nextOpenGenericInterface.MakeGenericType(interfaceGenericTypeArguments));
return (nextType, nextOpenInterface.MakeGenericType(interfaceTypeArguments));
}
}
}
Expand All @@ -103,30 +111,30 @@ private static (Type implementation, Type registrationInterface)? FindGenericInt
}

/// <summary>
/// Gets all derivatives of the concrete, generic type.
/// Scans for types in the specified assembly that derive from the specified open type.
/// </summary>
/// <param name="assembly">
/// The assembly to search.
/// The assembly to search for derived types.
/// </param>
/// <param name="openGenericType">
/// The open generic type, e.g. `typeof(ResourceDefinition&lt;&gt;)`.
/// <param name="openType">
maurei marked this conversation as resolved.
Show resolved Hide resolved
/// The open generic interface to match against.
/// </param>
/// <param name="genericArguments">
/// Parameters to the generic type.
/// <param name="typeArguments">
/// Generic type arguments to construct <paramref name="openType" />.
/// </param>
/// <example>
/// <code><![CDATA[
/// GetDerivedGenericTypes(assembly, typeof(ResourceDefinition<>), typeof(Article))
/// GetDerivedTypesForOpenType(assembly, typeof(ResourceDefinition<,>), typeof(Article), typeof(int))
/// ]]></code>
/// </example>
public IReadOnlyCollection<Type> GetDerivedGenericTypes(Assembly assembly, Type openGenericType, params Type[] genericArguments)
public IReadOnlyCollection<Type> GetDerivedTypesForOpenType(Assembly assembly, Type openType, params Type[] typeArguments)
{
ArgumentGuard.NotNull(assembly, nameof(assembly));
ArgumentGuard.NotNull(openGenericType, nameof(openGenericType));
ArgumentGuard.NotNull(genericArguments, nameof(genericArguments));
ArgumentGuard.NotNull(openType, nameof(openType));
ArgumentGuard.NotNull(typeArguments, nameof(typeArguments));

Type genericType = openGenericType.MakeGenericType(genericArguments);
return GetDerivedTypes(assembly, genericType).ToArray();
Type closedType = openType.MakeGenericType(typeArguments);
return GetDerivedTypes(assembly, closedType).ToArray();
}

/// <summary>
Expand All @@ -135,22 +143,22 @@ public IReadOnlyCollection<Type> GetDerivedGenericTypes(Assembly assembly, Type
/// <param name="assembly">
/// The assembly to search.
/// </param>
/// <param name="inheritedType">
/// <param name="baseType">
/// The inherited type.
/// </param>
/// <example>
/// <code>
/// GetDerivedGenericTypes(assembly, typeof(DbContext))
/// GetDerivedTypes(assembly, typeof(DbContext))
/// </code>
/// </example>
public IEnumerable<Type> GetDerivedTypes(Assembly assembly, Type inheritedType)
public IEnumerable<Type> GetDerivedTypes(Assembly assembly, Type baseType)
{
ArgumentGuard.NotNull(assembly, nameof(assembly));
ArgumentGuard.NotNull(inheritedType, nameof(inheritedType));
ArgumentGuard.NotNull(baseType, nameof(baseType));

foreach (Type type in assembly.GetTypes())
{
if (inheritedType.IsAssignableFrom(type))
if (baseType.IsAssignableFrom(type))
{
yield return type;
}
Expand Down
Loading