How a Method Becomes An Action

This is one of them “coming of age” stories about how a lowly method becomes a full fledged Action in ASP.NET MVC. You might think the two things are the same thing, but that’s not the case. It is not just any method gets to take the mantle of being an Action method.

Routing

Like any good story, it all begins at the beginning with Routing. By default, one of the routes defined in the MVC project template has the following URL pattern:

{controller}/{action}/{id}

When a request comes in and matches that route, we populate a dictionary of route values (accessible via the RequestContext) based on this route. For example, if a request comes in for:

/home/list/123

We add the key “action” with the value “list” to the route values dictionary (We’ve also added “home” as the value for “controller”, but that’s for another story. This is the story of the action.) At the heart of it, an action is just a string. That’s how it starts out after all, as a sub string of the URL.

Later on, when the request is handed of to MVC, MVC interprets the value in the route values for “action” to be the action name. In this case, it knows that the request should be handled by the action “list”. Contrary to popular belief, this does not necessarily mean that a method named List will handle this request, as we’ll soon see.

Action Method Selection

Once we’ve identified the name of the action, we need to identify a method that can respond to that action. This is the job of the ControllerActionInvoker.

By default, the invoker simply uses reflection to find a public method on a class that derives from Controller which has the same name (case insensitive) as the current action.

Like many things within this framework, you can tweak this default behavior.

ActionNameAttribute

Introduced in ASP.NET MVC CodePlex Preview 5 which we just released, applying this attribute to a method allows you to specify the action that the method handles.

For example, suppose you want to have an action named View, this would conflict with the View method of Controller. An easy way to work around this issue without having to futz with routing or method hiding is to do the following:

[ActionName("View")]
public ActionResult ViewSomething(string id)
{
  return View();
}

The ActionNameAttribute redefines the name of this action to be “View”. Thus this method is invoked in response to requests for /home/view, but not for /home/viewsomething. In the latter case, as far as the action invoker is concerned, an action method named “ViewSomething” does not exist.

One consequence of this is that if you’re using our conventional approach to locate the view that corresponds to this action, the view should be named after the action, not after the method. In the above example (assuming this is a method of HomeController), we would look for the view ~/Views/Home/View.aspx by default.

This attribute is not required on an action method. Implicitly, the name of a public method serves as the action name for that method.

ActionSelectionAttribute

We’re not done yet matching the action to a method. Once we’ve identified all methods of the Controller class that match the current action name, we need to whittle the list down further by looking at all instances of the ActionSelectionAttribute applied to the methods in the list.

This attribute is an abstract base class for attributes which provide fine grained control over which requests an action method can respond to. The API for this method is quite simple and consists of a single method.

public abstract class ActionSelectionAttribute : Attribute
{
  public abstract bool IsValidForRequest(ControllerContext controllerContext
    , MethodInfo methodInfo);
}

At this point, the invoker looks for any methods in the list which contain attributes which derive from this attribute and calls the IsValidForRequest() method on each attribute. If any attribute returns false, the method that the attribute is applied to is removed from the list of potential action methods for the current request.

At the end, we should be left with one method in the list, which the invoker then invokes. If more than one method can handle the current request, the invoker throws an exception indicating the problem. If no method can handle the request, the invoker calls HandleUnknownAction() on the controller.

The ASP.NET MVC framework includes one implementation of this base attribute, the AcceptVerbsAttribute.

AcceptVerbsAttribute

This is a concrete implementation of ActionSelectionAttribute which uses the current HTTP request’s http method (aka verb) to determine whether or not a method is the action that should handle the current request.

This allows for having two methods of the same name (different parameters of course) to both be actions, but respond to different HTTP verbs.

For example, we may want two versions of the Edit method, one which renders the edit form, and the other which handles the request when that form is posted.

[AcceptVerbs("GET")]
public ActionResult Edit(string id)
{
  return View();
}

[AcceptVerbs("POST")]
public ActionResult Edit(string id, FormCollection form)
{
  //Save the item and redirect…
}

