Search code examples
c#asp.net-coredependency-injectiondecoratorscrutor

Decorate BaseController with Scrutor in .netCore 3.1


I have an application in .net core 3.1 with angular front end. I want to use the decorator to the base controller in order to log CUD operations in the entire application. I am using Scrutor nuget package in the project.

The Base Controller is the following

    using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Xenagos.Data;
using Xenagos.Data.EFCore;
using Xenagos.ViewModels;

namespace Xenagos.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public abstract class BaseController<TEntity, TViewEntity, TRepository> : ControllerBase, IBaseController<TEntity, TViewEntity> 
        where TEntity : class
        where TViewEntity : class, IViewEntity
        where TRepository : IRepository<TEntity>
    {
        private readonly IRepository<TEntity> repository;
        private readonly IMapper mapper;

        public BaseController(TRepository repository, IMapper mapper)
        {
            this.repository = repository;
            this.mapper = mapper;
        }


        // GET: api/[controller]
        [HttpGet]
        public virtual async Task<ActionResult<ComplexData<TViewEntity>>> Get()
        {
            var results = await repository.GetAll();
            List<TViewEntity> resultsView =
                this.mapper.Map<List<TEntity>, List<TViewEntity>>(results);
            return Ok(new ComplexData<TViewEntity>(resultsView));
        }

        // GET: api/[controller]/5
        [HttpGet("{id}")]
        public async Task<ActionResult<TEntity>> Get(int id)
        {
            var entity = await repository.Get(id);
            if (entity == null)
            {
                return NotFound();
            }

            return entity;
        }

        // PUT: api/[controller]/5
        [HttpPut("{id}")]
        public virtual async Task<IActionResult> Put(string id, TViewEntity entity)
        {
            if (!id.Equals(entity.Id))
            {
                return BadRequest();
            }
            await repository.Update(this.mapper.Map<TEntity>(entity));
            return NoContent();
        }

        // POST: api/[controller]
        [HttpPost]
        public virtual async Task<ActionResult<TEntity>> Post(TViewEntity entity)
        {
            await repository.Add(this.mapper.Map<TEntity>(entity));
            return CreatedAtAction("Get", new { id = entity.Id }, entity);
        }

        // DELETE: api/[controller]/5
        [HttpDelete("{id}")]
        public async Task<ActionResult<TViewEntity>> Delete(int id)
        {
            var entity = await repository.Delete(id);
            if (entity == null)
            {
                return NotFound();
            }
            return this.mapper.Map<TViewEntity>(entity);
        }
    }
}

The decorator I made is the following

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xenagos.Controllers;
using Xenagos.ViewModels;

namespace Xenagos.Data
{
    public class LoggingDecorator<T, TViewEntity, TRepository> : IBaseController<T, TViewEntity>
        where T : class
        where TViewEntity : class, IViewEntity
        where TRepository : IRepository<T>
    {

        private IBaseController<T, TViewEntity> _baseController;
        private readonly ILogger<LoggingDecorator<T, TViewEntity, TRepository>> _logger;

        public LoggingDecorator(IBaseController<T, TViewEntity> baseController, ILogger<LoggingDecorator<T, TViewEntity, TRepository>> logger)
        {
            _baseController = baseController;
            _logger = logger;
        }

        Task<ActionResult<TViewEntity>> IBaseController<T, TViewEntity>.Delete(int id)
        {
            _logger.LogWarning($"Deleting record from ... with ID:{id}");
            Task<ActionResult<TViewEntity>> result = _baseController.Delete(id);

            return result;
        }

        public Task<ActionResult<ComplexData<TViewEntity>>> Get()
        {
            return _baseController.Get();
        }

        Task<ActionResult<T>> IBaseController<T, TViewEntity>.Get(int id)
        {
            return _baseController.Get(id);
        }

        public Task<ActionResult<T>> Post(TViewEntity entity)
        {
            _logger.LogWarning($"Adding new record from ... with object data :{JsonConvert.SerializeObject(entity)}");
            return _baseController.Post(entity);
        }

        public Task<IActionResult> Put(string id, TViewEntity entity)
        {
            _logger.LogWarning($"updating record from ... with object data :{JsonConvert.SerializeObject(entity)}");
            Task<IActionResult> result = _baseController.Put(id, entity);

            return result;
        }
    }
}

In the startup class in the public void ConfigureServices(IServiceCollection) I use the following lines

services.AddScoped<IBaseController<Models.Property, PropertyViewModel>, BaseController<Models.Property, PropertyViewModel, PropertyRepository>>();
            services.Decorate<IBaseController<Models.Property, PropertyViewModel>, LoggingDecorator<Models.Property, PropertyViewModel, PropertyRepository>>();

I have extracted an Interface from the base controller, on top of all the previous actions. While the application runs, it does not call/pass-through the decorator. What I'm missing here ? I haven't used the decorator pattern with .net core and dependency injection before. All the added code is in the backend only, I haven't altered the front end at all.

Thank you in advance.


Solution

  • Logging enter/exit on controller actions using ActionFilter:

    public class LoggingActionFilter : IActionFilter
    {
        ILogger _logger;
        public LoggingActionFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<LoggingActionFilter>();
        }
        public void OnActionExecuting(ActionExecutingContext context)
        {
            // do something before the action executes
            _logger.LogInformation($"Action '{context.ActionDescriptor.DisplayName}' executing");
        }
        public void OnActionExecuted(ActionExecutedContext context)
        {
            // do something after the action executes
            _logger.LogInformation($"Action '{context.ActionDescriptor.DisplayName}' executed");
        }
    }
    

    Startup

    services.AddMvc()
        .AddMvcOptions(options =>
        {
            options.Filters.Add<LoggingActionFilter>();
        });
    

    You might also want to implement IAsyncActionFilter for the async actions.

    To read more about action filters, look here.

    You can also add exception filters to log all exceptions.