How to read action method's attributes in ASP.NET Core MVC?
Asked Answered
D

5

79

Based on this article I'm trying to create an IActionFilter implementation for ASP.NET Core that can process attributes that are marked on the controller and the controller's action. Although reading the controller's attributes is easy, I'm unable to find a way to read the attributes defined on the action method.

Here's the code I have right now:

public sealed class ActionFilterDispatcher : IActionFilter
{
    private readonly Func<Type, IEnumerable> container;

    public ActionFilterDispatcher(Func<Type, IEnumerable> container)
    {
        this.container = container;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        var attributes = context.Controller.GetType().GetCustomAttributes(true);

        attributes =
            attributes.Append(/* how to read attributes from action method? */);

        foreach (var attribute in attributes)
        {
            Type filterType = typeof(IActionFilter<>)
                .MakeGenericType(attribute.GetType());
            IEnumerable filters = this.container.Invoke(filterType);

            foreach (dynamic actionFilter in filters)
            {
                actionFilter.OnActionExecuting((dynamic)attribute, context);
            }
        }
    }

    public void OnActionExecuted(ActionExecutedContext context) {
        throw new NotImplementedException();
    }
}

My question is: how do I read the action method's attributes in ASP.NET Core MVC?

Dempster answered 7/8, 2015 at 9:47 Comment(1)
You'd get the MemberInfo for whatever method you're interested in using the reflection API, then use GetCustomAttributes on that. Hopefully I'm not misunderstanding the questionCogent
I
102

You can access the MethodInfo of the action through the ControllerActionDescriptor class:

public void OnActionExecuting(ActionExecutingContext context)
{
    if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
    {
        var actionAttributes =
           descriptor.MethodInfo.GetCustomAttributes(inherit: true);
    }
}

The MVC 5 ActionDescriptor class used to implement the ICustomAttributeProvider interface which gave access to the attributes. For some reason this was removed in the ASP.NET Core MVC ActionDescriptor class.

Italian answered 7/8, 2015 at 10:6 Comment(6)
How can I do that in asp.net mvc 6Pliant
This is about MVC 6 (or Core 1.0).Italian
My mistake I did not look clearly. Could you check my answer is right or not? I just find a way to get attribute and will post it.Pliant
It can also be useful to check the controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes() to get attributes that have been set on the controller instead of the method as well.Party
You can use pattern matching for this code: if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)Adaliah
Thanks for mentioning the answer for MVC 5 while you were at it!Forbearance
H
45

Invoking GetCustomAttributes on a method and/or class is slow(er). You should not invoke GetCustomAttributes every request since .net core 2.2, which @Henk Mollema is suggesting. (There is one exception which I will explain later)

Instead, on application startup time, the asp.net core framework will invoke GetCustomAttributes on the action method and controller for you and store the result in the EndPoint metadata.

You can then access this metadata in your asp.net core filters via the EndpointMetadata property of the ActionDescriptor class.

public class CustomFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Get attributes on the executing action method and it's defining controller class
        var attributes = context.ActionDescriptor.EndpointMetadata.OfType<MyCustomAttribute>();
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

If you do not have access to the ActionDescriptor (for example: because you are operating from a Middleware instead of an filter) from asp.net core 3.0 you can use the GetEndpoint extension method to access it's Metadata. For more info see this github issue.

public class CustomMiddleware
{
    private readonly RequestDelegate next;

    public CustomMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        // Get the enpoint which is executing (asp.net core 3.0 only)
        var executingEnpoint = context.GetEndpoint();

        // Get attributes on the executing action method and it's defining controller class
        var attributes = executingEnpoint.Metadata.OfType<MyCustomAttribute>();

        await next(context);

        // Get the enpoint which was executed (asp.net core 2.2 possible after call to await next(context))
        var executingEnpoint2 = context.GetEndpoint();

        // Get attributes on the executing action method and it's defining controller class
        var attributes2 = executingEnpoint.Metadata.OfType<MyCustomAttribute>();
    }
}

Like stated above, Endpoint Metadata contains the attributes for the action method and its defining controller class. This means that if you would want to explicitly IGNORE the attributes applied on either the controller class or the action method, you have to use GetCustomAttributes. This is almost never the case in asp.net core.

Handkerchief answered 9/3, 2020 at 14:40 Comment(5)
This should be the correct answer if you are using endpoint routing with ASP.NET Core 3.0 or laterBiller
Great answer, I had to upgrade to 3.1 but it was worth it.Caucasoid
OfType<MyCustomAttribute> need using System.LinqLooney
The documentation for EndpointMetadata says "This API is meant for infrastructure and should not be used by application code."Disassemble
For your Middleware solution, for a given request, is there a way to access the value of MyCustomAttribute from the HttpContext?Below
P
8

My custom attribute is inherit from ActionFilterAttribute. I put it on my controller but there is one action do not need it. I want to use AllowAnonymous attribute to ignore that but it not work. So I add this snippet in my custom attribute to find the AllowAnonymous and skip it. You can get other in the for loop.

    public class PermissionAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            foreach (var filterDescriptors in context.ActionDescriptor.FilterDescriptors)
            {
                if (filterDescriptors.Filter.GetType() == typeof(AllowAnonymousFilter))
                {
                    return;
                }
            }
        }
    }
Pliant answered 29/4, 2016 at 7:59 Comment(1)
The FilterDescriptors property only works if your custom attribute actually is an MVC Filter. If you have a custom passive attribute applied to your action, then obviously your solution will not work.Forthright
S
7

I created an extension method that mimics the original GetCustomAttributes based in Henk Mollema's solution.

    public static IEnumerable<T> GetCustomAttributes<T>(this Microsoft.AspNet.Mvc.Abstractions.ActionDescriptor actionDescriptor) where T : Attribute
    {
        var controllerActionDescriptor = actionDescriptor as ControllerActionDescriptor;
        if (controllerActionDescriptor != null)
        {
            return controllerActionDescriptor.MethodInfo.GetCustomAttributes<T>();
        }

        return Enumerable.Empty<T>();
    }

Hope it helps.

Skied answered 14/4, 2016 at 18:5 Comment(1)
Works fine in combination with NuGet package System.Reflection.Extensions and a using to System.ReflectionPaschal
D
0

As answered by Henk Mollena

public void OnActionExecuting(ActionExecutingContext context)
{
    var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
    if (controllerActionDescriptor != null)
    {
        var controllerAttributes = controllerActionDescriptor
                               .MethodInfo
                               .GetCustomAttributes(inherit: true);
    }
}

is the correct way if you want to check the presence of an attribute applied to an action.

I just want to add to his answer in case if you want to check the presence of an attribute applied to the controller

public void OnActionExecuting(ActionExecutingContext context)
{
    var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
    if (controllerActionDescriptor != null)
    {
        var actionAttributes = controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(inherit: true);
    }
}

Also you can use the overloaded function of the GetCustomAttributes functions to get your specific attribute(s)

var specificAttribute = GetCustomAttributes(typeof(YourSpecificAttribute), true).FirstOrDefault()
Dichotomy answered 28/2, 2019 at 7:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.