Home > Enterprise >  c# Converting a type to a generic
c# Converting a type to a generic

Time:07-22

I'm trying to consolidate some code in an old .NET 4.0 project using UnityContainer for DI.

I have the following list of registrations, which are lazy loaded, with a constructor injection in a Unity registry:

public class MyRegistry : UnityRegistry
{
    public MyRegistry()
    {
        Register(GetDataAccessInjectionFactory<AddressProviderBase>());
        Register(GetDataAccessInjectionFactory<CustomerProviderBase>());
        // Hundreds of these registrations..... manually maintained... urrgh
        // I want to scan for them instead
    }
    
    private Func<IUnityContainer, T> GetDataAccessInjectionFactory<T>() where T : class
    {
        return c => DataAccessProvider<T>.GetDataAccess(c.Resolve<ITenantCodeProvider>());
    }
}

The DataAccessProvider is just a cached concurrent dictionary in order to switch database connection based on the tenant. It looks like this:

public class DataAccessProvider<T> where T : class
{
    public static T GetDataAccess(ITenantCodeProvider tenantCodeProvider)
    {
        // ... implementation
    }
}

I'm trying to replace them with a single UnityContainer scan using a custom convention:

Scan(scan =>
{
    scan.With<CustomProviderBaseConvention>();
    scan.AssemblyContaining<AddressProviderBase>();
});

However, the IAssemblyScannerConvention interface provides me with this:

public class CustomProviderBasesConvention : IAssemblyScannerConvention
{
    // required interface
    void IAssemblyScannerConvention.Process(Type type, IUnityRegistry registry)
    {
        // this "type" obviously won't work. Do I need reflection here?
        registry.Register(GetDataAccessInjectionFactory<type>());
    }

    public Func<IUnityContainer, T> GetDataAccessInjectionFactory<T>() where T : class
    {
        return c => DataAccessProvider<T>.GetDataAccess(c.Resolve<ITenantCodeProvider>());
    }
}

This is more of a generics / typing issue than something to do with Unity DI. Any pointers about how to replace the hundreds of manually added and maintained ProviderBase registrations would be most welcomed.

Even using reflection, I end up with the same problem, that I can't cast the result:

public class CustomProviderBasesConvention : IAssemblyScannerConvention
{
    void IAssemblyScannerConvention.Process(Type type, IUnityRegistry registry)
    {
        MethodInfo method = typeof(DataAccessProvider<>).GetMethod("GetDataAccess", BindingFlags.Public | BindingFlags.Instance);
        MethodInfo generic = method.MakeGenericMethod(type);
        // cast needs a type
        var factory = (Func<IUnityContainer, ????>)generic.Invoke(this, null);
        registry.Register(factory);
    }

    public Func<IUnityContainer, T> GetDataAccessInjectionFactory<T>() where T : class
    {
        return c => DataAccessProvider<T>.GetDataAccess(c.Resolve<ITenantCodeProvider>());
    }
}

I have the same problem if I loop through the assembly types:

var assembly = typeof(AddressProviderBase).Assembly;
var types = assembly.GetTypes().Where(x => x.FullName.EndsWith("ProviderBase")).ToList();
foreach(var t in types)
{
    MethodInfo method = typeof(DataAccessProvider<>).GetMethod("GetDataAccess", BindingFlags.Public | BindingFlags.Instance);
    MethodInfo generic = method.MakeGenericMethod(t);
    Register((Func<IUnityContainer, ??>)generic.Invoke(this, null));
}

CodePudding user response:

The Unity itself may have the better way to do this, but if we want to go in the reflection way, then we can do the following.

public class CustomProviderBasesConvention : IAssemblyScannerConvention
{
    void IAssemblyScannerConvention.Process(Type type, IUnityRegistry registry)
    {
        var method = typeof(CustomProviderBasesConvention).GetMethod(nameof(Helper),
                            BindingFlags.Instance | BindingFlags.NonPublic);
        var generic = method.MakeGenericMethod(type);
        generic.Invoke(this, new[] { registry });
    }
    
    private void Helper<T>(IUnityRegistry registry) where T : class
    {
        var factory = GetDataAccessInjectionFactory<T>();
        registry.Register(factory);

    }
    public Func<IUnityContainer, T> GetDataAccessInjectionFactory<T>() where T : class
    {
        return c => DataAccessProvider<T>.GetDataAccess(c.Resolve<ITenantCodeProvider>());
    }
}

The problem in the assemble scan code may be solved with exactly the same way with generic Helper function.

  • Related