POST method called on MVC UserControls as well as their parent views - asp.net-mvc

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.

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.

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

Related

MVC Child action [duplicate]

I have some partial actions that I render with the Asp.Net Futures RenderAction method. Some of these perform redirects after the forms in them have been processed.
Now that I upgraded to Asp.Net MVC 2 RC it gives me an error "Child actions are not allowed to perform redirect actions".
I checked out the source code and I found the line that throws the exception. To Get around it I can make a custom RedirectResult, But before I do I want to understand why the framework doesn't allow it in the first place. There must be a good reason and maybe I Shouldn't do either.
Any one know the reason for this limitation?
Thanks
The limitation exists because MVC has already started rendering a view to the client. The effect of redirecting from this point is undefined. It could work perfectly, it could continue rendering the original view without redirecting, it could throw a different exception, etc.
Since the result of performing this action is undefined, the framework blocks it. In practice, RenderAction should never be used to render anything other than a view (or view-like content) for similar reasons.
In your particular case, the outer action should redirect. If you're just going to end up redirecting from within the view anyway without showing anything to the user, then there was really no purpose to going through the view in the first place, as the outer action could have delegated the work appropriately on its own.
Try to use something like this in Child Action:
ControllerContext.HttpContext.Response.Redirect(ControllerContext.HttpContext.Request.Url.ToString());
My solution.
Action method:
return View("Redirect", model);
View:
<script type="text/javascript" language="javascript">
document.location = '<%: Url.Action("Index", "Album", new { id = Model.Id }) %>';</script>
In my case, the form being rendered is a "configure" panel on an extension to a website I'm building. I'd like the extension's own controller to be able to handle the form processing and then redirect back to the admin page listing all configured extensions. I don't think it's appropriate or practical here to ask the parent page's controller to process the form for the extension. What would you suggest I do instead?
In my unusual case, I had a custom AuthorizeAttribute attached to my controllers which was attempting to redirect on a child action, which is (as mentioned above) not allowed.
To resolve the issue, I removed authorisation checking redirection on all child actions:
Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
//Child actions cannot redirect anyway, so no need to check permissions.
If filterContext.IsChildAction Then Exit Sub
.. parent authorisation checks ..
Sometimes this error occured when you try to render an action of base action result.
Example:
ActionResult X
Return View
View X
RenderAction Y
ActionResult Y
// Bla bla
return View
// else
return RedirectToAction X
In that case just point the partial view form's submit url to action that was the target of your problematic redirection and let it perform itself redirection to its GET version.

A public action method '..' was not found on controller '..'

