C#

Lazily Resolving Services To Fix Circular Dependencies In .NET Core

In this blog, I’m talking about a common problem with circular dependencies. We recognize this problem in the development phase.

When building a .NET application, good design dictates that you should avoid circular dependencies between your services. A circular dependency is when some components depend on each other, directly or indirectly, e.g. A depends on B which depends on C which depends on A:

 

 

It is generally agreed that this should be avoided; I won’t go into the details of the conceptual and theoretical reasons, because there are plenty of resources about it on the web.

But circular dependencies also have a very concrete effect. If you accidentally introduce a circular dependency in a .NET Core app that uses dependency injection, you will know immediately, because the resolution of a component involved in the dependency cycle will fail. For instance, if you have these components:

  • A, which implements an interface IA and depends on IB.
  • B, which implements an interface IB and depends on IC.
  • C, which implements an interface IC and depends on IA.

When you try to resolve IA, the dependency injection container will try to create an instance of A; to do that, it will need to resolve IB, so it will try to create an instance of B; to do that, it will need to resolve IC, so it will try to create an instance of C; and to do that, it will need to resolve IA… which was already being resolved. Here you have it: circular dependency. The resolution of IA will cannot be complete; in fact, .NET Core’s built-in IoC container detects this and throws a helpful exception:

System.InvalidOperationException: A circular dependency was detected for the service of type ‘Demo.IA’.

So, clearly, this situation should be avoided.

Let’s Start,

However, when a real-world app reaches a certain level of complexity, it can sometimes be difficult to avoid. One day, you innocently add a dependency to service, and things blow up in your face. So you’re faced with a choice: refactor a significant part of your app to avoid the dependency cycle, or “cheat”.

While, ideally, you would opt for refactoring, it’s not always practical. Because of deadlines, you might not have the time to refactor your code and thoroughly test it for regressions.

One way to do that is to inject the IServiceProvider into your class, and use services.GetRequiredService<T>() when you need to use T. For instance, the  C class I mentioned earlier might initially look like this:

class C: IC
{
    private readonly IA _a;

    public C(IA a)
    {
        _a = a;
    }

    public void Bar()
    {
        //...
        _a.Foo()
        //...
    }
}

To avoid the dependency cycle, you could rewrite it like this:

class C: IC
{
    private readonly IServiceProvider _services;

    public C(IServiceProvider services)
    {
        _services = services;
    }

    public void Bar()
    {
        //...
        var a = _services.GetRequiredService<IA>();
        a.Foo();
        //...
    }
}

Because it’s no longer necessary to resolve IA while C is being constructed, the cycle is broken (at least during construction), and the problem is fixed.

However, I don’t really like this approach, because it smells of the Service Locator pattern, which is a known anti-pattern. I see two main issues with it:

  • It makes your class depend explicitly on the service provider. This is bad because your class shouldn’t have to know anything about the injection dependency mechanism being used; after all, the app could be using Pure DI, i.e. not use an IoC container at all.
  • It hides the dependencies of your class. Instead of having them all declared at the constructor level, you now have just a IServiceProvider which doesn’t tell you anything about the actual dependencies. You have to scan the code of the class to find them.

A cleaner workaround

The approach I actually use in this situation takes advantage of the Lazy<T> class. You will need the following extension method and class:

public static IServiceCollection AddLazyResolution(this IServiceCollection services)
{
    return services.AddTransient(
        typeof(Lazy<>),
        typeof(LazilyResolved<>));
}

private class LazilyResolved<T>: Lazy<T>
{
    public LazilyResolved(IServiceProvider serviceProvider)
        : base(serviceProvider.GetRequiredService<T>)
    {
    }
}

Call this new method on your service collection during service registration:

services.AddLazyResolution();

This enables the resolution of a Lazy<T> which will lazily resolve a T from the service provider.

In the class that depends on IA, inject Lazy<IA> instead. When you need to use IA, just access the lazy’s value:

class C: IC
{
    private readonly Lazy<IA> _a;

    public C(Lazy<IA> a)
    {
        _a = a;
    }

    public void Bar()
    {
        //...
        _a.Value.Foo();
       //...
    }
}

Note: DO NOT access the value in the constructor, just store the Lazy itself. Accessing the value in the constructor would eagerly resolve IA, which would cause the same problem we were trying to solve.

This solution isn’t perfect, but it solves the initial problem without too much hassle, and the dependencies are still clearly declared in the constructor.

 

I hope you guys understand how I can do this. Let me know if you face any difficulties.

You can watch my previous blog here.

Happy Coding {;}

Nayan Raval

Nayan Raval is a MEAN Stack .Net Developer has extensive experience with designing and developing enterprise-scale applications. Key Areas Of Expertise: • ASP.NET Core MVC • ASP.NET Core Web API • C# • ASP.NET MVC 5 • Angular All versions • HTML5 • CSS3 / SCSS • Bootstrap • JavaScript • Azure • JQuery Databases and related • Microsoft SQL server MSSQL • PostgreSQL • Entity Framework (EF) • LINQ UI Frameworks • Kendo UI • Telerik • JQuery UI • Prime NG and Material UI API Integration • SignalR • DateDog • Twilio Voice Call And Message • Stripe • SendGrid (Email Camping) • Checkr • Zoom Video Call • Auth0 • Elastic Search • Quartz - Scheduler • JWT Token • Google Calendar

Share
Published by
Nayan Raval

Recent Posts

Testing hk

Testing

2 years ago

Create and Used PIPE in angular

In this article, we have to show Create and Used PIPE in angular

2 years ago

Operation

Testing

2 years ago

Create and Used PIPE in angular

In this article, we have to show Create and Used PIPE in angular

2 years ago

Create and Used PIPE in angular

In this article, we have to show Create and Used PIPE in angular

2 years ago

TETS NEW

test

3 years ago