When a POST request for /home/edit is received, the action invoker creates a list of all methods of the controller that match the “edit” action name. In this case, we would end up with a list of two methods. Afterwards, the invoker looks at all of the ActionSelectionAttribute instances applied to each method and calls the IsValidForRequest() method on each. If each attribute returns true, then the method is considered valid for the current action.

For example, in this case, when we ask the first method if it can handle a POST request, it would respond with false because it only handles GET requests. The second method responds with true because it can handle the POST request and it is the one selected to handle the action.

Helpers

One consequence to keep in mind when using helpers which use our routing API to generate URLs is that the parameters for all of these helpers take in the action name, not the method name. So if I want to render the URL to the following action:

[ActionName("List")]
public ActionResult ListSomething()
{
  //...
}

Use “List” and not “ListSomething” as the action name.

<!-- WRONG! -->
<%= Url.Action("ListSomething") %>

<!-- RIGHT! -->
<%= Url.Action("List") %>

This is one reason you’ve seen the MVC team resistant to including helper methods, such as Url<T>(…), that use an expression to define the URL of an action. The action is not necessarily equivalent to a method on the class with the same name.

Summary

So in the end, an action is a logical concept that represents an event caused by the user (such as clicking a link or posting a form) which is eventually mapped to a method which handles that user event.

It’s convenient to think of an action as a method of the same name, but they are distinct concepts. A lowly method can become an action by the power of its own name (aka name dropping), but in this egalitarian framework, any method, no matter its name, can handle a particular action, by merely using the ActionNameAttribute.

Technorati Tags: ,

What others have said

Requesting Gravatar... Simone Aug 29, 2008 3:35 AM
# re: How a Method Becomes An Action
Phil, I think the routing rule in the sample should be {controller}/{action}/{id}, no {id}/{action}/{id}

And about the ActionName attribute. This is a nice way to add many view with the same name, but I really like the correspondence between methods and actions since it allowed the Url helpers that provided compile-time check and intellisense when writing redirect to actions and url to actions.
So, not sure whether to like this new feature or not :)
Requesting Gravatar... David Alpert Aug 29, 2008 3:50 AM
# re: How a Method Becomes An Action
I have to agree with Simone, initially i liked the "ActionName" attribute.

In the end, however, if that means that i cannot find a way to do this:


