top of page

Decorator Design Pattern in ASP.NET Core

Updated: Apr 5, 2023

The Decorator design pattern is a structural pattern that allows developers to add functionality to an object dynamically without affecting its behavior. In ASP.NET Core, the Decorator pattern can be used to add functionality to a service or middleware pipeline without modifying the original code. This pattern is used when we want to add new functionality to an existing class or an interface without modifying its code.



The Decorator pattern involves creating a base class or interface and then adding functionality to it through one or more decorators. The decorator should implement the same interface or inherit from the same class as the base service. It should also contain a reference to the base service instance. The decorator should add the additional functionality required, either by modifying the behavior of the base service or by providing additional functionality. Let's look at how the Decorator pattern can be implemented in ASP.NET Core.


Implementation of Decorator pattern in ASP.NET Core:

STEP 1: Implement the base service: The base service should contain the basic functionality that needs to be extended. This can be achieved by implementing an interface or a concrete class. The following is an example of a base service:

public interface IOrderService
{
    Order GetOrderById(int id);
}

public class OrderService : IOrderService
{
    public Order GetOrderById(int id)
    {
        // Implementation to get the order from the database
    }
}

STEP 2: Implement the decorator: The decorator should implement the same interface or inherit from the same class as the base service. It should also contain a reference to the base service instance. The decorator should add the additional functionality required, either by modifying the behavior of the base service or by providing additional functionality. The following is an example of a decorator that adds caching functionality to the OrderService:

public class CachingOrderServiceDecorator : IOrderService
{
    private readonly IOrderService _orderService;
    private readonly IMemoryCache _cache;

    public CachingOrderServiceDecorator(IOrderService orderService, IMemoryCache cache)
    {
        _orderService = orderService;
        _cache = cache;
    }

    public Order GetOrderById(int id)
    {
        var cacheKey = $"Order_{id}";
        if (!_cache.TryGetValue(cacheKey, out Order order))
        {
            order = _orderService.GetOrderById(id);
            if (order != null)
            {
                _cache.Set(cacheKey, order, TimeSpan.FromMinutes(5));
            }
        }
        return order;
    }
}

In this example, the CachingOrderServiceDecorator adds caching functionality to the OrderService. The decorator has a reference to the OrderService instance and also to the IMemoryCache instance. The GetOrderById method first checks the cache for the order and returns it if it exists. If the order is not in the cache, the decorator calls the GetOrderById method of the OrderService and stores the result in the cache before returning it.


STEP 3: Register the service: In ASP.NET Core, services can be registered using the built-in dependency injection container. The base service should be registered as a singleton or scoped service, depending on the requirements of the application. The decorator should be registered as a transient service, as it should be created each time it is required. The following is an example of how to register the services:

services.AddSingleton<IOrderService, OrderService>();
services.AddTransient<IOrderService, CachingOrderServiceDecorator>();

In this example, the OrderService is registered as a singleton service, as there should only be one instance of it in the application. The CachingOrderServiceDecorator is registered as a transient service, as it should be created each time it is required.


STEP 4: Use the service: The decorated service can be used in the application as if it were the original service. The decorator will add the additional functionality without affecting the behavior of the original service. The following is an example of how to use the decorated service:

public class OrderController : Controller
{
    private readonly IOrderService _orderService;

    public OrderController(IOrderService orderService)
    {
        _orderService = orderService;
    }

    public IActionResult GetOrder(int id)
    {
        var order = _orderService.GetOrderById(id);
        if (order == null)
        {
            return NotFound();
        }
        return Ok(order);
    }
}

In this example, the OrderController has a constructor that accepts an instance of the IOrderService interface. The decorated service is injected into the controller, and it can be used in the GetOrder action method as if it were the original service. The CachingOrderServiceDecorator will add caching functionality to the GetOrderById method of the OrderService without modifying its code.


Advantages of using Decorator pattern in ASP.NET Core:

  1. Open-Closed Principle: The Decorator pattern follows the Open-Closed principle, which states that a class should be open for extension but closed for modification. This means that new functionality can be added to a class without modifying its code, which makes it easier to maintain and test.

  2. Separation of Concerns: The Decorator pattern separates the concerns of the base service and the additional functionality provided by the decorator. This makes it easier to understand and modify the code, as each class has a clear responsibility.

  3. Reusability: The Decorator pattern promotes code reuse, as decorators can be added or removed without affecting the behavior of the base service.

Disadvantages of using Decorator pattern in ASP.NET Core:

  1. Complexity: The Decorator pattern can add complexity to the code, as it involves creating multiple classes and interfaces. This can make it harder to understand and maintain the code, especially if the decorators are nested.

  2. Performance: The Decorator pattern can affect the performance of the application, as each decorator adds an additional layer of code. This can increase the number of method calls and memory usage, which can impact the performance of the application.

Conclusion:

The Decorator pattern is a useful design pattern that can be used in ASP.NET Core to add functionality to a service or middleware pipeline without modifying the original code. It provides a way to extend the functionality of a class or an interface in a flexible and maintainable way. While it can add complexity to the code and affect performance, the benefits of separation of concerns, reusability, and adherence to the Open-Closed principle make it a valuable pattern to consider when designing and implementing applications in ASP.NET Core.

0 comments
bottom of page