I'm working on a .NET Core Project. I need to automatically registers a default for some raw generics. When there parameter count is the same everything works fine.
public void RegisterServiceDefaults(IServiceCollection services)
{
services.AddSingleton(typeof(IAdaptable<,>), typeof(DynamicAdapter<,>));
}
My IAdaptable<TEntity, TDTO>
works to allow dynamic adaptation between entities, but in my concrete services by default we expect an interface so we can control how each property is adapted to, my IAdaptable<TEntity, TDTO>
is really just a wrapper for convenience on this interface
IAdaptable<TEntity, TIDTO, TDTO>
where TDTO: TIDTO
{
}
IAdaptable<TEntity, TDTO> : IAdaptable<TEntity, TDTO, TDTO>
{
}
How can I generically register my adaptables so that if someone requests an IAdaptable<TEntity, TDTO, TDTO>
it will by default return the IAdaptable<TEntity, TDTO>
?
EDIT Provided for understanding the problem TL;DR
This is being used for a rest framework which provides two ways of registration, the end user can create an IAdaptable<TEntity, TIDTO, TDTO>
a sample adapter might looks like so:
public UserAdapter : IAdaptable<User, IUserDTO, UserDTO>, IUserDTO
{
User _user;
public UserAdapter(User user)
{
this._user = user;
}
public int Id
{
get => this._user.Id;
set { /*Do nothing you cannot override the server Id*/}
}
//... The rest of the User Properties
}
When a the end user creates a service they can use an IAdaptable<TEntity, TIDTO, TDTO>
or if they do not want to create a concrete one they can pass some options to the dynamic one which is only aware of the TEntity
, and TDTO
property, no reason to create an TIDTO
when it's only used for adapting.
Now when the end user creates a service they can rely on either interface, the GenericBase service by default requests an IAdaptable like so:
public class RestService<TEntity, TIDTO, TDTO>
where TDTO: TIDTO
{
public RestService(DbContext context, IAdaptable<TEntity, TIDTO, TDTO> adapter, IValidator<TEntity> validator)
{
}
}
and for a simplified version:
public class RestService<TEntity, TDTO> : RestService<TEntity, TDTO, TDTO>
{
//Implementation...
}
Now generally speaking, if the end user wants a dynamic adapter they should be using the simplified RestService
, but depending on their architecture they maybe implement interfaces on the DTOs or maybe they just used the more explicit base and decide to use the RestService<TEntity, TIDTO, TDTO>
, In which case will leave them with a nasty runtime error. So to avoid having my support team handle the end user error, I would like to avoid the situation and have the IAdaptable<TEntity,TIDTO, TDTO>
work with my dynamic adapter which is a IAdaptable<TEntity,TDTO>
.
You can create your own scanning implementation using Reflection to register multiple types with a single line.
Here we implement a simple Scan
extension method that allows you to pick an an interface (whether generic or non-generic) and all implementations of that interface will be registered for all of the provided assemblies.
public static class ServiceCollectionExtensions
{
public static IServiceCollection Scan(
this IServiceCollection serviceCollection,
Assembly assembly,
Type serviceType,
ServiceLifetime lifetime)
{
return Scan(serviceCollection, new Assembly[] { assembly }, serviceType, lifetime);
}
public static IServiceCollection Scan(
this IServiceCollection serviceCollection,
IEnumerable<Assembly> assemblies,
Type interfaceType,
ServiceLifetime lifetime)
{
foreach (var type in assemblies.SelectMany(x =>
x.GetTypes().Where(t => t.IsClass && !t.IsAbstract)))
{
foreach (var i in type.GetInterfaces())
{
// Check for generic
if (i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType)
{
var genericInterfaceType = interfaceType.MakeGenericType(i.GetGenericArguments());
serviceCollection.Add(new ServiceDescriptor(genericInterfaceType, type, lifetime));
}
// Check for non-generic
else if (!i.IsGenericType && i == interfaceType)
{
serviceCollection.Add(new ServiceDescriptor(interfaceType, type, lifetime));
}
}
}
return serviceCollection;
}
// TODO: Add overloads ScanTransient, ScanSingleton, etc that call the
// above with the corresponding lifetime argument
}
To register all of the types that close the generic IAdaptable<,,>
type and register them as singleton, you would then simply need to use:
services.Scan(
assembly: typeof(User).Assembly,
interfaceType: typeof(IAdaptable<,,>),
lifetime: ServiceLifetime.Singleton);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With