Sometimes we need to resolve a dependency but not with one implementation, but with multiple. There are couple of scenarios that calls for conditional resolving of types. Such as when using a generic class and methods with a dependency on another class. Now imagine the aforementioned class is an abstract one, and we need to pass various concrete implementations. Another scenario is when we need to get an instance for an interface or abstract class, but we need to do it based on some criteria. As of the time of this writing, the built-in Asp.Net Core DI does not accommodate that. So in this post I’m going to explain what my problem was and how I solved it. We’re also going to see how to solve the problem we need to resolve dependency based on conditions.
Problem: Multiple Implementation For the Same Abstract Type
The problem that I’ve encountered with was I had a generic service that worked with multiple context. The details of each context didn’t matter, it was only the generic db context methods that mattered. Let me clarify by showing you an example.
As you can see, this class has a dependency on
DbContext and uses it as you can see in
GetRequest method. Now the problem is I want to use this method with multiple db context and not just one. Imagine in one controller I want to use this with
SchoolDbContext in another controller I want to use it with
HomeDbContext. So now what should I register in my DI container? Maybe something like this?
Services.AddScoped<DbContext, HomeDbContext >();
But wait, now when we saw a constructor with
DbContext in it, which one of these concrete contexts should we get?
HomeDbContext? We need some mechanism in our controller to specify which one of these we need. I’m going to show you two way to solve this. First way is more elegant and uses generic. But we need access to the underlying class because we need to make it generic. So it cannot be used for all scenarios. In second approach access to underlying class is not needed, we basically solve it with inheritance. But I’m not very happy with the second approach.
Solution: Multiple Implementation For the Same Type With Generics
In this approach, we define the generic type argument for our class and restrict the argument type to DbContext, like this.
As you can see we pass the
TContext in our constructor instead of
DbContext. The good thing in this approach is the change in our class in minimal. We only changed the type argument of our class. Now we can simply register it in our container like this.
Finally we can use it in our controller like this.
Don’t forget to register the actual different contexts too.
This approach I believe is better than the next one that I’m going to show you. But the downside of this is we don’t always have access to the underlying class.
Solution: Multiple Implementation For the Same Type Without Generics
Another solution is to inherit form the
HttpRequestServiceNonEntity class and pass in the desired db context.
Now we can simply register the
HttpRequestServiceNonEntityHome in our container and use it, like so.
Here we are using a concrete class, but I think it’s not important. Because it only serve as a wrapper to meet our architectural needs.
Problem: Conditional Resolving of Types
Imagine we have a controller that needs to get some kind of service based on some condition. In this case we want to get an implementation for
IGrantService. But we need to give different implementation for when user is not authorized.
Solution: Conditional Resolving Without Third Party Container
In these scenario we can add a delegate to our container that receives a bool indicating that user is authorized or not and return a service. Something like the code below.
As you can see we switch on
isAuthorized argument and return different services based on whether or not the user was authorized. Now we can use it like this in our controller.
The important thing in the controller is to pass the bool value to our delegate which we’re doing by
Solution: Conditional Resolving With Third Party Container
Solving this problem with a third party container is really easy. In this instance I’m going to use Autofac, but all full DI containers have this feature. Frankly all of this headache can be avoided and this solution is the preferred way of doing this. I start by defining a enum for the type of services that I want to resolve. We can use string too, but magic string is bad, mmmkay?
After that we register the services like so.
Finally use it like so.
That’s it! We basically achieved the same thing with only couple of lines as oppose to the previous semi-convoluted solution.
In this post I explained what we can do if we need to receive multiple implementation for the same interface. I’ve discussed how to solve this problem with generics, but changing the implementation of the underlying class. I also showed how we can do it without changing the underlying class and without generics. We also saw how we can receive different instance based on condition both with and without using a DI framework.