Referbruv!


Card image cap

Exploring ASP.NET Core Fundamentals - Implementing the Decorator Pattern

Design Patterns  • Posted 5 months ago

In a previous article, we discussed in detail about the decorator pattern and how it helps in maintaining code integrity and satisfies a couple of design principles in its structuring. In this article we'll discuss about how we can implement the decorator pattern in dotnet core.

Various popular DI (Dependency Injection) containers such as Ninject and Autofac have different code syntaxes to implement decorator pattern. In theory, we inject a derivative of an interface as a constructor parameter to a wrapper component which itself is a derivative of the same interface.

Read: Understanding the Decorator Pattern

Let's take our previous example of the Reader class:

public interface IDataReader
{
	List<Data> ReadData();	
}

public class DataReader : IDataReader
{
	public List<Data> ReadData() 
	{
		var data = new List<Data>();
		// some logic for reading data 
		data.Add(...);
		return data;
	}
}

public class FormattedDataReader : IDataReader
{
	IDataReader reader;

	public FormattedDataReader(IDataReader reader) 
	{
		this.reader = reader;
	}

	public List<Data> ReadData()
	{
		List<Data> data = this.reader.ReadData();
		//additional logic on the data (the new requirement)
		return data;		
	}
}

In the above examples, both the classes DataReader and FormattedDataReader implement the interface IDataReader and the FormattedDataReader expects a parameter of its base type viz. IDataReader. Now we pass an instance of DataReader for the FormattedDataReader constructor and the whole stuff works fine.

This approach is implemented in a Ninject container as follows:

IKernel container = new StandardKernel();
container.Bind<IDataReader>()
.To<FormattedDataReader>()
.InSingletonScope()
.WithConstructorArgument<IDataReader>(container.Get<DataReader>());

We instruct the Ninject container to pass in an instance of the DataReader class as parameter to the constructor of the type FormattedDataReader which is to be substituted whenever an instance of type IDataReader is requested.

And a similar approach follows in Autofac container as follows:

IContainer container;
var builder = new ContainerBuilder();
builder.RegisterType<DataReader>()
.Named<IDataReader>("reader")
.SingleInstance();

builder.RegisterDecorator<IDataReader>(
	(c, innerParam) => new FormattedDataReader(innerParam),
	fromKey: "reader"
);

container = builder.Build();

That's a bit complicated when compared to Ninject but in simple terms we instruct the Autofac container to pass in an instance of the class DataReader which is registered as a named type under key "reader" to the constructor of the type FormattedDataReader, whenever an instance of type IDataReader is requested.

Coming to the implementation using DI container provided under ASP.NET Core MVC, it's no straightforward approach since the DI container doesn't contain any extension method to specify the constructor parameters to be passed when a service type is called.

Hence if we go by the below approach, which is the conventional,

services.AddSingleton<IDataReader, FormattedDataReader>();

we encounter a circular reference runtime error, since the DI container blindly substitutes FormattedDataReader whenever it sees for a type IDataReader. Now since the FormattedDataReader type expects an instance of type IDataReader in its constructor, the DI container windsup into a circular loop.

Now the solution?

There's a workaround, if not the alternative for implementing such a dependency resolution.

services.AddSingleton<IDataReader>(
	x => new FormattedDataReader(
		new DataReader()
	)
);

In this overload of the AddSingleton() method of the IServiceCollection, we explicitly specify the object instance to be passed when an instance of type IDataReader is requested, along with an instance of type DataReader as the constructor parameter.

While this works, I don't see this an elegant method since we're explicitly creating an object using the new keyword. For this, we can use a method CreateInstance() from the class ActivatorUtilities under the Microsoft.Extensions.DependencyInjection namespace.

The method takes in a type parameter followed by a service collection instance if needed to be used for resolution and any constructor parameters for that instance which is returned.

The above piece of code then becomes,

services.AddSingleton<IDataReader>(
	x => ActivatorUtilties.CreateInstance<FormattedDataReader>(x,
		ActivatorUtilties.CreateInstance<DataReader>(x)
	)
);

This is one example of the limitations of the DI container provided in the ASP.NET Core MVC. Although the DI container has its own advantages of being lightweight and performant, such scenarios might make it a bit complex.

isn't it?

Other Structural Patterns:

The Decorator Pattern (this)

The Adapter Pattern

The Proxy Pattern

Published 5 months ago

Sponsored Links
We use cookies to improve user experience. Learn More