Copied to clipboard

Flag this post as spam?

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


  • Chester Campbell 98 posts 209 karma points
    Jun 07, 2021 @ 15:00
    Chester Campbell
    0

    Converting Umbraco Forms to be an AJAX form

    Hi,

    This was originally a reply on an old thread in a now-archived forum: https://our.umbraco.com/forum/umbraco-pro/contour/60788-Umbraco-Forms-and-Ajax ... I'm reposting it here so it'll get more visibility.

    Essentially, the old thread was a discussion on how to convert Umbraco Forms to use an AJAX form. I ran into a problem deep inside UF and am stuck ...

    I'm trying this in Umbraco 8 and I am getting an error that I don't know how to resolve. First, my code:

    My controller:

    using System.Web.Mvc;
    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Forms.Core.Data.Storage;
    using Umbraco.Forms.Core.Providers;
    using Umbraco.Forms.Core.Services;
    using Umbraco.Forms.Web.Controllers;
    using Umbraco.Forms.Web.Models;
    using Umbraco.Web;
    using Umbraco.Web.Cache;
    
    namespace myProject.Controllers
    {
        public class UmbracoFormsAjaxController : UmbracoFormsController
        {
            private readonly DistributedCache _distributedCache;
            private readonly IFormStorage _formStorage;
            private readonly IRecordStorage _recordStorage;
            private readonly IRecordService _recordService;
            private readonly FieldCollection _fieldCollection;
            private readonly IFieldTypeStorage _fieldTypeStorage;
            private readonly IFieldPreValueSourceService _fieldPreValueSourceService;
            private readonly IFieldPreValueSourceTypeService _fieldPreValueSourceTypeService;
            private readonly IUmbracoContextAccessor _umbracoContextAccessor;
    
            private IPublishedContent _basePage;
    
            public UmbracoFormsAjaxController(
                DistributedCache distributedCache,
                IFormStorage formStorage,
                IRecordStorage recordStorage,
                IRecordService recordService,
                FieldCollection fieldCollection,
                IFieldTypeStorage fieldTypeStorage,
                IFieldPreValueSourceService fieldPreValueSourceService,
                IFieldPreValueSourceTypeService fieldPreValueSourceTypeService,
                IUmbracoContextAccessor umbracoContextAccessor) 
                : base(
                    distributedCache,
                    formStorage,
                    recordStorage,
                    recordService,
                    fieldCollection,
                    fieldTypeStorage,
                    fieldPreValueSourceService,
                    fieldPreValueSourceTypeService,
                    umbracoContextAccessor)
            {
                _distributedCache = distributedCache;
                _formStorage = formStorage;
                _recordStorage = recordStorage;
                _recordService = recordService;
                _fieldCollection = fieldCollection;
                _fieldTypeStorage = fieldTypeStorage;
                _fieldPreValueSourceService = fieldPreValueSourceService;
                _fieldPreValueSourceTypeService = fieldPreValueSourceTypeService;
                _umbracoContextAccessor = umbracoContextAccessor;
            }
    
            protected override IPublishedContent CurrentPage
            {
                get
                {
                    return _basePage;
                }
            }
    
            [HttpPost, ValidateInput(false), ValidateAntiForgeryToken]
            public ActionResult HandleFormAjax(FormViewModel model, int currentPageId)
            {
                // set base page
                _basePage = _umbracoContextAccessor.UmbracoContext.Content.GetById(currentPageId);
    
                // handle form
                HandleForm(model);
    
                // return rendered view only
                return RenderForm(model.FormId, model.RecordId, model.Theme, true);
            }
        }
    }
    

    Here's my customer Render.cshtml in my theme directory:

    @inherits WebViewPage<Umbraco.Forms.Web.Models.FormViewModel>
    @using Umbraco.Forms.Mvc.BusinessLogic
    @using Umbraco.Web.Models
    @{
        Html.EnableClientValidation(true);
        Html.EnableUnobtrusiveJavaScript(true);
    
        //Get the actual view to render the form html
        string formThemedView = FormThemeResolver.GetFormView(Model);
    
        //get the script.cshtml view to render client-side assets
        string formScriptView = FormThemeResolver.GetScriptView(Model);
    }
    @if (Model.SubmitHandled)
    {
        @Html.Partial(formThemedView);
    }
    else
    {
        <div id="umbraco_form_@(Model.FormClientId)" class="umbraco-forms-form @(Model.CssClass) umbraco-forms-@(Model.Theme)">
    
            @using (Ajax.BeginForm("HandleFormAjax", "UmbracoFormsAjax", new AjaxOptions
            {
                InsertionMode = InsertionMode.ReplaceWith,
                UpdateTargetId = "umbraco_form_" + Model.FormClientId,
                AllowCache = false
            }))
            {
                @Html.AntiForgeryToken()
    
                @Html.HiddenFor(x => Model.FormId, new { id = (string)null })
                @Html.HiddenFor(x => Model.FormName, new { id = (string)null })
                @Html.HiddenFor(x => Model.RecordId, new { id = (string)null })
                @Html.HiddenFor(x => Model.PreviousClicked, new { id = (string)null })
                @Html.HiddenFor(x => Model.Theme, new { id = (string)null })
    
                if (TempData.ContainsKey("parent"))
                {
                    <input type="hidden" name="currentPageId" value="@(((IPublishedContent)TempData["parent"]).Id)" />
    
                    TempData.Remove("parent");
                }
                <input type="hidden" name="FormStep" value="@(Model.FormStep)" />
                <input type="hidden" name="RecordState" value="@(Model.RecordState)" />
                @Html.Partial(formThemedView)
            }
    
            @if (Model.RenderScriptFiles)
            {
                @* Render the scripts.cshtml file to included standard conditionals and validation logic *@
                Html.RenderPartial(formScriptView);
            }
    
        </div>
    }
    

    Then, my partial that calls the RenderMacro helper:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage
    @{
        Layout = null;
        TempData["parent"] = Umbraco.AssignedContentItem;
    }
    
    <div>
        @Umbraco.RenderMacro("renderUmbracoForm", new { FormGuid = "db967296-c296-493b-8da9-7e3cf2f0b090", FormTheme = "newsletter-subscription", ExcludeScripts = "1" })
    </div>
    

    So, at this point my form does render on the page and the field validation works and the form submits via AJAX to my HandleFormAjax() action. However, inside that action at the line HandleForm(model); I get the following error:

    System.NullReferenceException
      HResult=0x80004003
      Message=Object reference not set to an instance of an object.
      Source=Umbraco.Forms.Core
      StackTrace:
       at Umbraco.Forms.Core.Services.WorkflowService.GetPageElements()
       at Umbraco.Forms.Core.Services.WorkflowService.ExecuteWorkflows(List`1 workflows, RecordEventArgs e)
       at Umbraco.Forms.Core.Services.WorkflowService.ExecuteWorkflows(Record record, Form form, FormState state, Boolean editMode)
       at Umbraco.Forms.Core.Services.RecordService.Submit(Record record, Form form)
       at Umbraco.Forms.Web.Controllers.UmbracoFormsController.SubmitForm(Form form, FormViewModel model, Dictionary`2 state, ControllerContext context)
       at Umbraco.Forms.Web.Controllers.UmbracoFormsController.GoForward(Form form, FormViewModel model, Dictionary`2 state)
       at Umbraco.Forms.Web.Controllers.UmbracoFormsController.HandleForm(FormViewModel model)
       at myProject.Controllers.UmbracoFormsAjaxController.HandleFormAjax(FormViewModel model, Int32 currentPageId) in C:\temp_source\projectfolder\mySolution\myProject\Controllers\UmbracoFormsAjaxController.cs:line 73
    

    Something is blowing up inside of WorkflowService.GetPageElements() and I'm guessing that "Page" refers to the current Umbraco page. And, of course, since this process was kicked off by an AJAX call there is no current Umbraco page (as far as the process is aware).

    In UmbracoFormsAjaxController we're setting the CurrentPage property when the form makes the AJAX call but apparently that's not enough?? I have looked around at the other properties available to me and I don't see anything further I can do.

    I'm hoping that someone else might have an idea?

    BTW; just to re-iterate. The whole reason I'm doing this is so that Umbraco Forms will work on pages that are being Output Cached. Yes, I build all my widgets on my pages so that render time is at a minimum, but Output Caching still greatly improves the responsiveness of the site and so I'm loath to just turn it off.

    Using Umbraco Forms shouldn't be a choice between Output Caching or Forms. They should both work together.

    Does anyone have an ideas where I can go from here?

    Thanks!

  • Chester Campbell 98 posts 209 karma points
    Jun 07, 2021 @ 15:01
    Chester Campbell
    0

    Ugh, I want this to appear in the Umbraco-Forms forum. Why can't I pick the forum when creating a new topic!?

Please Sign in or register to post replies

Write your reply to:

Draft