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.
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.