BeginUmbracoForm applying incorrect action to form (form rendered via ajax)
Hi everyone,
Really, really hoping someone can help me, I've got into a routing SNAFU.
My situation is that I have a page, which calls a Partial View Macro, itself calling a list of items via a Html.Action call, this in turn loads my Controller, calls the action, renders the partial and displays the list. This all works fine.
I am using bootstrap.js and populating the contents of the modal with jQuery.ajax calls. Each item has a call to action that shows the popup with a form on it.
The Ajax call that retrieves the form sits on the Controller, and renders the Partial view (with correct Form Model) to a string. The string is returned via Ajax and rendered onto the modal area with jQuery before showing said Modal.
Everything works fine with displaying the form. Unobtrusive validation works, the Model seems to work, however the problem is the action attribute of the resultant form is not correct.
Here's the example Code. For example the page url is '/myItems'. THe DocType and Template are excluded for simplicity. here's the simplified PMV template for the list that is rendered on the page:
Pretty straigh forward so far, right? Ok so now comes the MyItemsSurfaceController, also simplified.
namespace MyProject.Controllers
{
public class MyItemsSurfaceController : SurfaceController
{
//display the list view
public ActionResult GetListView()
{
return PartialView("_myList", new MyListViewModel());
}
//This is the submission end point for the pop up form
[HttpPost]
public ActionResult SubmitMyForm(MyFormViewModel FormValues)
{
//check valid
if (!ModelState.IsValid) { return CurrentUmbracoPage(); }
//TODO : Integrate handling here
return RedirectToCurrentUmbracoPage();
}
//This method is used by jQuery Ajax to get the popup contents
public JsonResult GetMyForm(e) {
if (!HttpContext.Request.IsAjaxRequest()) { return Json(new { success = false });}
//get the form
string formView = ViewToString("_myForm", new MyFormViewModel());
if (String.IsNullOrWhiteSpace(formView)) { return Json(new { success = false });}
return Json(new { success = true, id = "my-form", content = formView });
}
//Renders a view to a string
private string ViewToString(string viewName, object model)
{
try
{
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
catch
{
}
}
You can see my intent here. So now here's the simplified Form Model and View.
Model:
public class GuestPassFormViewModel
{
[Required]
[StringLength(50)]
public string Value { get; set; }
}
So, thats the lot, for completeness here's the javascript that loads the form:
function MyPopOut($) {
$.ajax({
url: '/umbraco/surface/clubLocatorSurface/GetGuestPassForm/',
async: false,
type: 'POST',
dataType: 'json',
contentType: 'application/json, charset=utf-8'
}).success(function (data) {
if (data.success) {
//this is just a jQuery function that populates the modal div
createModelContent('modal--' + data.id, data.content);
} else {
alert("Unable to show Form");
}
}).error(function () {
alert("Unable to show Form");
});
}
And heres the Html content result in the pop up. As I said the form function fine but submits ot the wrong place. where I need the action to be '/myList/' so it hits the SubmitMyForm action, it instead sets action to that supplied by the URL from the ajax call (/umbraco/surface/clubLocatorSurface/GetGuestPassForm/).
<form method="post" enctype="multipart/form-data" action="/umbraco/surface/clubLocatorSurface/GetGuestPassForm/">
<div>
<input type="text" value="" placeholder="Enter Value" name="Value" id="Vlaue" data-val-required="The Value field is required." data-val-length-max="50" data-val-length="The field Value must be a string with a maximum length of 50." data-val="true" class="text">
<span data-valmsg-replace="true" data-valmsg-for="Value" class="field-validation-valid"></span>
</div>
<div>
<input type="submit" value="submit" class="submit">
</div>
<input type="hidden" value="04E13ACAB1B2E300FBC8A5DC267C0CD251DF0E981AABE9ED785BDBD83044B2E99C992E423C39A02F8704BE7E3C27125ADA88608E08E7E57F4CBEFCD3C8C54D1FF298FF1168C5B84B9B22834B3265BEFBDE186506224C4A7A2AED81883AC491D3F7D1AA5C3239A11670D5B92BDFE2A3E918D7E800DEFE85181070007E63DB62F388525412AA59D6B1DCE88E1F1DD96F5AA8DDE34A9456357E239DD5E201D181B4" name="ufprt">
</form>
So there, it is. Lots of writing but a simple question How do I make this Form post to the correct place??
I suspect it is down to the HttpContext or ControllerContext following the Ajax call and subsiquent View to string process, however I am at a loss as to where to go with it as the BeginUmbracoForm seems correct...
Please, pelase can anyone help?? I'll b monitoring this question all day so please ask questions and I'll try out/expalin whatever I can. I do hope its as simple as "Change the HttpContext somehow" :)
I have a work around, but I'm sure this isn't the right way to go about it...
It seems it's all to do with the HttpContext within the GetMyForm method of the controller. When this executes the HttpContext in in the context of the Ajax posted to URL, of course. Deep in The Umbraco Html Helper this is used to implicitly set the formAction (var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery;).
However RenderForm (which is called within BeginUmbracoForm) allows the overriding of this via the HtmlAttributes parameter of BeginUmbracoForm. Therefor what I've done is change my GetMyForm method as so:
//This method is used by jQuery Ajax to get the popup contents
public JsonResult GetMyForm(e) {
if (!HttpContext.Request.IsAjaxRequest()) { return Json(new { success = false });}
//Because this is a 'from ajax only' request, we need to modify the return path
MyFormViewModel Mdl = new MyFormViewModel();
Mdl.PostBack = HttpContext.Request.UrlReferrer.AbsolutePath;
//get the form
string formView = ViewToString("_guestPass", Mdl);
if (String.IsNullOrWhiteSpace(formView)) { return Json(new { success = false }); }
return Json(new { success = true, id = "my-form", content = formView });
}
Extend the ViewModel to accept the PostBack property of course, then within the form's template grab the property if not empty and apply to a Dictionary for BeginUmbracoForm:
Now, I'm not convinced this is the correct solution, so I'm not marking this as solved just yet.
If one of the Masters here can confirm that this solution is acceptible then that's grand, I'll mark as solved. However I am quite certain there is a better/proper way to do this so I defer the final answer to anyone that cares to comment!
See my response above, the solution has proven to be suitable (its used on a production site right now :D)
Basically, overload your call to BeginUmbracoForm and add htmlAttributes parameter, in here supply an anonymous object with this value (and any other attributes you wish to add) :
Yea a real fix for this would be great. Seems while @Html.Action works fine, in a script block using @UrlAction the URL gets hyjacked and '/umbraco/surface' gets prepended to the controller name
BeginUmbracoForm applying incorrect action to form (form rendered via ajax)
Hi everyone,
Really, really hoping someone can help me, I've got into a routing SNAFU.
My situation is that I have a page, which calls a Partial View Macro, itself calling a list of items via a
Html.Action
call, this in turn loads my Controller, calls the action, renders the partial and displays the list. This all works fine.I am using bootstrap.js and populating the contents of the modal with jQuery.ajax calls. Each item has a call to action that shows the popup with a form on it.
The Ajax call that retrieves the form sits on the Controller, and renders the Partial view (with correct Form Model) to a string. The string is returned via Ajax and rendered onto the modal area with jQuery before showing said Modal.
Everything works fine with displaying the form. Unobtrusive validation works, the Model seems to work, however the problem is the action attribute of the resultant form is not correct.
Here's the example Code. For example the page url is '/myItems'. THe DocType and Template are excluded for simplicity. here's the simplified PMV template for the list that is rendered on the page:
Pretty straigh forward so far, right? Ok so now comes the
MyItemsSurfaceController
, also simplified.You can see my intent here. So now here's the simplified Form Model and View. Model:
View:
So, thats the lot, for completeness here's the javascript that loads the form:
And heres the Html content result in the pop up. As I said the form function fine but submits ot the wrong place. where I need the action to be '/myList/' so it hits the
SubmitMyForm
action, it instead sets action to that supplied by the URL from the ajax call (/umbraco/surface/clubLocatorSurface/GetGuestPassForm/
).So there, it is. Lots of writing but a simple question How do I make this Form post to the correct place??
I suspect it is down to the HttpContext or ControllerContext following the Ajax call and subsiquent View to string process, however I am at a loss as to where to go with it as the BeginUmbracoForm seems correct...
Please, pelase can anyone help?? I'll b monitoring this question all day so please ask questions and I'll try out/expalin whatever I can. I do hope its as simple as "Change the HttpContext somehow" :)
Thanks in advance,
Danny "Blatant"
I have a work around, but I'm sure this isn't the right way to go about it...
It seems it's all to do with the HttpContext within the
GetMyForm
method of the controller. When this executes the HttpContext in in the context of the Ajax posted to URL, of course. Deep in The Umbraco Html Helper this is used to implicitly set the formAction (var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery;
).However RenderForm (which is called within BeginUmbracoForm) allows the overriding of this via the HtmlAttributes parameter of BeginUmbracoForm. Therefor what I've done is change my
GetMyForm
method as so:Extend the ViewModel to accept the PostBack property of course, then within the form's template grab the property if not empty and apply to a Dictionary for BeginUmbracoForm:
Now, I'm not convinced this is the correct solution, so I'm not marking this as solved just yet.
If one of the Masters here can confirm that this solution is acceptible then that's grand, I'll mark as solved. However I am quite certain there is a better/proper way to do this so I defer the final answer to anyone that cares to comment!
Kind Regards,
Danny "Blatant"
I am encountering the exact same problem! It's a real pain. Still haven't found a workout around for this. Anyone able to help?
Hi Jacob,
See my response above, the solution has proven to be suitable (its used on a production site right now :D)
Basically, overload your call to BeginUmbracoForm and add htmlAttributes parameter, in here supply an anonymous object with this value (and any other attributes you wish to add) :
Kind Regards,
Danny Blatant
Anyone found a solution please?
Thanks
Yea a real fix for this would be great. Seems while @Html.Action works fine, in a script block using @UrlAction the URL gets hyjacked and '/umbraco/surface' gets prepended to the controller name
is working on a reply...