Search code examples
c#asp.net-coreasp.net-core-mvcasp.net-core-2.1

A suitable constructor could not be found for type filter


I have this typefilter that was recently created and it is located in a separate project.

public class RolesFilterAttribute : TypeFilterAttribute
{
    public RolesFilterAttribute() : base(typeof(RolesFilterAttributeImpl))
    {

    }

    public class RolesFilterAttributeImpl : IActionFilter
    {
        private readonly ValidateRoleClient validateRoleClient;
        private string Role;
        private string SecretKey;
        public RolesFilterAttributeImpl(string Role, string SecretKey, ValidateRoleClient validateRoleClient)
        {
            this.validateRoleClient = validateRoleClient;
            this.Role = Role;
            this.SecretKey = SecretKey;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.HttpContext.Request.Cookies["Token"] != null || context.HttpContext.Request.Cookies["RefreshToken"] != null)
            {
                TokenViewModel tvm = new TokenViewModel
                {
                    Token = context.HttpContext.Request.Cookies["Token"],
                    RefreshToken = context.HttpContext.Request.Cookies["RefreshToken"]
                };
                ValidateRoleViewModel vrvm = new ValidateRoleViewModel
                {
                    Role = Role,
                    SecretKey = SecretKey,
                    Token = tvm
                };
                validateRoleClient.ValidateRole(vrvm);
            }
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            throw new NotImplementedException();
        }
    }
}

This is how I declare it above my method:

[TypeFilter(typeof(RolesFilterAttribute), Arguments = new object[] { "role", "abc1234" })]
public IActionResult About()
{
    return View();
}

I believe I have declared what I needed to in my Startup.cs

services.AddScoped<ValidateRoleClient>();
services.AddScoped<RolesFilterAttribute>();

However, when I start the app and navigate to the about page, this is what I encounter:

An unhandled exception occurred while processing the request. InvalidOperationException: A suitable constructor for type 'App.Link.Filters.RolesFilterAttribute' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor. Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type instanceType, Type[] argumentTypes, out ConstructorInfo matchingConstructor, out Nullable[] parameterMap)

What else am I missing that I am not declaring?


Solution

  • [TypeFilter(typeof(RolesFilterAttribute), …]
    

    This says that the filter type you want to create is a RolesFilterAttribute. Within the type filter, you are also passing two arguments "role" and "abc1234". So when the type filter will trigger the creation of RolesFilterAttribute it will look for a constructor that takes those two strings. But there is only a single constructor:

    public RolesFilterAttribute()
        : base(typeof(RolesFilterAttributeImpl))
    { }
    

    So you have two parameters for a parameter-less constructor. That’s why you are getting the error.

    Instead, what you want to do is have the [TypeFilter] attribute create an actual filter. So you need to pass the RolesFilterAttributeImpl type there:

    [TypeFilter(typeof(RolesFilterAttributeImpl), Arguments = new object[] { "role", "abc1234" })]
    

    At that point, your RolesFilterAttribute also becomes redundant, so you can just get rid of that and just define the RolesFilterAttributeImpl (which I would rename to just RolesFilter since it’s a filter, not an attribute or an attribute implementation).

    Furthermore, since you are using [TypeFilter], you do not need to register your filter type with your dependency injection container. You only need to have the dependencies of your type registered, so only ValidateRoleClient in your case.

    So your filter implementation would just look like this:

    public class RolesFilter : IActionFilter
    {
        private readonly ValidateRoleClient validateRoleClient;
        private readonly string role;
        private readonly string secretKey;
    
        public RolesFilter(string role, string secretKey, ValidateRoleClient validateRoleClient)
        {
            this.validateRoleClient = validateRoleClient;
            this.role = role;
            this.secretKey = secretKey;
        }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            // …
        }
    
        public void OnActionExecuting(ActionExecutingContext context)
        { }
    }
    

    [TypeFilter] is btw. just one way to allow specifying filters using attributes. You can also create your own attribute which is then actually just a filter factory which is responsible of creating the filter instance at run-time. That’s what [TypeFilter] does for you by default.

    See also my related answer on the topic of using dependencies in MVC filters.