foreach (int productID in (List)listOfProductIDs) {
string route = Html.ResolveRoute(h => h.ShowProduct(productID);
}


then i'd drop the attribute like a hot potato in favor of a lambda-based method to resolve routes in my views.
Requesting Gravatar... haacked Aug 29, 2008 5:44 AM
# re: How a Method Becomes An Action
Simone, thanks for the tip. I corrected my post. The thing is, an action has always been a string that just happened to map to a method on a controller. All along, you could have written action filters that change that string, thus destroying the tenuous connection between an action name and method.

For strongly typed checking, there are other techniques you can use, such as a static class of custom made route values so that all your strings are in one place. etc... Use your imagination, I think you'll figure out other ideas.

Remember, the request starts off as a string, even if we took away ActionName, that doesn't suddenly make the URL helpers work in all cases.
Requesting Gravatar... shawn Aug 29, 2008 7:27 AM
# re: How a Method Becomes An Action
Great post Phil.

I agree completely with the "action is just a logical construct" concept as it enforces loose coupling from View to Controller.
I'm relieved to see the ASP.NET MVC architects finally help express this for developers via these new additions in preview 5.

Frankly, while undeniably tempting, I consider controller lambda expressions being used in a view as a gross violation of the pattern!

Requesting Gravatar... Justice~! Aug 29, 2008 7:58 AM
# re: How a Method Becomes An Action
"This is one reason you’ve seen the MVC team resistant to including helper methods, such as Url(…), that use an expression to define the URL of an action. The action is not necessarily equivalent to a method on the class with the same name."

Can't it be resolved nonetheless if the action we're calling is decorated with an attribute, though? I mean, it seems odd to me that if we're breaking down the action in that way we wouldn't be able to substitute the ActionName assigned to that method. At least, I would figure that's the way it should be.

Requesting Gravatar... Jonathan Aug 29, 2008 5:44 PM
# re: How a Method Becomes An Action
Excellent article Phil.

"The ASP.NET MVC framework includes one implementation of this base attribute, the AcceptVerbsAttribute."

Let's not forget about the NonActionAttribute :)
Requesting Gravatar... Andy Aug 29, 2008 10:32 PM
# jQuery and MVC
Phil,

I am really keen to use MVC but my hesitation revolves around the native integration with jQuery ?

i.e. MS Ajax is integrated heavily with ASP.NET but it seems that jQuery is not. This is something that I find really frustrating as native integration should really exist for a lib as popular as jQuery ?

Is there plans to integrate this natively or ? As currently, the benefits combining MS Ajax and MVC far outweigh the benefits of using jQuery and MVC.

Thoughts? Love a post or some further indication from you in this regard.

Cheers,

Andy
Requesting Gravatar... CarlH Aug 29, 2008 11:00 PM
# re: How a Method Becomes An Action
A agree with Simone, compiletime checking of URLs is a absolute requirement, especially for a huge site.
Requesting Gravatar... Timothy Khouri Aug 30, 2008 12:02 AM
# re: How a Method Becomes An Action
Phil, I know I bug you a lot about what else I wish to see in MVC... but this feature is just another

one of those 100% solid and well done items.

Back in the Preview 4 starter template in the Register action of the AccountController... the whole

if (Request.HttpMethod != "POST")
{
return View();
}
thing was really bugging me. It didn't seem like a natural way to handle the scenario

(oh, btw, that wasn't changed in the Preview 5 template).

Thanks for the awesome product,
-Timothy Khouri

P.S. I'm not just trying to butter you up because I've seen your angry side.
Requesting Gravatar... Magnus Mårtensson Aug 30, 2008 4:19 PM
# re: How a Method Becomes An Action
I love the new ActionName and AcceptedVerbs attributes!

They really help make a well evolved controller more intuitive to coders! I have a sample at my site where the Index action GETs from one method and POSTs to another!

Cool stuff for sure!

Cheers, /Magnus
Requesting Gravatar... mvcFan Aug 31, 2008 5:45 PM
# Preview 5 & ActionName
Although they are distinct concepts, it's practical to have an action as a method of the same name.
With ActionName, the problem has just moved: You still have to find a name for "ViewSomething".

I think it's much simpler to adopt a solid naming convention. Instead of using "View", you can still use "Index", "Details", "MyView" or anything else.

Personally, the 2 methods with the same name ("Edit") is another bad example, as "Edit" can be used to display the form and "Update" to update the database (see weblogs.asp.net/.../...ontroller-action-names.aspx).

According to me, ActionName was a waste of time.

I don't know when I will update my application to Preview 5, as it seems that there are a lot of breaking changes (See http://forums.asp.net/1146.aspx). Now I am a bit afraid.
Requesting Gravatar... haacked Sep 01, 2008 3:15 AM
# re: How a Method Becomes An Action
@Andy There’s nothing preventing anyone from using JQuery with MVC. I even did a screencast where I demonstrated its use. In P5, one of our motivations for refactoring all the Ajax helpers to be extension methods in their own namespace was to enable people to write their own helpers using JQuery.

I'd love to do it myself, but I imagine others out there will jump on this opportunity to supply the community with those helpers.
Requesting Gravatar... Steve Sanderson Sep 01, 2008 9:34 PM
# re: How a Method Becomes An Action
> Although they are distinct concepts, it's practical
> to have an action as a method of the same name.

I have to agree with mvcFan. Not yet going so far as to say "ActionName was a waste of time", but I am curious about what the intended use case is here.

As with anything in software, an extra abstraction layer adds flexibility, but it also adds an extra level of cognitive friction. Why would developers want to have to keep track of which action methods correspond to which "action names"? When placing a link to another action method, doesn't the developer usually want to say *exactly which* actual C# method should be called, not obscuring that intention behind a mapping layer that might in fact cause the link to go to some other method? There is such a thing as too many abstraction layers :)

The fact that this destroys any possibility of strongly-typed link helpers and redirection helpers, reducing the whole API to a string-based system, is unfortunate too. Is it really worth it? Who was asking for action names? Can you cite compelling use cases?

I'm sure you'll come along and explain how action names are great and then we'll all be happy. Until then, curious...
Requesting Gravatar... Justice~! Sep 02, 2008 12:13 AM
# re: How a Method Becomes An Action
I'm still waiting to hear about the possibility of using the [ActionName] as something to aid in compile-time URL generation. Sincerely though the lack of compile-time checking on URLs seems like a very strange design decision, defeating the potential of using MVC on a large site. Am I misreading this?
Requesting Gravatar... Steve Sanderson Sep 02, 2008 1:10 AM
# re: How a Method Becomes An Action
@Justice: You're right, having generic helpers infer the action name from a detected [ActionName] attribute would be possible, but it would make a mockery of the whole system. You'd then use one action name when you reference it with a string, and a different name when you reference it with a lambda expression.
Requesting Gravatar... haacked Sep 02, 2008 1:46 AM
# re: How a Method Becomes An Action

@Steve @Justice The ability to have the strongly typed URL helpers was already an illusion, though a compelling one. There were many other ways that an action might not correspond to a method of the same name. For example, a filter might have tweaked things, a custom route could tweak things, a controller factory could tweak things, a custom action invoker could tweak things.

An action truly is just a string, as is an URL. Routing and MVC are loosely coupled. We could try really really hard to tightly couple the two and make the action-to-method a one-to-one mapping, but to make that really work, we'd have to remove all those layers of extensibility.

Hence, we're moving the strongly typed helpers to MvcFutures. If you don't use any of these extensibility layers, they'll sort of work. But we don't want to support them given that at all layers, you can cause a breakage inadverdently via the extensibility layers. The fact that an action really is a logical concept is not something we made up. It's been that way all along. We're just trying to expose that fact so people are aware of it and the consequences of treating it otherwise.

Requesting Gravatar... Justice~! Sep 02, 2008 4:32 AM
# re: How a Method Becomes An Action
@Steve - I agree with you but then again I'm not all that keen on the "ActionName" aspect. I respect that my bias may be showing! ;)

@Haacked - I do agree with you from the coupling standpoint (in fact, I've done some interesting stuff with routing that couldn't use the strongly typed stuff). I think it's probably a case of the MVC team finding its footing because I remember presentations, etc. making a very big deal about strongly typed routing styles.

As a secondary concern, I noticed Image and ImageButton were moved to Futures as well in p4 (which I assume has to do with the weird REsolveUrl() in MVC vs. in WebForms). The implication seems to be that the Extension methods are moved into futures because they're not going to be further supported. Is that what is happening with Image and ImageButton?
Requesting Gravatar... haacked Sep 02, 2008 7:40 AM
# re: How a Method Becomes An Action
@Justice that is indeed the case. The MvcFutures project is where we put features that we're considering for future versions of ASP.NET MVC.

Some of the helpers we don't feel add all that much value for the QA cost it would entail. It seems like some of those are trivial, but when you add up a bunch of trivial items, it starts to total alot of work.
Requesting Gravatar... Aybar Dumlu Sep 11, 2008 7:52 AM
# re: How a Method Becomes An Action
cool stuff, thank you
_________________________
http://www.aybardumlu.com
Requesting Gravatar... code Sep 17, 2008 8:48 AM
# re: How a Method Becomes An Action
this is the kind of stuff i want to read about on this blog
Requesting Gravatar... celik Sep 20, 2008 8:41 AM
# re: How a Method Becomes An Action
I like AcceptVerbs attribute. Makes your code cleaner instaed of if then else loops.
Requesting Gravatar... Steve Sep 24, 2008 9:48 AM
# re: How a Method Becomes An Action
Strongly typed helpers was one of the biggest wins of asp.net mvc.

If anything, I'd like the checking down on builds.

I am using this everyday, real life stuff. Now theories, and these strongly typed helpers are extremely valuable.
Requesting Gravatar... Kaitlyn Sep 29, 2008 9:05 AM
# re: How a Method Becomes An Action
Great explanation, thank you for the post
Requesting Gravatar... Sime Dec 23, 2008 6:03 AM
# re: How a Method Becomes An Action
ActionName attribute is a good candidate for REST approach !
Requesting Gravatar... Nik Radford Feb 05, 2009 9:08 PM
# re: How a Method Becomes An Action
Hmm, but even using the helper methods, couldn't you still use reflection to discover the actual action name?
Requesting Gravatar... Tiendq Feb 12, 2009 4:02 PM
# re: How a Method Becomes An Action
I agree with mvcFan too, you don't need to use Edit again for update action and then use HTTP verb to find correct action method.
Requesting Gravatar... buy liquor online Dec 16, 2009 5:26 AM
# re: How a Method Becomes An Action
that really was a "coming of age" story!
Requesting Gravatar... Dave Jan 31, 2010 12:59 PM
# re: How a Method Becomes An Action
Why doesn't it respect the controller name in the lookup for the action.

If you have a base controller with common actions you cannot override any of these actions from derived controllers.

you get the ambiguous action methods error?


Requesting Gravatar... Brian Kendig Mar 09, 2010 5:58 AM
# re: How a Method Becomes An Action
Phil said: "An action truly is just a string." Okay, then I want some function call that'll generate an action string to refer to a specific controller method.

I don't often tweak action names via filters, custom routes, controller factories, or custom action invokers. I *do* often use links from web pages to other web pages. Having to use "magic strings" for this is not a workable option for large sites; there's no type checking of arguments, and it's very hard to trace down all the places which call a specific method if I change that method's name or arguments.

It seems right now that strongly-typed link helpers are somewhat broken (in MVC 1, at least); an action I invoke via Html.RenderAction<MyController>(c => c.MyAction()) will have any Html.ActionLink() calls in its view spit out empty hrefs. I was hoping things would improve in MVC 2, but instead it looks like MVC 2 is moving farther from this and closer to magic strings!

Am I supposed to tough up and tolerate magic strings, or is there a best-of-both-worlds way out there?

Requesting Gravatar... Property auctions Aug 26, 2010 4:17 PM
# re: How a Method Becomes An Action
I too think that both method and action are the same.Thanks for the info you have made.
Requesting Gravatar... mvcvm Sep 07, 2011 6:41 PM
# re: How a Method Becomes An Action
The ControllerFactory and ControllerActionInvoker are not quite clever enough to detect the difference in arguments when posting from a strongly typed view:

"... The current request for action X on controller type Y is ambiguous between the following action methods: X(MyModel) on type Y and X(TheModel) on type TheController ..."

public class TheController: Controller
{
[HttpPost]
public virtual ActionResult X(TheModel model)
{ ... }
}

public class Y: TheController
{
[HttpPost]
public virtual ActionResult X(MyModel model)
{ ... }
}

As the signatures differ, neither overriding nor hiding is involved.

This would be useful for inheriting from any frameworks and then quickly adding special behaviour.
Requesting Gravatar... Saeed Neamati Jan 30, 2012 2:29 PM
# re: How a Method Becomes An Action
This resolves my issue, for having similar methods, with similar names and signatures, but having different HttpMethod attributes on top of them. One method should server HTTP Get, and another method the HTTP Post (post parameters would be extracted from Request object, rather than letting MVC bind them).

This scheme was impossible. But by using ActionNameAttribute, I think I'll make it work.

Thanks

What do you have to say?

(will show your gravatar)
Please add 2 and 3 and type the answer here: