aspnet / DependencyInjection

[Archived] Contains common DI abstractions that ASP.NET Core and Entity Framework Core use. Project moved to https://github.com/aspnet/Extensions

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

What is the context of services.AddTransient ?

SherifRefaat opened this issue · comments

Hi, I have the following problem

    public void ConfigureServices(IServiceCollection services)
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(x => x.GetTypes())
            .ToArray();

        RegisterRepositories(ref services, assemblies);
    }

    private void RegisterRepositories(ref IServiceCollection services, Type[] assemblies)
    {
        var interfaceT = typeof(IRepository);
        foreach (var t in assemblies.Where(x => interfaceT.IsAssignableFrom(x) && x != interfaceT))
        {
            services.AddTransient(t);
        }
    }

As you can see I am adding my classes that implements from IRepository (marker/empty interface). But the problem is that they don't get registered! I get the following message:

InvalidOperationException: Unable to resolve service for type 'MyProject.Core.Repositories.MenuRepository' while attempting to activate 'MyProject.Controllers.MenuController'.

Which is the my my controller that request a certain repository in its constructor.

But if I changed the code to the following, the DI is able to resolve the instance and activate the controller

   public void ConfigureServices(IServiceCollection services)
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(x => x.GetTypes())
            .ToArray();

        // Register Repositories
        var interfaceT = typeof(IRepository);
        foreach (var t in assemblies.Where(x => interfaceT.IsAssignableFrom(x) && x != interfaceT))
        {
            services.AddTransient(t);
        }
    }

I don't see anything obviously wrong in the code you've shown. But I do have a few comments/questions:

  1. The ref keyword isn't needed because the services parameter is never set
  2. What does the MenuController constructor look like?
  3. What does the MenuRepository type definition look like?

@SherifRefaat did you debug the startup code? There are no loaded repository types in the application so nothing gets added to the container.

Assemblies aren't all loaded on startup, they are loaded as they are used. That's likely your problem.

edit: Take a look at

var assemblies = AppDomain.CurrentDomain.GetAssemblies();

You'll see that MyProject.Core isn't in there.

@davidfowl It seems that when the GetAssemblies() is called the IRepository was still not referenced hence MyProject.Core was not loaded. Am I correct?

I changed to the following and it is working

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            var projectTypes = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(x => x.GetTypes())
                .ToList();

            RegisterFromBaseType(services, typeof(IRepository), projectTypes);
        }

        private void RegisterFromBaseType(IServiceCollection services, Type type, List<Type> allTypes)
        {
            foreach (var t in allTypes.Where(x => type.IsAssignableFrom(x) && x != type))
            {
                services.AddTransient(t);
            }
        }

Also, @davidfowl would this have produced different result on Mono because its runtime has multiple Execution Modes (i.e. interpreter mode)?

@SherifRefaat possibly, relying on loaded assemblies is a mistake specifically because of the non deterministic behavior you're experiencing.

When you do typeof(IRepository) in ConfigureServices, it will be loaded as part of the method being JITed.