I have an ASP.NET Core 2.2 MVC web application using the Repository Pattern. I created a class called LogAttribute
that is derived from ActionFilterAttribute so that I can log information after execution of controller actions.
Here is an example of using this action filter attribute in a mvc controller class:
public class HomeController : Controller
{
private readonly IMyRepository _repository;
public HomeController(IMyRepository repository)
{
_repository = repository;
}
[Log("Go to Home Page")]
public async Task<IActionResult> Index()
{
...
}
[Log("Go to About Page")]
public async Task<IActionResult> About()
{
...
}
}
So when I go to /Home
, it should log "Go to Home Page". And when I go to /About
page, it should log "Go to About Page".
However, I have no idea how to access my repository from LogAttribute
class. Here is the LogAttribute
class:
public class LogAttribute : ActionFilterAttribute
{
private IDictionary<string, object> _arguments;
private IMyRepository _repository;
public string Description { get; set; }
public LogAttribute(string description)
{
Description = description;
}
// // Injecting repository as a dependency in the ctor DOESN'T WORK
// public LogAttribute(string description, IMyRepository repository)
// {
// Description = description;
// _repository = repository;
// }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_arguments = filterContext.ActionArguments;
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var description = Description;
// NullReferenceException since I don't know
// how to access _repository from this class
_repository.AddLog(new LogAction
{
Description = description
});
}
}
So my question is how can I access my repository (or at least my DbContext) from my LogAttribute
class?
You don't. Attributes cannot have constructor injected parameters and their lifetime is unbounded, making them a very poor choice for integration with the ASP.NET Core pipeline.
Therefore, attributes should not perform any "heavy lifting" themselves. I feel the various tutorials and guides online that show trivial logging (using Debug.WriteLine
) inside an ActionFilterAttribute
are doing their readers a disservice (e.g. (DO NOT DO THIS!) https://www.tutorialsteacher.com/mvc/action-filters-in-mvc )
If you're using ASP.NET Core, then implement IActionFilter
(or IAsyncActionFilter
) and IFilterFactory
separately and make the IFilterFactory
the Attribute
(instead of the IActionFilter
), like so:
// This class is the attribute. Note that it is not an action filter itself.
// This class cannot have DI constructor injection, but it can access the IServiceProvider.
public class LogAttribute : Attribute, IFilterFactory
{
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetService<LogFilter>();
}
}
// This class is the actual filter. It is not an attribute.
// This class *can* have DI constructor injection.
public class LogFilter : IActionFilter // or IAsyncActionFilter
{
public LogFilter( DbContext db )
{
}
}
A full example can be found here: ( https://www.devtrends.co.uk/blog/dependency-injection-in-action-filters-in-asp.net-core )