How to get constructor named arguments from attribute

Viewed 452

I have the same problem as this question. The problem is that, even when using named arguments in the attribute's constructor, I still get an empty collection.

This is my Attribute definition:

public class MenuItemAttribute : Attribute
{
    ApplicationCategoryEnum ApplicationCategory { get; }
    string ControllerDisplayName { get; }
    bool IsDefaultRoute { get; }

    public MenuItemAttribute(ApplicationCategoryEnum applicationCategory, string controllerDisplayName, bool isDefaultRoute)
    {
        ControllerDisplayName = controllerDisplayName;
        ApplicationCategory = applicationCategory;
        IsDefaultRoute = isDefaultRoute;
    }
}

and this is how I use it:

[MenuItem(
    applicationCategory: ApplicationCategoryEnum.CONTROLPANEL,
    controllerDisplayName: "Account Management",
    isDefaultRoute: false)]
public class AccountController : Controller { /// }

This is how I get through the Controller's attributes:

var attributeData = controllerInfo
    .CustomAttributes
    .FirstOrDefault(attrs => attrs.AttributeType == typeof(MenuItemAttribute))
    .NamedArguments;

Arguments are there as I can see them if I use ConstructorArguments instead of NamedArguments.

What am I missing?

2 Answers

The syntax:

[MenuItem(
    applicationCategory: ApplicationCategoryEnum.CONTROLPANEL,
    controllerDisplayName: "Account Management",
    isDefaultRoute: false)]

represents constructor arguments; the data you want is in the ConstructorArguments member:

var attrib = typeof(AccountController)
    .CustomAttributes
    .FirstOrDefault(attrs => attrs.AttributeType == typeof(MenuItemAttribute));
if (attrib is object)
{
    var declared = attrib.Constructor.GetParameters();
    var values = attrib.ConstructorArguments;
    for (int i = 0; i < values.Count; i++)
    {
        Console.WriteLine($"{declared[i].Name}={values[i].Value}");
    }
}

(NamedArguments are for the ever-so-subtly-different usage involving =, i.e. IsDefaultRoute = true invokes the property-setter of the IsDefaultRoute property with the value true)

Currently all your parameters are positional ones, so they are returned in ConstructorArguments.

As written in the docs:

Each non-static public read-write field and property for an attribute class defines a named parameter for the attribute class.

Changing your MenuItemAttribute to for example:

    public class MenuItemAttribute : Attribute
    {
        ApplicationCategoryEnum ApplicationCategory { get; }
        public string ControllerDisplayName { get; set; }
        public bool IsDefaultRoute { get; set; }

        public MenuItemAttribute(ApplicationCategoryEnum applicationCategory)
        {
            ApplicationCategory = applicationCategory;
        }
    }

And usage

[MenuItem(ApplicationCategoryEnum.CONTROLPANEL, ControllerDisplayName = "asdsa", IsDefaultRoute = true)]

Will give you 2 elements in NamedArguments collection.

Related