I’m migrating an old ASP.NET 4.7.2 MVC application to ASP.NET Core and wish to learn more of the DI approach, since it’s already deeply integrated into the Core framework. However, so far I’ve encountered some issues utilizing it. I understand DI is supposed to be used across the call chain, with every class having its dependencies injected. But what if you want to use an instance of a class only in a specific method of your current one? Are you supposed to inject it for the whole class, wasting performance on potentially heavy instantiations when it’s not necessary?
Here’s an example;
public class TestClass(ILogger<TestClass> logger)
{
public void DoSomething()
{
// Do work here
}
public void DoAnotherThing()
{
// Do work here
}
private void CallSomethingFromInnerClass()
{
// Problematic code
new InnerClass().DoInnerClassWork();
}
}
public class InnerClass(ILogger<InnerClass> logger)
{
public void DoInnerClassWork()
{
// Do work here
}
}
I have a class that has many methods, each doing its own thing. Assume that class cannot be split into different smaller classes, maybe it’s a controller. The inner class expects a logger with its own category. Now, I could instantiate it in the method and pass the existing logger, but that logger would have the wrong category (TestClass
instead of InnerClass
). I could also inject an IServiceProvider
, however as I understand that is an anti-pattern and shouldn’t be done when implementing DI. I could also introduce a [FromService]
attribute, however that is also an anti-pattern, plus it only works for controller actions called from the environment, not private methods.
Another approach would be to indeed separate the method to a different object (see this). However, doesn’t that just kick the can down the road? If you would need to then inject that object to call the method, doesn’t the injection still happen for the whole TestClass?
What is the best approach here? Is the answer really injecting it for the whole class? What if the inner class is an ORM helper, do I need to create a DB connection every time?
3
Answers
Lazy initialization is used in this kind of situations and it can be applied on DI aswell see => Lazy initialization
According to your scenario, If you want to use an instance of a class only in a specific method, you don’t necessarily need to inject it for the entire class.
While injecting dependencies for the entire class is the most straightforward and maintainable approach, using factory methods, lazy initialization, or method-level injection can provide performance benefits without violating DI principle.
Both has pros and corns actually. So, as I told you earlier, its a programmer’s call or preference.
When injecting a dependency into a class in ASP.NET Core, the injection happens at the class level, meaning the dependency is available to all methods within that class. However, this does not necessarily mean that the dependency is instantiated immediately or that it has a heavy performance impact.
As it has couple of approach you could decide depending on which one actually best fits within your scenario.
If you prefer, Lazy initialization, it defers the creation of the instance until it is first accessed. In that scenario, you can do something like below:
In addition, For ORM helpers like DbContext, Entity Framework Core manages connections efficiently through connection pooling. Injecting DbContext into your services is typically efficient because it does not create a new database connection each time but reuses existing connections from the pool.
Yeah one thing is obvious, lazy initialization, or scoped/transient services can provide a balance between efficiency and adherence to dependency injection principles.
Note: If you have time, I would recommned you to check this official document.
Injection constructors should be simple and any heavy initialization should be postponed until after the object graph has been created. This make the construction of big object graphs very lightweight and fast, in which case you typically don’t have to worry about wasting performance.
Postponing initialization means, for instance, not connecting to the database inside the constructor, but instead when the class is first used. O/RMs like Entity Framework, for instance, use this approach. Only when any of the
DbSet
properties are called, will connections to the database be made. Injecting an unusedDbContext
is fairly lightweight.Still, object graphs consisting of thousands of instances could still cause performance problems. My experience, however, is that very large object graphs only exist with object graphs where its classes apply the Constructor Over-Injection code smell. In other words, classes that violate the Single Responsibility Principle.
And besides reducing the number of dependencies a class has, a typically quicker fix (or workaround) is to increase the lifetime of dependencies. For instance from Transient to Scoped. This caches the component with all its created dependencies and can severely reduce the number of created objects in the object graph.
If methods have a low cohesion, this is an indication of a violation of the Single Responsibility Principle.
Even controllers can be split up into, and arguably should be when their number of constructor arguments grows. Some care must be taken, though, to ensure the application’s API URL paths should stay the same to prevent breaking clients.