5

I understand how I can register dependencies for ASP.NET core and how constructor injection works for my controllers (I have that working). Does everything have to get passed through constructors and then explicitly down to objects created by those constructed objects, or how do the objects that get instantiated, such as my controllers, and the objects that they create, use the service collection to access or instantiate additional objects?

Update 19.June.2020:

So let's say my Startup calls this (this is just sample code to see if I can get this question and hence my account unbanned):

    public static IServiceCollection AddRepository(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var serviceProvider = services.BuildServiceProvider(); //TODO: dispose
        ContentstackClient stack = serviceProvider.GetService<ContentstackClient>();
        Trace.Assert(stack != null, "configure ContentstackClient service before IRepository service");
        IConfigureSerialization configurator = serviceProvider.GetService<IConfigureSerialization>();
        Trace.Assert(configurator != null, "configure IConfigureSerialization before ContentstackRepository");
        configurator.ConfigureSerialization(stack.SerializerSettings);
        //TODO: Apply ContentstackRepositoryConfiguration (UseSync, etc.) from appsettings.json
        ContentstackRepositoryConfiguration repoConfig = ContentstackRepositoryConfiguration.Get(
            ContentstackRepositoryConfiguration.StartType.FastestStartSync, stack);
            services.AddSingleton<IRepository>(new ContentstackRepository(repoConfig));//, serviceProvider.GetService<ILogger>()));
//TODO: happens automatically?            serviceProvider.Dispose();
            return services;
        }

I have this in my controller:

    public EntryController(
        IRepository repository, 
        ILogger<EntryController> logger, 
        ContentstackClient stack)
    {
        _repository = repository;
        _logger = logger;
        _stack = stack;
    }

But what if code in my view or elsewhere wants to access the IRepository singleton? Do I have to pass the IRepository all over the place, or is there some way to access it explicitly through a service locator or something?

commodore73
  • 61
  • 2
  • 11
  • I know it's bad practice (better to use constructor injection than service locator) but I needed to ensure that it is possible. In the controller I can access the service provider as HttpContext.RequestServices.GetService(typeof(MyClass)).I can instantiate things or I can pass the service provider to other objects. – commodore73 May 23 '20 at 22:28

3 Answers3

3

I know asp.net core DI framework supports three types of component injection.
(1) Constructor
(2) Paramter invocation
(3) HttpContext.RequestServices

Note: HttpContext.RequestServices makes use of the service locator pattern. It's not a good pattern but if you lost access to the service and you can only get it through HttpContext.RequestServices, then it may be acceptable.
Here is a discussion about it.

I refactored your code to register a Repository service.

public static IServiceCollection AddRepository(
    this IServiceCollection services,
    IConfiguration configuration)
{
    // You can declare a singeton instance then declare IRepository is service and ContentstackRepository is your implementation class
    // When you use pass IRepository in the controller constructor, actually you get the ContentstackRepository instance.
    services.AddSingleton<IRepository, ContentstackRepository>(f => 
    {
        // Don't use this approach in the IServiceCollection, if you want register the instance.        
        // var serviceProvider = services.BuildServiceProvider();
    
        // You can use the 'f' lamda to get IServiceProvider and use GetService to get the instance.
        ContentstackClient stack = f.GetService<ContentstackClient>();
        Trace.Assert(stack != null, "configure ContentstackClient service before IRepository service");
    
        IConfigureSerialization configurator = f.GetService<IConfigureSerialization>();
        Trace.Assert(configurator != null, "configure IConfigureSerialization before ContentstackRepository");        
        configurator.ConfigureSerialization(stack.SerializerSettings);
    
        // return ContentstackRepository instance 
        ContentstackRepositoryConfiguration repoConfig = ContentstackRepositoryConfiguration.Get(ContentstackRepositoryConfiguration.StartType.FastestStartSync, stack);
        return new ContentstackRepository(repoConfig);  
    }); 
}   

