Skip to content

Decorator Pattern in Asp.NET Core 3.1

The decorator pattern is a structural design pattern used for dynamically adding behavior to a class without changing the class. You can use multiple decorators to extend the functionality of a class whereas each decorator focuses on a single-tasking, promoting separations of concerns. Decorator classes allow functionality to be added dynamically without changing the class thus respecting the open-closed principle.

You can see this behavior on the UML diagram where the decorator implements the interface to extend the functionality.Decorator-Pattern

When to use the Decorator Pattern

The decorator pattern should be used to extend classes without changing them. Mostly this pattern is used for cross-cutting concerns like logging or caching. Another use case is to modify data that is sent to or from a component.

Decorator Pattern Implementation in ASP .NET Core 3.1

You can find the code of the demo on Github.

I created a IDataTypeService class with a GetData method which returns a list of ints. Inside the loop, I added a Thread.Sleep to slow down the data collection a bit to make it more real-world like.

public class DataService : IDataTypeService
  {
      public DateTime GetCreateData()
      {
          throw new NotImplementedException();
      }

      public List<int> GetData()
      {
          var data = new List<int>();

          for (var i = 0; i < 10; i++)
          {
              data.Add(i);

              Thread.Sleep(350);
          }

          return data;
      }


  }

This method is called in the GetData action and then printed to the website. The first feature I want to add with a decorator is logging. To achieve that, I created the DataServiceLoggingDecorator class and implement the IDataTypeService interface. In the GetData method, I add a stopwatch to measure how long collecting data takes and then log the time it took.

public class DataServiceLoggingDecorator : IDataTypeService
  {
      private readonly IDataTypeService _dataService;
      private readonly ILogger<DataServiceLoggingDecorator> _logger;

      public DataServiceLoggingDecorator(IDataTypeService dataService, ILogger<DataServiceLoggingDecorator> logger)
      {
          _dataService = dataService;
          _logger = logger;
      }

      public DateTime GetCreateData()
      {
          throw new NotImplementedException();
      }

      public List<int> GetData()
      {
          _logger.LogInformation("Starting to get data");
          var stopwatch = Stopwatch.StartNew();

          var data = _dataService.GetData();

          stopwatch.Stop();
          var elapsedTime = stopwatch.ElapsedMilliseconds;

          _logger.LogInformation($"Finished getting data in {elapsedTime} milliseconds");

          return data;
      }
  }

Additionally, I want to add caching also using a decorator. To do that, I created the DataServiceCachingDecorator class and also implemented the IDataTypeService interface. To cache the data, I use IMemoryCache and check the cache if it contains my data. If not, I load it and then add it to the cache. If the cache already has the data, I simply return it. The cache item is valid for 2 hours.

public class DataServiceCachingDecorator : IDataTypeService
{
    private readonly IDataTypeService _dataService;
    private readonly IMemoryCache _memoryCache;

    public DataServiceCachingDecorator(IDataTypeService dataService, IMemoryCache memoryCache)
    {
        _dataService = dataService;
        _memoryCache = memoryCache;
    }


    public List<int> GetData()
    {
        List<int> cacheEntry;
        const string cacheKey = "data-key";
        // Look for cache key.
        if (!_memoryCache.TryGetValue(cacheKey, out cacheEntry))
        {
            // Key not in cache, so get data.
            cacheEntry = _dataService.GetData();

            // Set cache options.
            var cacheEntryOptions = new MemoryCacheEntryOptions()
                // Keep in cache for this time, reset time if accessed.
                .SetSlidingExpiration(TimeSpan.FromSeconds(30));

            // Save data in cache.
            _memoryCache.Set(cacheKey, cacheEntry, cacheEntryOptions);
        }


        //if (_memoryCache.TryGetValue<List<int>>(cacheKey, out var data))
        //{
        //    return data;
        //}

        //data = _dataService.GetData();

        //_memoryCache.Set(cacheKey, data, TimeSpan.FromMinutes(120));

        return cacheEntry;
    }
   
   
}

All that is left to do is to register the service and decorator in the ConfigureServices method of the startup class with the following code:

services.AddScoped(serviceProvider =>
           {
               var logger = serviceProvider.GetService<ILogger<DataServiceLoggingDecorator>>();
               var memoryCache = serviceProvider.GetService<IMemoryCache>();

               IDataTypeService concreteService = new DataService();
               IDataTypeService loggingDecorator = new DataServiceLoggingDecorator(concreteService, logger);
               IDataTypeService cacheingDecorator = new DataServiceCachingDecorator(loggingDecorator, memoryCache);

               return cacheingDecorator;
           });

With everything in place, I can call the GetData method from the service which gets logged and the data placed in the cache. When I call the method again, the data will be loaded from the cache.

public class ValuesController : ControllerBase
   {
       private readonly IDataTypeService _dataService;

       public ValuesController(IDataTypeService dataService)
       {
           _dataService = dataService;
       }

       // GET: api/<ValuesController>
       [HttpGet]
       public IEnumerable<string> Get()
       {
         
           var data = _dataService.GetData();
           string[] the_array = data.Select(i => i.ToString()).ToArray();

           return the_array;
       }

       // GET api/<ValuesController>/5
       [HttpGet("{id}")]
       public string Get(int id)
       {
           return "value";
       }

       // POST api/<ValuesController>
       [HttpPost]
       public void Post([FromBody] string value)
       {
       }

       // PUT api/<ValuesController>/5
       [HttpPut("{id}")]
       public void Put(int id, [FromBody] string value)
       {
       }

       // DELETE api/<ValuesController>/5
       [HttpDelete("{id}")]
       public void Delete(int id)
       {
       }
   }

 

Published inASP.NETDesign Pattern

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *