Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Danny Blatant 91 posts 358 karma points
    Oct 30, 2014 @ 12:54
    Danny Blatant
    0

    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:

    @inherits Umbraco.Web.Macros.PartialViewMacroPage
    @Html.Action("GetListView", "MyItemsSurface");
    

    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; }
    }
    

    View:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<MyProject.Models.MyFormViewModel>
    @using (Html.BeginUmbracoForm<MyProject.Controllers.MyItemsSurfaceController>("SubmitMyForm", null ) 
    {
        <div>
            @Html.TextBoxFor(m => m.Value, new { @class = "text", placeholder = "Enter vALUE" })
            @Html.ValidationMessageFor(m => m.Value)
        </div>
    }
    

    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" :)

    Thanks in advance,

    Danny "Blatant"

  • Danny Blatant 91 posts 358 karma points
    Oct 30, 2014 @ 14:07
    Danny Blatant
    0

    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:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<MyProject.Models.MyFormViewModel>
    @using (
        Html.BeginUmbracoForm<MyProject.Controllers.MyItemsSurfaceController>(
            "SubmitMyForm", 
            null, 
            (String.IsNullOrWhiteSpace(Model.PostBack) ? null : new { Action = Model.PostBack } )
        )
    ) 
    {
        <div>
            @Html.TextBoxFor(m => m.Value, new { @class = "text", placeholder = "Enter Value" })
            @Html.ValidationMessageFor(m => m.Value)
        </div>
    }
    

    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"

  • Jacob Polden 67 posts 177 karma points
    Mar 17, 2015 @ 12:48
    Jacob Polden
    0

    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?

  • Danny Blatant 91 posts 358 karma points
    Mar 17, 2015 @ 14:40
    Danny Blatant
    1

    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) :

    new { Action = "/my/post/path" }
    

    Kind Regards,

    Danny Blatant

  • Simon 692 posts 1068 karma points
    Dec 09, 2015 @ 15:23
    Simon
    0

    Anyone found a solution please?

    Thanks

  • Greg Miller 9 posts 79 karma points
    Nov 08, 2016 @ 20:44
    Greg Miller
    0

    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

Please Sign in or register to post replies

Write your reply to:

Draft