If you want to use the IRepository singleton in another class, you need to pass it to the constructor.

If you want to use method or property injection, you can consider using Autofac that is useful di inject library.

AsPas
  • 359
  • 5
  • 22
Changemyminds
  • 1,147
  • 9
  • 25
  • Upvoting this answer as it explains it well. To the OP : Please don't change the good job .net-core has done for IoC...back to ServiceLocator. "Constructor Injection is not an anti-pattern. On the contrary, it is the most powerful DI pattern available, and you should think twice before deviating from it." https://blog.ploeh.dk/2010/01/20/RebuttalConstructorover-injectionanti-pattern/ – granadaCoder Jul 17 '20 at 17:41
  • Thank you for the explanation and additional suggestions. I upvoted as well, but intend to delay the bounty to see if any more answers come in. If there is no technique other than service locator, then I think passing objects around is the best and maybe only solution. Invoke parameter seems nice but complex and potentially out of scope. I have found other cases where I can use attributes instead of DI, which can have some advantages. Autofac is definitely out of scope. – commodore73 Jul 17 '20 at 19:05
  • This doesn't answer the question which asks how to access ServiceCollection explicitly? The answer is all about implicit access i.e. relies on the framework injecting the object. What I am looking for is some way of doing something like ServiceProvider.GetService(typeof(T)) on demand from any code especially a class library! – john blair Apr 25 '23 at 16:23
1

You have at least four options for using something like dependency injection in .NET Core:

Constructor Injection: This is some magic in the framework. I do not know how it works, but it works, at least when you do not instantiate objects explicitly. One great example is in a controller. All you need to do is implement the constructor in your controller to accept arguments, which the framework instantiates from the service provider. You can store these in the controller object and then pass them to anything deeper in the call stack. Be sure that all required services are registered before the framework instantiates the controller.

Third-Party Dependency Injection Frameworks: There are tons of these with various features and performance profiles, and for me, all of them are out of scope because they are not native to the framework itself and I don’t want to push my opinions on anyone that uses my code. By mentioning any, I would be almost certain to exclude some, and since I do not intend to use them, I will not list any. Which of these will survive the coming DI wars? Who knows. I want to work with .NET itself, not third-party extensions.

Reflection: You can use reflection to check the types in one or more assemblies looking for those that fulfill a contract or have an attribute defined. I consider this to be a form of implicit dependency injection: you never register implementations of services; you just drop your assemblies into the solution. Honestly it's my favorite way. There is some performance penalty in the reflection, so if you use the results more than once, be sure to cache them.

Per-Request Middleware Dependencies: This is just one of those mouthfuls that I want to spit out immediately due to the complexity of combined flavors and textures. To be honest, I do not really understand it or want to spend the time to figure it out. I want to use constructor injection and reflection and I do not want to introduce a third pattern anyway. It may be a better option for you than me.

The most important thing is to avoid using the service locator pattern.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#constructor-injection-behavior

https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/reflection

https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/attributes

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-2.2#per-request-middleware-dependencies

https://stackify.com/service-locator-pattern/

commodore73
  • 61
  • 2
  • 11
0

I think you have 2 options here:

  1. You can try to register implementation for IRepository using AddSingleton method in your Startup class.
services.AddSingleton<IRepository, [class_that_implements_IRepository]>();

In this case in order to get a reference to IRepository you have to inject it into constructor.

  1. The other way is to use a singleton implementation based on static class
    public interface IRepository
    {
        int DoSomething();
    }

    public class MyRepositoryImplementation : IRepository
    {
        public int DoSomething()
        {
            return 42;
        }
    }

    public static class MyRepositorySingleton
    {
        private static IRepository _repository = null;

        static MyRepositorySingleton()
        {
            _repository = new MyRepositoryImplementation();
        }

        public static IRepository GetRepository()
        {
            return _repository;
        }
    }

Vladimir
  • 149
  • 1
  • 6