Workaround for Circular Dependencies in .NET and ASP.NET Core

This blog post explains an approach to resolving circular service dependencies in .NET and ASP.NET Core. In this context, services are .NET types that the dependency framework injects into the application, not HTTPS services for exchanging JSON.

Sometimes, two services depend on each other. For example, I have an IDeliveryClient implementation that deserializes JSON entries from the SaaS headless CMS. This IDeliveryClient needs a JsonSerializerOptions object to configure serialization, where that object must come from dependency injection. JsonSerializerOptions include JsonConverters, one of which injects the IDeliveryClient into members of the object being instantiated by deserialization. So the JsonSerializerOptions needs the repository to exist, the repository needs to keep track of the JsonSerializerOptions, and I do not want to use a service locator. I cannot construct the list of JsonConverters, and hence the JsonSerializerOptions, without the IDeliveryClient, and I the constructor for IDeliveryClient a JsonSerializerOptions. With only constructor injection, one of the objects must exist before the other.

To address this problem, I implemented an IConfigureJsonSerializer interface for classes that construct JsonSerializerOptions objects. The IConfigureJsonSerializer interface contains one method to specify the IDeliveryClient and another method that returns a JsonSerializerOptions.

public interface IConfigureJsonSerializer
{
    public JsonSerializerOptions GetJsonSerializerOptions();
    public void SetDeliveryClient(IDeliveryClient deliveryClient);
}

I implemented IConfigureJsonSerializer to constructs JsonSerializerOptions at runtime including one that depends on the IDeliveryService and registered a class for this service.

using System;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

using Deliverystack.Core.Attributes;
using Deliverystack.Html;
using Deliverystack.Interfaces;
using Deliverystack.StackContent.Models;
using Deliverystack.StackContent.Models.Fields;

public class ContentstackJsonSerializerConfigurator : IConfigureJsonSerializer
{
    private IDeliveryClient _client;

    public JsonSerializerOptions GetJsonSerializerOptions()
    {
        JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true };

        foreach (Type blockType in ModularBlockBase.Implementations)
        {
            MethodInfo methodInfo = blockType.GetRuntimeMethod(
                "GetJsonConverter", new Type[0]);
            object obj = Activator.CreateInstance(blockType);
            JsonConverter jsonConverter = methodInfo.Invoke(
                obj, new object[0]) as System.Text.Json.Serialization.JsonConverter;
            options.Converters.Add(jsonConverter);
        }
        
        foreach (Type type in
            AutoLoadJsonConverter.GetTypesWithAttribute(typeof(AutoLoadJsonConverter)))
        {
            if (AutoLoadJsonConverter.IsEnabledForType(type))
            {
                options.Converters.Add(Activator.CreateInstance(type) as JsonConverter);
            }
        }

        options.Converters.Add(new ReferenceField.ReferenceFieldJsonConverter(_client));
        options.Converters.Add(new MarkupFieldJsonConverter());
        return options;
    }

    public void SetDeliveryClient(IDeliveryClient deliveryClient)
    {
        _client = deliveryClient;
    }
}

In the constructor for the IDeliveryService that receives the implementation of this interface, I pass this IDeliveryService to the IConfigureJsonSerializer.

public class ContentstackDeliveryClient : IDeliveryClient
{
    private IConfigureJsonSerializer _jsonConfigurator;

    public ContentstackDeliveryClient(HttpClient httpClient,
        ContentstackDeliveryClientOptions options,
        IConfigureJsonSerializer jsonConfigurator)
    {
        _httpClient = httpClient;
        ApiKey = options.ApiKey;
        AccessToken = options.AccessToken;
        BaseAddress = options.BaseAddress;
        ContenstackEnvironment = options.Environment;
        _jsonConfigurator = jsonConfigurator;
        _jsonConfigurator.SetDeliveryClient(this);

This required registering IConfigureJsonSerializer before configuring IDeliveryClient in the Configure() method of Startup.cs.

services.AddSingleton<IConfigureJsonSerializer, ContentstackJsonSerializerConfigurator>();
services.AddSingleton<ContentstackDeliveryClientOptions>(
    new ContentstackDeliveryClientOptions(
        Configuration.GetSection("ContentstackOptions")));
services.AddHttpClient<IDeliveryClient, ContentstackDeliveryClient>();

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: