Search code examples
c#asp.net-coreasp.net-core-2.2

Unable to access repository (or DbContext) from a class that is derived from ActionFilterAttribute


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?


Solution

  • 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 )