How to use a generic base controller with ActionFilters

Viewed 123

I'm using ILogger<T> and want it to be included in my base controller since every controller should use it.

I'm defining each derived class as UsersController : BaseController<UsersController>

In my base class I'm assigning ILogger<T> which works fine until I try to access the controller in my ActionFilter which I need to know how to cast it.

How do I achieve this or do it in a better way that accomplishes the same thing.

// Base Controller
public class BaseController<T> : ControllerBase
{
    protected readonly IOptions<AppSettings> _appSettings;
    protected readonly ILogger<T> _logger;
}

// Instance Controller
public class UsersController : BaseController<UsersController>
{
    public UsersController(ILogger<UsersController> logger,
        IOptions<AppSettings> appSettings) 
        : base(logger, appSettings)
    {
        ...
    }

// ActionFilter used to set viewbag on all pages/layout pages
public class ViewBagActionFilter : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        // Can't use typeof() or nameof()
        if (context.Controller.GetType().BaseType.Name == "BaseController")
        {
            // This doesn't work either
            var controller = context.Controller as BaseController;
            controller.ViewBag.Username = controller?.User?.Username;
        }

        base.OnResultExecuting(context);
    }
}
2 Answers

I don't know if this will help but ILogger<T> inherits from ILogger so you could just have your base controller not be generic:

public class BaseController : ControllerBase
{
    protected readonly IOptions<AppSettings> _appSettings;
    protected readonly ILogger _logger;
}

and the constructor in your inherited controller will still work.

To make it clear, what I meant. Instead of using BaseController-Class in your Action-Filter, just use ControllerBase... it already has the ViewBag-Property, hasn’t it? This is my suggestion:

// Base Controller
public class BaseController<T> : ControllerBase
{
    protected readonly IOptions<AppSettings> _appSettings;
    protected readonly ILogger<T> _logger;
}

// Instance Controller
public class UsersController : BaseController<UsersController>
{
    public UsersController(ILogger<UsersController> logger,
        IOptions<AppSettings> appSettings) 
        : base(logger, appSettings)
    {
        ...
    }

// ActionFilter used to set viewbag on all pages/layout pages
public class ViewBagActionFilter : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        
        // This could work I think, but untested 
        var controller = context.Controller as ControllerBase;
        controller.ViewBag.Username = controller?.User?.Username;
        

        base.OnResultExecuting(context);
    }
}

Edit: If you have Properties, that need to be accessible in Filters, you can define an Interface like this:

public interface IAccessible
{
   // your Properties here
   string UserName { get;}
   dynamic ViewBag { get;}
   //...
}

And implement it in your BaseController... you see, the interface doesn’t have to be generic... Then you can use it in you filter like:

var controller = context.Controller as IAccessible;
        controller.ViewBag.Username = controller?.Username;
Related