1

I have a view in my site that displays all controller and its action methods of my application:
Action Method:

public ActionResult GetAllController()
{
    var controllers = typeof (MvcApplication).Assembly.GetTypes().Where(typeof (IController).IsAssignableFrom);
    return View(controllers.ToList());
}

View:

<ul>
    @foreach (var item in Model)
    {
            <li>
                @item.Name
                <ul>
                    @foreach (var action in item.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(method => typeof(ActionResult).IsAssignableFrom(method.ReturnType)))
                    {
                        <li>action.Name</li>
                    }
                </ul>

            </li>
    }

</ul>

It works like a charm:

HomeController
     Index
     About
MainController
     Index
     Create
     Edit
     Delete
...

Now I want to display another name for controllers and the action methods. for doing that I have created a custom attribute:

public class DisplayNameAttribute : FilterAttribute
{
        public string Title { get; set; }

        public DisplayNameAttribute(string title)
        {
            this.Title = title;
        }

}

So in this case I just set that attribute for each of controllers or action methods like this:

[DisplayName("Latest News")]
public ActionResult News()
{
     return View();
}

In this case I created an extension method for using inside views:

public static string DisplayAttribute<T>(this T obj, Expression<Func<T, string>> value)
        {
            var memberExpression = value.Body as MemberExpression;
            var attr = memberExpression.Member.GetCustomAttributes(typeof(DisplayNameAttribute), true);
            return ((DisplayNameAttribute)attr[0]).Title;
        }

So inside view I'm using this way for displaying the title of action method or controller:

@item.DisplayAttribute(p => p.Name)

But when I run application I'll get this error:

{"Index was outside the bounds of the array."}

That throws from this line of code:

return ((DisplayNameAttribute)attr[0]).Title;

Any idea?

10
  • what does the rendered html look like? This might be available front end instead? Commented Mar 8, 2015 at 12:52
  • pretty simple. you are trying to reference an index in an array that does not exist i.e attr.Length would be 0 but you are asking to get the first item in the attr array - there is no first item. ALWAYS validate the object/array before accessing it. Commented Mar 8, 2015 at 12:53
  • 1
    Is it a typo that you use ControllerName instead of DisplayName on the action? If not, that's probably your issue. Commented Mar 8, 2015 at 12:53
  • 2
    Also, you should probably derive your attribute from Attribute instead of FilterAttribute, so that MVC doesn't add it to the filter pipeline. Commented Mar 8, 2015 at 12:54
  • @jbutler483 As I mentioned the result is this error: Index was outside the bounds of the array. Commented Mar 8, 2015 at 12:59

3 Answers 3

1

Looking at this more closely, I can see that there are multiple problems with your code. Though, the main problem is the fact that you try to read attributes of a framework property, and expect it to contain your own defined attributes (which, as far as I know isn't even possible).

So, let's analyze the call @item.DisplayAttribute(p => p.Name). I assume here, that item refers to the same item in your previous razor code, which would mean it has a type of MethodInfo (from System.Reflection). And you call your method DisplayAttribute with the following expressions:

DisplayAttribute<MethodInfo>(MethodInfo item, Expression<Func<MethodInfo, string>> mi => mi.Name);

Bear in mind that this isn't actually a valid call in C#, but I've added the type information to easier show what is going on.

Now, the MemberExpression you acquire inside the DisplayAttribute method, is not the controller method at all, it's the property string MethodInfo.Name {get;}. This is a framework property (defined in mscorlib as far as I know), and obviously does not have your defined property.

What you probably wanted to do, was something akin to this instead:

public static string GetAttributedName(this ICustomAttributeProvider attributeProvider) {
    return attributeProvider.GetCustomAttributes(typeof(DisplayNameAttribute), true).Select(a => a.Title).FirstOrDefault() ?? "No title found";
}

The bit at the end with "No title found" is just to make it simpler to find where the problem lies (if this does not work). If you want it to throw an exception if there is no attribute, simply replace FirstOrDefault with First, and remove the part after the method call.

[Edit]

Forgot to mention: usage would be like this: @item.GetAttributedName()

[Edit 2]

I made some improvements, you can see a basically working sample here: https://dotnetfiddle.net/7H5ITo.

Sign up to request clarification or add additional context in comments.

Comments

1

If the element action or controller isn't decorated with the DisplayName attribute it is pretty normal that you will get an IndexOutOfBounds exception. You might check for that in your extension method:

public static string DisplayAttribute<T>(this T obj, Expression<Func<T, string>> value)
{
    var memberExpression = value.Body as MemberExpression;
    var attr = memberExpression.Member.GetCustomAttributes(typeof(DisplayNameAttribute), true);
    if (attr.Length > 0)
    {
        return ((DisplayNameAttribute)attr[0]).Title;
    }

    // Return some default value
    return memberExpression.Member.Name;
}

Comments

0

For another name of your action use Display(name="") attribute. For example:

Display(name="View Action Name")
public ActionResult ActionName()
{
  return View();
}

to get action view name, in View modify it with

@foreach (var item in Model)
    {
        <li>
            @item.Name
            <ul>
                @foreach (var action in item.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(method => typeof(ActionResult).IsAssignableFrom(method.ReturnType)))
                {
                    try
                    {
                        <li>@action.CustomAttributes.SingleOrDefault(m => m.AttributeType.Name == "DisplayAttribute").NamedArguments.ElementAt(0).TypedValue</li>
                    }
                    catch(NullReferenceException){}



                }
            </ul>

        </li>
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.