RSS

Tag Archives: Post

MVC and posting data using Html.BeginForm and Url Routing

We all have pages which represent “fill in” data forms. Quite common is a user’s details form: what’s your first name, what’s your last name, email, date of birth etc. This can be coded in various ways, but I thought that this would be a good way to get to know ASP.NET MVC’s Html.BeginForm( ). While there’s also an Ajax.BeginForm(), I think that the two may differ mainly in the desired behavior after you have completed posting and saving the form data. For example, if you’d like to redirect your user to some other view, or display an entirely new View altogether, you might prefer using Html.BeginForm( ). However, you may want to simply display an informatory “Saved successfully” message, and in that case you’d probably consider using Ajax.BeginForm( ) or simply jQuery’s post.

The thing is that Routing could make this a little tricky (as it was in my case). If a certain Route led to a Controller and resulted in a View with a “fill in” details form, the generated html Form’s Action attribute is going to be that exact route which you originally used. So how on earth are you supposed to save your data, if the same route was used to originally render your details in the first place? Sounds confusing? I also thought so till I figured it out. Lets clarify this issue with a real world example. Suppose your website allows users to view and edit their personal profile. Your route could be like so:

Users would be able to use: http://<server or site name>/profile/myUsername to view and edit their profile. Our server side code residing in the AccountController is the following:

Note that the controller has a LoadProfile action method (which corresponds to the route action shown earlier), and a SaveProfile action method. In order to have the form data posted to SaveProfile, we need to provide some arguments to Html.BeginForm( ):

Basically, this works. The View renders fine, the submit button posts the data to SaveProfile as expected and everything seems great. But, I’m only missing one thing: I don’t have the original username from which the Form originally rendered. In order words, I have no idea at this time to which user the filled in data relates to. This was available from the Route url when the profile was loaded, but it’s unavailable now, when Html.BeginForm( ) uses custom data for posting. I thought that this would be easy to accomplish, because the Html.BeginForm( ) has overloads which accept routing data, but it turned out that when I provided those, other Routes I have in my application got prioritized and this gets ugly, because it means that I’ll have to provide more routes or route constraints in order for this to work. And this was just one form in my app, and it doesn’t make sense that I’ll have to do this for all the forms I’ll develop.

This led me to a different question altogether: Why should I explicitly supply BeginForm( ) with an action or controller in the first place? How come that in MVC’s templates there aren’t such arguments when calling Html.BeginForm( ), and yet when posting data, the correct action is invoked? I noticed that if I provided no arguments to Html.BeginForm( ), the default action was a “profile/{username}” route, just like what I used for loading the user’s profile. I was puzzled over this, because I didn’t understand how MVC was supposed to differentiate between loading a profile and saving a profile, as the Route used for both operations is identical. I looked up MVC’s template and found an example for ChangePassword. Turns out that there are a couple of ChangePassword( ) overloads. The first overload received no arguments (for “loading” the form), but the second overload received a Model with the data (“saving” the form). So how does the route knows which method should be invoked?

The answer lies in an [HttpPost] attribute which is placed over the “saving” action method. The route is quite clever and the action type (either GET or POST) is used to differentiate between actions of the same name. This makes sense, because usually a GET operation is to be used for loading a form, and a POST operation is usually used to posting and saving a form. I went ahead and tried it: I removed the arguments from Html.BeginForm( ), changed LoadProfile and SaveProfile action methods simply to “Profile“, added [HttpPost] to the Profile action method which performs the saving operation, and changed the route accordingly. Here’s how this looks:

If we use two action methods by the same name without [HttpPost] on one of them, the run-time throws a “The current request for action ‘Profile’ on controller type ‘AccountController’ is ambiguous between the following action methods” exception.

Now I could use the RouteData collection for retrieving the user name, as well as other form fields which were sent over as arguments.

 
4 Comments

Posted by on 09/05/2011 in Software Development

 

Tags: , , , , , ,