tags:

views:

217

answers:

3

I'm trying to write a unit test that will loop through all action methods on my controller classes looking for action methods that don't have some security-related attribute on them (e.g. [Authorize]).

How does the ASP.NET routing engine determine which methods are action methods? Obviously the action methods have to be public, but methods like ToString() are not action methods, so there is some logic to this.

+7  A: 

All methods in a controller are considered Actions, except for non-public methods.

First, methods are matched by name: MethodName = ActionName.

If you want to override the default behavior, you use [ActionName] attribute.

public MyController
{
    [ActionName("ActionY")]
    public MethodX ()
    {
    }
}

Then this method will fire when a http://..../ActionY url is requested.

Read more in Phil's blog: How a Method Becomes An Action

EDIT: Okay, maybe this one: All public methods defined directly in your controller class, not inherited from base classes, not overriding those in base classes, unless you recursively identified those base methods to be actions already, not decorated with NoAction attribute.

User
"All methods in a controller are considered Actions, except for non-public methods."- Not true, ToString() is a public method but is not an action method.
Jon Kruger
To add to this, you can also add a [NonAction] attribute to a public method if you need it to be public for unit testing purposes.
dhulk
Okay, that one is obviously not, but you got the general idea. You inherit from the controller class and what public methods you define there will become your actions.
User
@dhulk: yes, you are correct.
User
@Mastermind - I know I'm being picky, but my goal is to write a unit test that verifies that there is a security-related attribute on all action methods, so I have to have the same algorithm that the routing engine is using.
Jon Kruger
@Jon Kruger upvote for that idea of unit test. Got to remember. :)
Arnis L.
+1  A: 

I stopped being lazy and I found the answer, most of which was in System.Web.Mvc.ActionMethodSelector.PopulateLookupTables() (thanks Reflector!)

    private IEnumerable<MethodInfo> GetActionMethods(Type controllerType)
    {
        return Array.FindAll(controllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance), IsValidActionMethod);
    }

    private static bool IsValidActionMethod(MethodInfo methodInfo)
    {
        return (!methodInfo.IsSpecialName && !methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(Controller)) && 
            !methodInfo.GetCustomAttributes(typeof(NonActionAttribute), true).Any());
    }

I was surprised to see all of the public methods on my base controller classes that were exposed with no security on them!

Jon Kruger
If you need all of your action methods in a controller to have the [Authorize] filter on them, I'm pretty sure you could just add it to the top of the controller. It has the same effect as adding it to every action. I don't believe it will affect the non-action methods but I could be mistaken about that.
dhulk
+1  A: 

There is a really nice explanation of how Action Methods are processed in the ASP.NET MVC book I'm using for reference. As it happens, the relevant pages are available on Google books:

http://books.google.com/books?id=Xb3a1xTSfZgC&amp;lpg=PA308&amp;ots=J9GgXhsads&amp;dq=action%20method%20selection%20sanderson&amp;pg=PA310

In case the link dies, it's Page 310 of Pro ASP.NET MVC Framework by Steven Sanderson.

Not sure that it will help you figure out how to write your unit tests, but it might help you follow the Reflector code...

Greg Malcolm
That's a great article! Thanks!
Jon Kruger