tags:

views:

1016

answers:

2

Rewritten: My original post seemed to be misunderstood. I have since reported it as a bug with the following description. My original post for this question can be found below the second <HR>.


I have a major issue with POST in a user control.

  • I have a UserControl which has a controller containing two ActionMethods called 'ContactForm'. The second has ActionVerb.POST on it to respond to the post-back. The user control is primarily used via AJAX - but that actually is irrelevant here. Obviously these action methods render a partial view.

  • I have a main page containing an additional html form for 'EnterContest'. Again - its controller has two ActionMethods called 'EnterContest', one of which responds to ActionVerb.POST. This view contains the 'ContactForm' in a side bar which is rendered with :

    <% Html.RenderAction("ContactUsForm", "Company", new { commentsBoxHeader = "Questions" }); %>

The problem occurs when posting back the 'EnterContest' Form (the main form on the view).

The POST request from Fiddler contains just this query string (obviously not containing any of the POST data from the contact us form because thats a completely separate HTTP ACTION).

contestId=ND09&email=fred&btnEnterContest=Submit

(yes, this looks like a GET but thats what Fiddler shows for a POST too)

  • First - (as expected) - the 'EnterContest(FormCollection data) method is called in the main controller. This processes the form submission to enter the contest - calls the webservice etc..

  • Second - (NOT expected) - The POST method of the 'ContactForm' controller is called. This immediately crashes because it is missing expected parameters - and we don't want it called anyway. This happens during the same Http request.

If I take a look at the stack trace - it is being called from the dynamically generated aspx page - originating at the Html.RenderAction line of code shown above. So obviously what is happening is the code that is looking to partially render the 'ContactUs' action method looks at the Request and sees there is a method to handle POST so it routes it there - which is VERY BAD. Its probably somewhere around this method in the framework :

System.Web.Mvc.dll!System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod

This behavior is VERY confusing and really breaks what seemed to be a simple page. I'm pretty sure it is a bug - because I cannot see an elegant work around without some really clumsy checking in my controller. I think RenderAction is in futures - but I don't know if the issue is present there or in the main framework.

Just to clarify what is NOT happening :

  • any clever jQuery
  • more than one HTTP request (verified in Fiddler)
  • nested forms

Thanks


Original post

I am using the RenderAction Html extension in ASP.NET MVC.

I came across something unexpected, but which is making more sense as I think about it.

Lets say I have a view containing a 'RenderAction' method to generate the contact for a part of the page.

<% Html.RenderAction("ContactUsForm", "Company", 
   new { commentsBoxHeader = "Questions" }); %>

In this case the partial view it generates creates an ajax form which posts back via <%= Html.BeginAjaxForm() %>.

So of course I need an actionresult to handle the AJAX postback.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ContactUsForm(FormCollection formdata)

Now the problem occurs when the parent view containing this partial render action has a normal (non-ajax) form with a POST action. What happens is that the POST method for the ContactUsForm action is call in addition to the POST action for the main view. Inside this action the formdata property contains all the properties for the parent view - so ContactUsForm dies with a null reference or something like that.

I've come up with 3 possible solutions :

1) create a different action name for the post back for any user controls on the page. This has the disadvantage that you have to post back to a different function than created the partial view. Often this can be more cumbersome but this is what i'm doing right now.

2) check in every POST method (you'd have to remember to run this check in every user control's POST action method) to see if the form data is intended for that form it, and if not return a default view.

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResultContactUsForm(FormCollection formData)
    {
        if (formData["btnSubmitContactUsForm"] == null) {

            // "This form is not for us!";
            // figure out how (if is possible) to return the get default view here
            // and call it with the right arguments
        }
    }

3) report it as a bug.

What should I be doing here? I'm leaning towards thinking of this as a bug

Edit: One very important thing I need to stress more is that BOTH POST methods are called - so its not just somethig like a nested form.

Edit 2: In Fiddler I see only one request. The problem is when it tries to render the ContactUsForm after having already handled the POST for my main page it hits the 'POST' method for 'ContactUsForm' instead of the non-post handler.

+2  A: 

EDIT2: I just noticed that you are using RenderAction instead of RenderPartial. I suspect what is happening is that it is using the RequestContext of the posted form to choose which ContactUsForm method to choose when the RenderAction is invoked. This is arguably correct behavior since the action is being invoked from a postback, just not the one you intended. I would approach this in a completely different manner. Have the partial generated by a ViewUserControl and include it on the page using RenderPartial instead. Remove the GET ContactUsForm method and only have the POST version. That is, the form itself is generated as a ViewUserControl with markup predetermined or dynamically generated via parameters passed via ViewData. The form response is handled via the controller action.

EDIT: Since you indicate that nesting is not the issue, is it possible that you are using javascript (say jQuery) to trigger the submit and your selector is too broad. If, for example, you had code like the following, that would account for the behavior you are seeing.

$(document).ready( function() {
    $('#mybutton').click( function() {
       $('form').submit();
    });
});

Original answer: (left for context)

It sounds like you have nested forms in your view. Try moving the RenderAction outside the form in the parent view and see if that fixes your problem. My feeling about forms in MVC views is that they should be compact and only cover the markup that contains the actual inputs. This is a change from WebForms where you typically wrap all of your mark up within the form. Use CSS to control layout if you need to have the form elements appear to be intermixed.

tvanfosson
thats something i hadn't thought about, but in my case it isn't the issue. i've got two forms in completely separate regions and i just verified that the tags don't overlap. thats definitely something to watch out for. could lead to some unexpected symptoms!
Simon_Weaver
@Simon: if that's the case then I would reword the whole section beginning with "Now the problem occurs when..." as that paragraph leads the reader to assume your problem is with nested forms.
Crescent Fresh
@tvanfosson - sorry i seemed to abandon this post for a day. i'm back now and i've rewritten the problem with more clarification. i've pretty much concluded its a bug and wondering about workarounds.
Simon_Weaver
+1  A: 

This is actually a confirmed bug both in ASP.NET MVC 1.0 with MVC Features library and in ASP.NET MVC 2.0. RenderAction behaves incorrectly when the request is POST. I have submitted the bug in ASP.NET Issue Tracker on Codeplex, please vote for it :) http://aspnet.codeplex.com/WorkItem/View.aspx?WorkItemId=5847

David