Introduction
In the last module we looked at how to use strongly typed configuration with our caching functionality in the ViewComponent. As before this is all based on Scott Allen’s “ASP.NET Core Fundamentals” course on Pluralsight.
The issue with both of the first two posts is they use caching inside the ViewComponent. This is what is called a code smell and violates the Single Responsibility Principle. The ViewComponent is presenting the data to the user but also executing the caching; this is not good.
What is the Single Responsibility Principle
The Single Responsibility Principle, or SRP, is the S
of SOLID. In short it states that something should only have one responsibility and do it well. If you are unfamilier with SRP or SOLID then I would highly recommend taking some time to read up on the subject.
In our example the ViewComponent, which is a UI item, should only be concerned about presenting data to the user and not caching.
The Decorator Pattern
So how do we solve this? One way to solve this problem is through the use of the “Decorator Pattern”.
So what is the Decorator Pattern?
“In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern. The decorator pattern is structurally nearly identical to the chain of responsibility pattern, the difference being that in a chain of responsibility, exactly one of the classes handles the request, while for the decorator, all classes handle the request.” Wikipedia
That’s all very well but what does that mean in practice?
ViewComponent Refactor
We have a data respository pattern using the interface IRestaurantData
which is implemented by SqlRestaurantData
. The ViewComponent is dependent on the interface but not the implementation which means the ViewComponent doesn’t care what it gets given through Dependency Injection as long as it satifies the IRestaurantData
interface. With this the ViewComponent can return to the original implementation.
public class RestaurantCountViewComponent : ViewComponent
{
private readonly IRestaurantData _restaurantData;
public RestaurantCountViewComponent(IRestaurantData restaurantData)
{
_restaurantData = restaurantData;
}
public IViewComponentResult Invoke()
{
// ViewComponent does not care what it gets as long as it implements the interface and gives the answer
var count = _restaurantData.GetCountOfRestaurants();
return View(count);
}
}
Implementing The Decorator Pattern
To implement the Decorator Pattern the class definition which is going to “decorate” or “wrap” the inner implementation is required to implement the interface, IRestaurantData
in our example, but also take a constructor dependency of IRestaurantData
as well.
As the functionality we are looking to implement is caching let’s create a new IRestaurantData
implementation called CachedRestaurantData
.
public class CachedRestaurantData : IRestaurantData
{
private readonly IRestaurantData _repository;
private readonly IDistributedCache _cache;
private readonly RestaurantCacheSettings _settings;
public CachedRestaurantData(IRestaurantData repository,
IDistributedCache cache,
RestaurantCacheSettings settings)
{
_repository = repository;
_cache = cache;
_settings = settings;
}
// snip for brevity
}
As we can see from the above it both implements and is dependent on IRestaurantData
. This will allow for a decorator setup. You can also see that it is also dependent on the IDistributedCache
and a settings class.
For all the methods that it defines that we are currently not interested in caching we will need to just pass through the call to the injected IRestaurantData
implementation.
For example this is the Add
implementation.
public Restaurant Add(Restaurant newRestaurant)
{
return _repository.Add(newRestaurant);
}
But for the method we do want to apply caching to, so that we can keep the same functionality as before, we can paste in the same caching implementation code we had in the previous post. This should be familier but if not please check out the earlier posts in this series (links at the end of the post).
public int GetCountOfRestaurants()
{
byte[] countBytes = _cache.Get("Count");
int count;
if (countBytes == null)
{
count = _repository.GetCountOfRestaurants();
var options = new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(_settings.Seconds)
};
_cache.Set("Count", BitConverter.GetBytes(count), options);
}
else
{
count = BitConverter.ToInt32(countBytes);
}
return count;
}
Now we’ve got to register this new implementation with the dependency injection framework.
Scrutor
Out of the box the Microsoft Dependency Injection framework does not support the Decorator Pattern. Due to this small limitation a nuget package has been made by Kristian Hellang called Scrutor which provides the required functionality.
Registering The Decorators
To register the decorators we need to make sure we have the base underlying implementation registered with the IServiceCollection
first and then we can apply further decorators.
services.AddScoped<IRestaurantData, SqlRestaurantData>();
// Add the decorator implementations after the
services.Decorate<IRestaurantData, CachedRestaurantData>();
This is easily done through using the Decorate
extension method which Scrutor provides.
And that’s it. The ViewComponent will now have the Caching version of IRestaurantData
injected into it’s constructor which will wrap the SqlRestaurantData
version.
Conclusion
In this post we have removed the multiple responsibilites which the ViewComponent had accumulated and reverted it back to doing the one thing it’s good at. We have discussed the decorator pattern and looked at how to implement it using Scrutor.
All the source code for this post can be found in the AddedDecorator pull request on my fork in Github.
Any questions/comments then please contact me on Twitter @WestDiscGolf
Other Posts in this Series
Part 1 - Beyond Basics - ASP.Net Core Adding Caching
Part 2 - Beyond Basics - ASP.Net Core Adding and Using Configuration
Part 3 - This Post