I wanted to put a random image on every viewpage of my mvc project. So i created a method that returns a partialView and call that method in the shared Layout page.
This works fine when I try to login with a correct username and password. The used is loged in and every page contains a random image. But when I give the invalid combination of username and password. The shared layout page does not find the controller I want to call with my #Html.Action and actualy the login view should be returned with an error message 'invalid combination of username and password' and ofcourse, with the random image.
InnerException:
{"A public action method 'RandomSponsor' was not found on controller 'Project.WebUI.Controllers.HomeController'."}
My Html.Action in shared layout.
#Html.Action("RandomSponsor", "Home")
Method in homecontroller.
[HttpGet]
[ChildActionOnly]
public ActionResult RandomSponsor()
{
var model = service.getRandomSponsor();
return PartialView("RandomSponsor", model);
}
The getRandomSponsor method works fine, this one always returns one random string value that is returned to the RandomSponsor.cshtml view.
RandomSponsor.schtml (only contains the image string)
<img src="~/Content/Images/Advert/#(Model)" alt="a" />
I searched the web for this problem but didn't found a solution, does anyone know the answer to this one?
Might it be something with HttpGet of HttpPost?
Regards.
If the executing request is a POST, then it will try to find a method RandomSponsor accepting HttpPost. If this makes sense, you could remove HttpGet and that should do the trick.
This can also happen if you have many layers of calls that start with a POST (I had an action returning a view returning a partial view calling RenderAction), then the call to RenderAction will still look for a POST method
Very similar to this problem that I had here - How to solve "public action method 'methodActionName' was not found on controller 'controllerNameController'"
And if you want to continue to accept the HTTP GET verb and fix the problem of cascading post request into a get request add this to your method
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
Keep in mind that [HttpGet] is the same as [AcceptVerbs(HttpVerbs.Get)]
This will happen if the request is a POST but the controller method is annotated [HttpGet]. For example, you might issue a POST that returns a view containing partial views called with #Html.Action, using controller methods annotated with [HttpGet]. If the original request is a POST, all of the controller methods subsequently called will need to support POST.
To fix it you can use the AcceptVerbs attribute to specify that your controller method accepts both POST and GET:
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get)]
Received this error all of the sudden on several different PartialViews (not all of them) when customizing an install of MVCForum. We had not made any changes to the methods or views concerning the errors so it was really frustrating as to why they were broken.
After trying the other solutions on this post and others, went back through the changes made and what ended up stopping the errors was that we had changed the bindings in IIS to another domain that had the 'enforce lower case url' URL Rewrite rule enabled.
When we disabled the enforce lowercase rule, the errors stopped and the site worked as it was supposed to. It's not a URL Rewrite issue (I don't think) because we are able to enforce www using it with no errors. It's a lowercase rewrite issue. Didn't matter if we had the lowercase rule before or after the www rule.
This solution probably doesn't apply to many cases of this error, but it worked for us. Hopefully someone else can benefit from such a simple fix.
I just solved this issue strangely enough on my local PC, by making sure my entire request path was lower case. So give that a try.
I know this is a pretty old thread - but as it's top Google result I thought I'd add a potentially missing link for MVC.Net 5.2.6.
Scenario
I was attempting to call a child action via #Html.Action("ActionName", new { Id = 123})
and received an error much like the above, but none of the other solutions worked. I could hit the controller action externally (i.e. HttpGet), but the child action kept throwing the exception and was driving me nuts!
The solution I found
After two-ing and fro-ing for some time, I started playing with my routing attributes. I had the controller set up as:
[Route("{action}")]
[RoutePrefix("Prefix")]
[RouteArea("AreaName")]
As there was only one public action i wanted, "Index", I removed the {action} and placed an explicit route attribute on the public action and put my ChildActionOnly attribute back on the child.
After I did that, I hit the run and hey presto - the action was hit.
Might be worth a try if you're getting this error while using attribute routing. Note I did attempt to route the child action and this didn't work.
In my case, the same issue was happening randomly with the implicit :
using (Html.BeginForm())
Changing above to :
using (Html.BeginForm("Action","Controller", FormMethod.Post))
fixed this issue.
Did you give it a shot with Html.RenderAction? It is typically faster then Html.Action as it interact directly into the response stream as opposed to building a string.
You can view the following topics for more info:
What is the difference (if any) between Html.Partial(view, model) and Html.RenderPartial(view,model) in MVC2?
Html.Partial vs Html.RenderPartial & Html.Action vs Html.RenderAction
Another thing to note is that for Html.Action or Html.RenderAction, your view doesn't need to be in Shared folder, that is only required if you use Html.Partial or Html.RenderPartial

How can I validate partial view that is returned from controller

I have a view page with ajax.action link which returns a partial view from controller and render it to divid as updated target id. But I could not perform client side validation on that partial view.
Can I have solution for it?
When you load a partial view's html with ajax it is normal for the JavaScript code not to be executed. Especially if you have calls to functions attached to onload event since this event is fired long before the ajax call is executed. Check out this article http://adammcraventech.wordpress.com/2010/06/11/asp-net-mvc2-ajax-executing-dynamically-loaded-javascript/ it describes all sorts of problems that you can have with this approach. If you want a more specific answer it will be good to proivide more info on your setup like - what version of .net/asp.net mvc you are using and what validation framework are you trying to use.

ASP.NET MVC Catching 'Save' POST with RenderPartial

