Search code examples
c#asp.net-coredependency-injectionautofac.net-5

Registering dynamic generic repository with Autofac ASP.Net Core 5


I'm trying to create a wrapper repository that will be able to use a generic repository based on type.

So far I have:

Startup:

builder.RegisterAssemblyTypes(Assembly.Load("Data"))
               .Where(t => t.Namespace.Contains("Repositories"))
               .As(t => t.GetInterfaces().FirstOrDefault(i => i.Name == "I" + t.Name))
               .InstancePerLifetimeScope();

builder.RegisterType<GenericRepositoryLong<EventRegistrationActivity>>()
            .As<IGenericRepositoryLong<ILongEntity>>()
            .Keyed<IGenericRepositoryLong<ILongEntity>>(ActivityType.EventRegistration)
            .AsSelf()
            .InstancePerLifetimeScope();

builder.Register<Func<ActivityType, IGenericRepositoryLong<ILongEntity>>>(c =>
{
    var componentContext = c.Resolve<IComponentContext>();
    return (activityType) =>
    {
        var repo = componentContext.ResolveKeyed<IGenericRepositoryLong<ILongEntity>>(activityType);
        return repo;
    };
});

ActivityRepository is basically a wrapping repository that handles Cache data consistency. It should also use the generic repository to save the entity. The generic repository is working great when injected, but it does not work when I try the following:

public class ActivityRepository: IActivityRepository
{
    private readonly Func<ActivityType, IGenericRepositoryLong<ILongEntity>> ActivityRepoFac;

    public ActivityRepository(Func<ActivityType, IGenericRepositoryLong<ILongEntity>> activityRepoFac)
    {
        ActivityRepoFac = activityRepoFac;
    }

    public async Task CreateActivityAsync(IFeedActivity activity, ActivityType activityType)
    {
        var repo = ActivityRepoFac(activityType);
        ...repo code here
    }
}

ActivityType is a simple enum, and the error I'm getting is :

The type
'Infra.DataProvider.GenericRepositoryLong`1[Domain.Entities.Activities.EventRegistrationActivity]'
is not assignable to service
'Infra.DataProvider.IGenericRepositoryLong`1[[Domain.ILongEntity, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'.

Is what I'm trying to do possible, and if so have I missed something? Also, is this a good practice?

Would appreciate any help on the matter.


Solution

  • There isn't enough to go on here to decisively say what's up, but the exception is pretty telling.

    The registration here is the important part that you'll need to test, and not even with all the keyed and extra settings. Just this:

    builder.RegisterType<GenericRepositoryLong<EventRegistrationActivity>>()
           .As<IGenericRepositoryLong<ILongEntity>>();
    

    What the exception is saying is that you can't cast GenericRepositoryLong<EventRegistrationActivity> to IGenericRepositoryLong<ILongEntity>.

    Put another way, if you did this, it wouldn't compile:

    IGenericRepositoryLong<ILongEntity> repo =
      new GenericRepositoryLong<EventRegistrationActivity>();
    

    I don't actually see the definition of the GenericRepositoryLong<T> or IGenericRepositoryLong<T> entities so I can't say for sure, but that's what the exception is saying.

    Usually this is because the interface is declared like this:

    public interface IGenericRepositoryLong<T>
    

    and it's missing the proper variance keyword for what you're trying to do. I'm guessing you probably want

    public interface IGenericRepositoryLong<out T>
    

    I would try troubleshooting this in a unit test that doesn't involve Autofac at all. If you were going to manually create all the types and properties and wire them up yourself, does it work? Trying to figure it out whilst also trying to register things and line up Autofac syntax can lead to confusion, like "Is it Autofac doing this, or is it something in my code?"

    Finally, unrelated to the question, I did notice you're registering the GenericRepositoryLong<T> both AsSelf and Keyed, which seems weird. I mean, you may actually want to also be able to directly resolve GenericRepositoryLong<T> but given the setup you're showing and how it's very tightly coupled to the activity type, chances are you probably don't want that in there. It won't hurt anything to have it, but it can also lead to confusion if you accidentally resolve something you didn't intend to simply because you added too many services/registrations.