Gain clarity on action methods and how the action invoker works in ASP.NET MVC
I've found that a lot of people are not clear on how an MVC applicatiocn selects which action method to execute in response to a URL request from a user. To unlock the secrets, you need to understand how the action invoker works. The job of the action invoker to select the appropriate action method is more complicated than may appear on the surface. There could be multiple overloaded methods with the same name in a controller, they may be decorated with different attributes that make their selection appropriate in some scenarios and not others, their action method name may be different from their class method name, and other complications. Designing an effective controller requires that you understand how the selection process works.
The first thing that the action invoker does is to look for the action portion of the route definition for the request. Using the default route defined in a new MVC application shown below, it is the name at the second position.
Even if the current URL doesn't include the action portion of the route, the default route definition defines it to be the "Index" action.
Once the action invoker has the name of the action, it needs to map that action name to a method of the controller. The simplest case occurs when there is a single method in the controller class with a name that matches the action name. For example, in a new MVC application, the Index action of the Home controller maps to the Index() method in that controller.
But what qualifies as an action method? Is every method defined in the controller a potential action method? Not at all. There are a few requirements that a method must meet for the action invoker to consider it to be an action method candidate:
- It must be a public method and not marked as static or shared.
- It cannot be a method defined on System.Object or the Controller base class. In other words, it must be defined within your custom controller object, not inherited from another object. So ToString() could not be an action method because it is defined on System.Object.
- It cannot be a special method, such as a constructor, property accessor, or event accessor.
- It cannot have the NonActionAttribute decoration. This attribute is handy if you want to include a public method in a controller but don't want to use it as an action method.
Taking all these restrictions into consideration, the action invoker uses reflection to find all methods in the controller with the same action name as the action name specified in the URL and meet the above requirements.
But here is where things get a bit murky. Through the use of attributes on the controller's methods, there could be multiple methods with the same name, the action name could be different from the method name, and there may be a selector attribute that restricts what requests an action method can respond to. This can create a bit of a gauntlet that the action invoker must navigate to find the correct action method, so we'll next explore these action method features.
The normal convention is that the method name defined in the controller is also the action name. This keeps things simple and straightforward, but you can also give an action method a different action name. Keep in mind that the action invoker matches up the route action name with the method's action name, which may not be the same as the method name.
Why might you want to give a method a different action name? One reason would be that you want to use an action name that is not a legal C# or VB method name. Another reason is that you want to use an MVC component name as the action name, such as View, which would conflict with the View method of the Controller object. Or maybe you want to use a different naming standard for action methods and controller methods. None of these are things you should do lightly, but it's good to have the flexibility when you need it.
An example should help sort this out. Let's say that you have a method defined in the Home controller called GetAuthor(), which returns information about the author of the module. (This is a contrived example, but it will serve our needs here.) The following code shows how the method is defined. It simply returns a hard-coded string to the browser.
public string GetAuthor()
return "Don Kiely";
If you leave the action name the same as the method name, the URL used to invoke the method will look like this:
That looks a little funky, and you'd prefer that the URL look like this instead:
All you'd to do is add the ActionName attribute to the method like this:
public string GetAuthor()
return "Don Kiely";
Once you add that attribute, "GetAuthor" is totally invisible as an action method name. If you try to invoke the method using the /Home/GetAuthor URL, you'll get a resource cannot be found HTTP 404 error. The action invoker sees this as an action method with the name "Author," not "GetAuthor."
Below is code that creates the links using the GetAuthor and Author action method names. One or the other will return an HTTP 404 error depending on whether you have applied the ActionName attribute as shown in the previous code. The second parameter specifies the desired action method name.
<p><%:Html.ActionLink("Get Author", "GetAuthor", "Home")%>
<%:Html.ActionLink("Author Action", "Author", "Home")%>
Once the action invoker has identified all the controller methods that match the desired action name, it examines the ActionMethodSelector attributes of all the candidate methods. You can decorate a controller method with this attribute to control under what conditions a controller method should agree to handle a particular type of request.
The attribute defines only a single method, IsValidForRequest, which the invoker executes on each of the candidate controller methods. If IsValidForRequest returns false for a controller method, that method is removed from the list of candidate action methods.
The ASP.NET MVC framework includes several implementations of the ActionMethodSelector attribute. One is NonActionAttribute. A controller method with the NonAction attribute is immediately removed from consideration.
The AcceptVerbsAttribute is more interesting. It uses the HTTP verb of the current request to determine whether the method should handle the request. This means that you could have multiple overloaded methods in the controller class designed to respond to different HTTP verbs, such as GET, POST, DELETE, and so on. It takes a single parameter with a list of the HTTP verbs that the method will handle.
Easier to use are the HTTP verb-specific classes that derive from ActionMethodSelectorAttribute: HttpDeleteAttribute, HttpGetAttribute, HttpPostAttribute, and HttpPutAttribute. Each corresponds to a single HTTP verb and accepts no parameters.
The Account controller, created as part of a new MVC Web application project, has two Register() methods in the controller. These are overloaded: One takes no parameters, and the other takes a model object as a parameter. The latter method is decorated with an HttpPost attribute, which means that it will handle requests that use the HTTP POST verb. The other Register method will handle requests that use any other HTTP verb, most commonly the GET verb.
public ActionResult Register()
public ActionResult Register(RegisterModel model)
The Final Selection
Once the action invoker has run this complete gauntlet, it should have whittled down the list of candidate action methods to exactly one. If there remains more than one candidate that can handle the request, the action invoker throws an exception indicating that there is an ambiguous match. It there is no method that can handle the request, the invoker invokes the unknown action handler, the HandleUnknownAction method of the Controller class. By default, this method returns an HTTP 404 status code indicating that the resource was not found. But you can override the method and do whatever you want to in response.
As you can see, the process the action invoker uses to select an action method isn't too complicated, although there is more than I've covered here. As long as you follow the rules and conventions of ASP.NET MVC, you shouldn't have any problem, and there are lots of options to handle special situations.
Don Kiely (firstname.lastname@example.org), MVP, MCSD, is a senior technology consultant, building custom applications and providing business and technology consulting services. His development work involves SQL Server, Visual Basic, C#, ASP.NET, and Microsoft Office.