I have a asp.net mvc page which renders a record from a database, it uses RenderPartial to call another view which renders an editable list of items related to that record.
My problem is I want a since save / submit button which not only saves changes made for that record but also changes made in the RenderPartial part... I have created a method accepting POST in the RenderPartials controller but it doesn't get called? Any ideas? Or am I using RenderPartial wrongly? I did it this way so that I have a controller that handles the subset of data
Update:
I don't think I've been clear enough:
Imagine a situation where you have a page that is filled with information from lots of different tables in a database... for example imagine you have a record of a person, and then you have all the links they have to Organisations that you want to list on the page, so the page contains:
Individual Name, Email, etc...
AND
Organisation Link 1
Organisation Link 2, etc... from a link table
Because of the amount of data I want to render of the page, I figured using different controllers to render each part would make sense.. but then when saving the data do I have to use just one controller method or can I call one controller to another... I only have one form and one 'save' button for the whole page
I hope this is clearer?
You don't need to have a specific controller for each controller, although I suspect you meant to say "I have created a method accepting POST in the RenderPartial's action ..."?
When you accept the default html helper commands, it can sometimes get confusing which action will be called. For a particular web page, the tag will determine where to POST values. So the action called will be dependent on the controller/action you specify in your Html.BeginForm call.
For example, here's a form we use from an ascx page:
<% using (Html.BeginForm("InvoiceDetail" //Action
, "CompanyInvoice" //Controller
, FormMethod.Post
, new { #id = "InvoiceForm", #autocomplete = "off" }))
{%>
A form can post to any action, in any controller. Not that you'd want to, but it's quite flexible. You can also have multiple forms in a single web page now, which is great for complex forms.

Why are Redirect Results not allowed in Child Actions in Asp.net MVC 2

I have some partial actions that I render with the Asp.Net Futures RenderAction method. Some of these perform redirects after the forms in them have been processed.
Now that I upgraded to Asp.Net MVC 2 RC it gives me an error "Child actions are not allowed to perform redirect actions".
I checked out the source code and I found the line that throws the exception. To Get around it I can make a custom RedirectResult, But before I do I want to understand why the framework doesn't allow it in the first place. There must be a good reason and maybe I Shouldn't do either.
Any one know the reason for this limitation?
Thanks
The limitation exists because MVC has already started rendering a view to the client. The effect of redirecting from this point is undefined. It could work perfectly, it could continue rendering the original view without redirecting, it could throw a different exception, etc.
Since the result of performing this action is undefined, the framework blocks it. In practice, RenderAction should never be used to render anything other than a view (or view-like content) for similar reasons.
In your particular case, the outer action should redirect. If you're just going to end up redirecting from within the view anyway without showing anything to the user, then there was really no purpose to going through the view in the first place, as the outer action could have delegated the work appropriately on its own.
Try to use something like this in Child Action:
ControllerContext.HttpContext.Response.Redirect(ControllerContext.HttpContext.Request.Url.ToString());
My solution.
Action method:
return View("Redirect", model);
View:
<script type="text/javascript" language="javascript">
document.location = '<%: Url.Action("Index", "Album", new { id = Model.Id }) %>';</script>
In my case, the form being rendered is a "configure" panel on an extension to a website I'm building. I'd like the extension's own controller to be able to handle the form processing and then redirect back to the admin page listing all configured extensions. I don't think it's appropriate or practical here to ask the parent page's controller to process the form for the extension. What would you suggest I do instead?
In my unusual case, I had a custom AuthorizeAttribute attached to my controllers which was attempting to redirect on a child action, which is (as mentioned above) not allowed.
To resolve the issue, I removed authorisation checking redirection on all child actions:
Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
//Child actions cannot redirect anyway, so no need to check permissions.
If filterContext.IsChildAction Then Exit Sub
.. parent authorisation checks ..
Sometimes this error occured when you try to render an action of base action result.
Example:
ActionResult X
Return View
View X
RenderAction Y
ActionResult Y
// Bla bla
return View
// else
return RedirectToAction X
In that case just point the partial view form's submit url to action that was the target of your problematic redirection and let it perform itself redirection to its GET version.

Resources