Luke T Brooks
Luke T Brooks

Reputation: 557

Get the Attribute tag from a Controller Method in MVC?

I'm trying to get the Attribute tag from an API Controller so that I can see what HTTP verb it allows at runtime. From the sample code below, I want to be able to get the [HttpGet] tag.

[HttpGet]
public void MyResource()
{
    // Controller logic
}

I am currently using System.Reflection to gather other information about my API while it is running, but so far I have been unable to retrieve the [HttpGet tag and other Http verb tags. I've tried each of the solutions below with no luck:

public void GetControllerMethodHttpAttribute()
{
    MethodInfo controllerMethod = typeof(TestController).GetMethods().First();

    // Solution 1
    var attrs = controllerMethod.Attributes;

    // Solution 2
    var httpAttr = Attribute.GetCustomAttributes(typeof(HttpGetAttribute));

    // Solution 3
    var httpAttr2 = Attribute.GetCustomAttribute(controllerMethod, typeof(HttpGetAttribute));

    // Solution 4
    var httpAttr3 = Attribute.IsDefined(controllerMethod, typeof(HttpGetAttribute));
}

All of the previous questions that I've researched about this topic only related to Custom Attribute tags and pulling values out of those, but I couldn't find any information about getting the framework-included Attribute tags.

Does anyone know how I can get the [HttpGet] Attribute tag?

Thanks!

Upvotes: 6

Views: 4681

Answers (2)

NineBerry
NineBerry

Reputation: 28509

The proposed solutions 3 and 4 work.

You must look out that you are referencing the correct HttpGetAttribute. There is one in System.Web.Http.HttpGetAttribute and there is one in System.Web.Mvc.HttpGetAttribute.

The following code lists the public methods of the ValuesController API controller with the information on whether they have HttpGet or HttpPost attributes.

var methods = typeof(ValuesController).GetMethods();
string infoString = "";

foreach(var method in methods)
{
    // Only public methods that are not constructors
    if(!method.IsConstructor && method.IsPublic)
    {
        // Don't include inherited methods
        if(method.DeclaringType == typeof(ValuesController))
        {

            infoString += method.Name;

            if(Attribute.IsDefined(method, typeof(System.Web.Http.HttpGetAttribute)))
            {
                infoString += " GET ";
            }
            if(Attribute.IsDefined(method, typeof(System.Web.Http.HttpPostAttribute)))
            {
                infoString += " POST ";
            }


            infoString += Environment.NewLine;
        }
    }
}

You need to exchange System.Web.Http. with System.Web.Mvc. when the controller is an MVC controller instead of an API controller.

Upvotes: 8

gunr2171
gunr2171

Reputation: 17579

This is how I ended up doing it:

public static IEnumerable<Attribute> GetSupportedVerbsForAction<T>(
    Expression<Func<T, IActionResult>> expression)
    where T : Controller
{
    //only consider a list of attributes
    var typesToCheck = new[] { typeof(HttpGetAttribute), typeof(HttpPostAttribute),
        typeof(HttpPutAttribute), typeof(HttpDeleteAttribute), typeof(HttpPatchAttribute)};

    var method = ((MethodCallExpression)expression.Body).Method;

    var matchingAttributes = typesToCheck
        .Where(x => method.IsDefined(x))
        .ToList();

    //if the method doesn't have any of the attributes we're looking for,
    //assume that it does all verbs
    if (!matchingAttributes.Any())
        foreach(var verb in typesToCheck)
            yield return verb;

    //else, return all the attributes we did find
    foreach (var foundAttr in matchingAttributes)
    {
        yield return method.GetCustomAttribute(foundAttr);
    }
}

Say you have the following Controller and Actions:

public class HomeController : Controller
{
    // implicit get
    public IActionResult Index() => View();

    // explicit get
    [HttpGet]
    public IActionResult GetAction() => View();

    // extra info
    [HttpPost(Name = "some name", Order = 5)]
    public IActionResult PostAction() => View();

    [HttpPut]
    [HttpDelete]
    [HttpPatch("my template", Name = "patch name", Order = 333)]
    public IActionResult MultiAction() => View();
}

You can call them like this:

var indexVerbs = GetSupportedVerbsForAction<HomeController>(x => x.Index()).ToList();
var getVerbs = GetSupportedVerbsForAction<HomeController>(x => x.GetAction()).ToList();
var postVerbs = GetSupportedVerbsForAction<HomeController>(x => x.PostAction()).ToList();
var multiVerbs = GetSupportedVerbsForAction<HomeController>(x => x.MultiAction()).ToList();

This will return the full attribute that you've set in case you've added any custom data.

enter image description here

Two things to note:

  1. You'll need to supply parameters to the function call. They don't need to be valid as the method itself won't be called, but I couldn't figure out how to allow you to do GetSupportedVerbsForAction<HomeController>(x => x.Index).
  2. The method will pass back a list of Attribute, so use the is keyword to figure out if it's the type you want.

Upvotes: 2

Related Questions