Copied to clipboard

Flag this post as spam?

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


  • Moshe Recanati 8 posts 88 karma points
    Aug 21, 2017 @ 05:28
    Moshe Recanati
    0

    Multi-page Form - save data

    Hi, We're in a middle of strategic project to our company. As part of this project we're using Umbraco Forms and with the multi-page form options. However it's mandatory for us to save the data between pages so the user can return back to see the data and continue the process. In addition the marketing team need to know if users got stuck in the process in between.

    I've searched for a solution for this (via coding) however until now I didn't found concrete guide on how to implement it in Umbraco Forms. I saw other packages might support it (Umbraco Contour or FormEditor) but I really don't want to go back to old versions and loose Umbraco Forms benefits. In addition I purchased it and seems to me like basic functionality to be able to save partial data in between pages.

    Appreciate your help here since our project got stuck due to this issue.

    Thank you in advance, Moshe

  • Jamie Brunton 3 posts 73 karma points
    Aug 08, 2018 @ 15:12
    Jamie Brunton
    0

    Did you find a solution to this?

  • J 370 posts 625 karma points
    Jun 04, 2019 @ 10:39
    J
    0

    Would be great to know if anyone found a solution to this?

  • Tom van Enckevort 101 posts 413 karma points
    Jun 06, 2019 @ 07:40
    Tom van Enckevort
    103

    Yes, it is still possible to partially save forms, but it does take a few steps to configure it:

    Go to App_Plugins\UmbracoForms\UmbracoForms.config and change the AllowEditableFormSubmissions setting to true.

    In an ApplicationEventHandler class, do the following during the ApplicationStarted event:

    protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
    {
        UmbracoFormsController.FormPrePopulate += (object sender, FormEventArgs e) =>
        {
            // nothing needed here, it just needs to exist to save all form data when submitting a form with multiple form pages
        };
    }
    

    The records saved will be in the Partially Submitted state when a user moves between pages, until the user hits the final page and clicks the Submit button.

    That should work for the current user's session. If you wish to load a partially submitted form in a future session, you probably need to set a cookie value with the record ID and then pass that into the macro or action you use to render the form on the page.

    I only found out about these steps by decompiling the Forms DLLs and checking how the code works there (and some trial and error), so I don't think it's an officially supported feature :)

    [Edit] One more thing: the partially submitted entries won't show up in the Forms entries dashboard in the CMS, unless you untick the Approved and Submitted filters at the top of the list.

  • Damiaan 439 posts 1292 karma points MVP 4x c-trib
    Jun 06, 2019 @ 07:46
  • J 370 posts 625 karma points
    Jun 06, 2019 @ 12:29
    J
    0

    Thanks Tom, Im using Umbraco 7.5 with Forms 4.4.7.

    I dont seem to have the event FormPrePopulate

    Tried using

    Umbraco.Forms.Web.Controllers.UmbracoFormsController.FormPrePopulate
    

    which threw the error

    'Umbraco.Forms.Web.Controllers.UmbracoFormsController' does not contain a definition for 'FormPrePopulate'

    Would you know which class i need to add/target?

    Thanks again for your input

  • Tom van Enckevort 101 posts 413 karma points
    Jun 06, 2019 @ 13:01
    Tom van Enckevort
    1

    I was using Umbraco Forms v7.x and I just had a look at the DLL for 4.4.7 and the FormPrePopulate method is not there, so it must've been added later.

    From what I can see from the code in that version, it should work with just the AllowEditableFormSubmissions setting enabled.

  • J 370 posts 625 karma points
    Jun 06, 2019 @ 15:52
    J
    0

    Thanks again, this time i am monitoring 2 tables to see which one changes when i click next on a multi-form. The two tables are [UFRecordFields], [UFRecords] - where i THINK the partial record would show.

    Unfortunately it doesnt update any table where i think the data would be stored for an unfinished form (I get to page 2 before i check the tables). I have made the one change

    AllowEditableFormSubmissions = "true"
    

    All other settings are the default, no console errors and i didnt add the method for ApplicationStarted.

    The only time a record is created is when i submit the form and its stored in the [UFRecords] table.

    Any other thoughts?

  • Tom van Enckevort 101 posts 413 karma points
    Jun 06, 2019 @ 16:14
    Tom van Enckevort
    4

    So in order to also save the partially submitted record in the database when the page gets changed you need to jump through a few more hoops.

    First of all, create a new SurfaceController that inherits from the default UmbracoFormsController:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Web;
    using System.Web.Hosting;
    using System.Web.Mvc;
    using System.Web.Security;
    using Umbraco.Core.Configuration;
    using Umbraco.Forms.Core;
    using Umbraco.Forms.Core.Common;
    using Umbraco.Forms.Core.Enums;
    using Umbraco.Forms.Data.Storage;
    using Umbraco.Forms.Mvc.Attributes;
    using Umbraco.Forms.Mvc.BusinessLogic;
    using Umbraco.Forms.Mvc.Models;
    using Umbraco.Forms.Web.Controllers;
    using Umbraco.Forms.Web.Services;
    using Umbraco.Web.Routing;
    using Umbraco.Web.Security;
    using UmbracoWeb = Umbraco.Web;
    
    namespace MyProject.Controllers.Surface
    {
        public class FormsController : UmbracoFormsController
        {
            /// <summary>
            /// Handles form submission for saves and submits.
            /// </summary>
            /// <param name="model"></param>
            /// <param name="captchaIsValid"></param>
            /// <returns></returns>
            [ValidateCaptcha]
            [ValidateFormsAntiForgeryToken]
            [HttpPost]
            [ValidateInput(false)]
            public ActionResult HandleFormSubmission(FormViewModel model, bool captchaIsValid)
            {
                if (Request["__prev"] != null || Request["next"] != null)
                {
                    // save the current form as partially submitted
                    // this calls a bunch of private methods from the base controller
                    var form = BaseGetForm(model.FormId);
    
                    model.Build(form);
    
                    model.FormState = BaseExtractAllPagesState(model, ControllerContext, form);
    
                    BaseStoreFormState(model.FormState, model);
                    BaseResumeFormState(model, model.FormState, false);
    
                    SaveForm(form, model, model.FormState, ControllerContext);
    
                    TempData[$"FormSaved.{model.FormId}"] = "true";
    
                    // redirect back to current page
                    return RedirectToCurrentUmbracoPage();
                }
                else
                {
                    // submit form like normal
                    var result = HandleForm(model, captchaIsValid);
    
                    return result;
                }
            }
    
            /// <summary>
            /// Saves the form entry as partially submitted.
            /// </summary>
            /// <param name="form"></param>
            /// <param name="model"></param>
            /// <param name="state"></param>
            /// <param name="context"></param>
            private void SaveForm(Form form, FormViewModel model, Dictionary<string, object[]> state, ControllerContext context)
            {
                // this method has been copied from the base controller's SubmitForm method and modified for the state
                using (ApplicationContext.ProfilingLogger.DebugDuration<UmbracoFormsController>(string.Format("Umbraco Forms: Submitting Form '{0}' with id '{1}'", (object)form.Name, (object)form.Id)))
                {
                    model.SubmitHandled = true;
    
                    Record record = new Record();
    
                    if (model.RecordId != Guid.Empty)
                        record = BaseGetRecord(model.RecordId, form);
    
                    record.Form = form.Id;
                    record.State = FormState.PartiallySubmitted;
                    record.UmbracoPageId = CurrentPage.Id;
                    record.IP = HttpContext.Request.UserHostAddress;
    
                    if (HttpContext.User != null && HttpContext.User.Identity.IsAuthenticated && Membership.GetUser() != null)
                        record.MemberKey = Membership.GetUser().ProviderUserKey.ToString();
    
                    foreach (Field allField in form.AllFields)
                    {
                        object[] objArray = new object[0];
    
                        if (state != null && state.ContainsKey(allField.Id.ToString()))
                            objArray = state[allField.Id.ToString()];
    
                        object[] array = allField.FieldType.ConvertToRecord(allField, objArray, context.HttpContext).ToArray();
    
                        if (record.RecordFields.ContainsKey(allField.Id))
                        {
                            record.RecordFields[allField.Id].Values.Clear();
                            record.RecordFields[allField.Id].Values.AddRange(array);
                        }
                        else
                        {
                            RecordField recordField = new RecordField(allField);
                            recordField.Values.AddRange(array);
                            record.RecordFields.Add(allField.Id, recordField);
                        }
                    }
    
                    record.RecordData = record.GenerateRecordDataAsJson();
    
                    BaseClearFormState(model);
    
                    using (var rs = new RecordStorage())
                    {
                        if (record.Id <= 0)
                        {
                            rs.InsertRecord(record, form);
                        }
                        else
                        {
                            rs.UpdateRecord(record, form);
                        }
                    }
    
                    RecordService.Instance.AddRecordIdToTempData(record, ControllerContext);
                }
            }
    
            #region Base class reflection methods
    
            private MethodInfo getFormMethod;
    
            private Form BaseGetForm(Guid formId)
            {
                if (getFormMethod == null)
                {
                    getFormMethod = typeof(UmbracoFormsController).GetMethod("GetForm", BindingFlags.NonPublic | BindingFlags.Instance);
                }
    
                var obj = getFormMethod.Invoke(this, new object[] { formId });
    
                return obj as Form;
            }
    
            private MethodInfo prepopulateFormMethod;
    
            private void BasePrepopulateForm(Form form, ControllerContext context, FormViewModel formViewModel, Record record = null)
            {
                if (prepopulateFormMethod == null)
                {
                    prepopulateFormMethod = typeof(UmbracoFormsController).GetMethod("PrepopulateForm", BindingFlags.NonPublic | BindingFlags.Instance);
                }
    
                prepopulateFormMethod.Invoke(this, new object[] { form, context, formViewModel, record });
            }
    
            private MethodInfo extractAllPagesStateMethod;
    
            private Dictionary<string, object[]> BaseExtractAllPagesState(FormViewModel model, ControllerContext context, Form form)
            {
                if (extractAllPagesStateMethod == null)
                {
                    extractAllPagesStateMethod = typeof(UmbracoFormsController).GetMethod("ExtractAllPagesState", BindingFlags.NonPublic | BindingFlags.Instance);
                }
    
                var obj = extractAllPagesStateMethod.Invoke(this, new object[] { model, context, form });
    
                return obj as Dictionary<string, object[]>;
            }
    
            private MethodInfo storeFormStateMethod;
    
            private void BaseStoreFormState(Dictionary<string, object[]> state, FormViewModel model)
            {
                if (storeFormStateMethod == null)
                {
                    storeFormStateMethod = typeof(UmbracoFormsController).GetMethod("StoreFormState", BindingFlags.NonPublic | BindingFlags.Instance);
                }
    
                storeFormStateMethod.Invoke(this, new object[] { state, model });
            }
    
            private MethodInfo storeResumeFormStateMethod;
    
            private void BaseResumeFormState(FormViewModel model, Dictionary<string, object[]> state, bool editSubmission = false)
            {
                if (storeResumeFormStateMethod == null)
                {
                    storeResumeFormStateMethod = typeof(UmbracoFormsController).GetMethod("ResumeFormState", BindingFlags.NonPublic | BindingFlags.Instance);
                }
    
                storeResumeFormStateMethod.Invoke(this, new object[] { model, state, editSubmission });
            }
    
            private MethodInfo getRecordMethod;
    
            private Record BaseGetRecord(Guid recordId, Form form)
            {
                if (getRecordMethod == null)
                {
                    getRecordMethod = typeof(UmbracoFormsController).GetMethod("GetRecord", BindingFlags.NonPublic | BindingFlags.Instance);
                }
    
                var obj = getRecordMethod.Invoke(this, new object[] { recordId, form });
    
                return obj as Record;
            }
    
            private MethodInfo clearFormStateMethod;
    
            private void BaseClearFormState(FormViewModel model)
            {
                if (clearFormStateMethod == null)
                {
                    clearFormStateMethod = typeof(UmbracoFormsController).GetMethod("ClearFormState", BindingFlags.NonPublic | BindingFlags.Instance);
                }
    
                clearFormStateMethod.Invoke(this, new object[] { model });
            }
    
            #endregion
        }
    }
    

    As you can see it does a check to see if the Previous or Next page buttons have been pressed and in that case calls the SaveForm method which basically mimics the SubmitForm method, but with a different record state.

    I had to add a few reflection methods in there as well, as some of the necessary methods are not public.

    To use this controller, you need to update the Render.cshtml view (this might be the Form.cshtml view for older Forms versions) as well:

    @using (Html.BeginUmbracoForm<MyProject.Controllers.Surface.FormsController>("HandleFormSubmission"))
    

    Note that the above code is for Forms v7.x, so you might have to change it to work with Forms v4 (a decompiler like dnSpy is useful to find out how that version works).

  • J 370 posts 625 karma points
    Jun 07, 2019 @ 09:59
    J
    0

    Thanks Tom i will give this a whirl. I cant mark this as an answer but if a mod is reading then i'm happy for this to be marked as an answer and can open a new thread with any other issues.

  • Jamie Townsend 37 posts 197 karma points c-trib
    Aug 09, 2019 @ 12:18
    Jamie Townsend
    1

    @Tom - many thanks for this, I can confirm this works great - I only had one issue and that was ExtractAllPagesState

    This didn't exist in 7.03.0 version of forms. I upgraded to the latest and it worked ok after that.

    I saved the recordId/UniqueId by changing SaveForm slightly to output the new ID so I could save it somewhere to then use again to restore the data.

    SaveForm(form, model, model.FormState, ControllerContext, out var recordId);
    

    .

    private void SaveForm(Form form, FormViewModel model, Dictionary<string, object[]> state, ControllerContext context, out string recordId)
        {           
            // this method has been copied from the base controller's SubmitForm method and modified for the state
            using (ApplicationContext.ProfilingLogger.DebugDuration<UmbracoFormsController>(string.Format("Umbraco Forms: Submitting Form '{0}' with id '{1}'", (object)form.Name, (object)form.Id)))
            {
                model.SubmitHandled = true;
    
                Record record = new Record();
    
                if (model.RecordId != Guid.Empty)
                    record = BaseGetRecord(model.RecordId, form);
    
                record.Form = form.Id;
                record.State = FormState.PartiallySubmitted;
                record.UmbracoPageId = CurrentPage.Id;
                record.IP = HttpContext.Request.UserHostAddress;
    
                if (HttpContext.User != null && HttpContext.User.Identity.IsAuthenticated && Membership.GetUser() != null)
                    record.MemberKey = Membership.GetUser().ProviderUserKey.ToString();
    
                foreach (Field allField in form.AllFields)
                {
                    object[] objArray = new object[0];
    
                    if (state != null && state.ContainsKey(allField.Id.ToString()))
                        objArray = state[allField.Id.ToString()];
    
                    object[] array = allField.FieldType.ConvertToRecord(allField, objArray, context.HttpContext).ToArray();
    
                    if (record.RecordFields.ContainsKey(allField.Id))
                    {
                        record.RecordFields[allField.Id].Values.Clear();
                        record.RecordFields[allField.Id].Values.AddRange(array);
                    }
                    else
                    {
                        RecordField recordField = new RecordField(allField);
                        recordField.Values.AddRange(array);
                        record.RecordFields.Add(allField.Id, recordField);
                    }
                }
    
                record.RecordData = record.GenerateRecordDataAsJson();
    
                BaseClearFormState(model);
    
                using (var rs = new RecordStorage())
                {
                    if (record.Id <= 0)
                    {
                        rs.InsertRecord(record, form);
                    }
                    else
                    {
                        rs.UpdateRecord(record, form);
                    }
                }
    
                RecordService.Instance.AddRecordIdToTempData(record, ControllerContext);
                recordId = record.UniqueId.ToString();
            }
        }
    

    Thanks again

  • Ash 18 posts 88 karma points
    Sep 19, 2019 @ 16:21
    Ash
    0

    That looks really helpful. What version of Umbraco Forms did you try this on please?

    thanks

  • Tom van Enckevort 101 posts 413 karma points
    Sep 19, 2019 @ 16:33
    Tom van Enckevort
    0

    It has to be v7.x, but according to Jamie some of the earlier versions (v7.0.x) don't have all the code, so you might have to use at least v7.1.0

  • Ash 18 posts 88 karma points
    Sep 20, 2019 @ 08:07
    Ash
    0

    Thanks Tom for your quick reply,

    I am using v7.0.4. And after decompiling Umbraco.Forms.Web.dll it appears it does have ExtractAllPagesState. I'll give your above code a go and see what comes out of it.

  • Ash 18 posts 88 karma points
    Sep 20, 2019 @ 13:31
    Ash
    0

    Thnaks @Tom it seems to be saving data fine in database from first page, however, when the next button is clicked it keeps redirecting back to the same page, is it because of this return RedirectToCurrentUmbracoPage(); Action Result?

    Should I change it to return RedirectToUmbracoPage() and pass value in there?

    But then problem is I am not able to find this method get_GoToPageOnSubmit() on RedirectToUmbracoPage(form.get_GoToPageOnSubmit());

  • Tom van Enckevort 101 posts 413 karma points
    Sep 20, 2019 @ 13:53
    Tom van Enckevort
    1

    Not sure, but it would be redirecting to the same Umbraco page using RedirectToCurrentUmbracoPage as it will be displaying the same page, it's only the form page that should be changed when the page reloads again.

    I can't remember how that is done exactly, so it might be worth looking at the decompiled Forms code to see how that works in your version.

  • Ash 18 posts 88 karma points
    Sep 25, 2019 @ 15:55
    Ash
    0

    I have managed to nail it down, had to make some more changes to @Tom's surface controller to get the partial submission work as per my requirements. Had to cover edge cases

    Happy to post here if someone needs in the future.

    Currently tested on on 7.1.x

    Going to test it on 7.0.4

    Thanks for your help

  • Tony 104 posts 162 karma points
    Oct 23, 2019 @ 11:05
    Tony
    0

    I'm working this into my current project, would you be able to post up the final code? thank you

  • Tony 104 posts 162 karma points
    Oct 23, 2019 @ 13:26
    Tony
    0

    Im applying the custom FormsController, however when I go to the next page and then return it loses the values. It also doesn't seem to be storing them in the DB

  • Ash 18 posts 88 karma points
    Oct 23, 2019 @ 13:44
    Ash
    0

    Hi Tony,

    Post your controller code here mate. Let's see what's going on.

  • Tony 104 posts 162 karma points
    Oct 23, 2019 @ 13:47
    Tony
    0

    Ive literally just c&p the controller Tom put up (seems to have no issues as Im using v7.2), it hits the controller, and saves in the DB, but only saves the guids for the questions, it isn't storing the actual values, and on top of that its not retaining them when I go back a page.

  • Ash 18 posts 88 karma points
    Oct 23, 2019 @ 13:59
    Ash
    0

    Save the recordId in a Session variable at the end of SaveForm method like below:

                 RecordService.Instance.AddRecordIdToTempData(record, ControllerContext);
                if (Session["Forms_RecordID"] == null)
                    Session["Forms_RecordID"] = record.UniqueId.ToString();
    

    You then need to implement ForwardNext method like this:

            protected void ForwardNext(Form form, FormViewModel model, Dictionary<string, object[]> state)
        {
            FormViewModel formStep = model;
            formStep.FormStep = formStep.FormStep + 1;
    
            SaveForm(form, model, model.FormState, ControllerContext);
            if (model.FormStep == form.Pages.Count<Page>()) //If it is the last page
            {
                model.SubmitHandled = true;  //Make as submithandled so it gets redirected to form page for submit message
                Session["Forms_RecordID"] = null;
    
            }
    

    For my requirements I didn't require previous button but you can implement that

            protected void BackwardPrevious(Form form, FormViewModel model, Dictionary<string, object[]> state)
        {
        //Put your code here for previous data to persist
            }
        }
    

    Edit: Forgot to mention in Save form add below after Record record = new Record();

           if (Session["Forms_RecordID"] != null)
                record = BaseGetRecord(new Guid(Session["Forms_RecordID"].ToString()), form);
    

    This will check if the record is existing, it will pull form values before continuing with next page.

    Hope that helps

  • Tony 104 posts 162 karma points
    Oct 23, 2019 @ 16:01
    Tony
    0

    Thats great, does the ForwardNext method need to be called from the HandleFromSubmission or is it something which should be being called automatically (as it doesn't seem to be). For example

    if (Request["next"] != null)
    {
        ForwardNext(form, model, model.FormState);
    }
    else
    {
        SaveForm(form, model, model.FormState, ControllerContext);
    }
    
  • Ash 18 posts 88 karma points
    Oct 23, 2019 @ 16:21
    Ash
    0

    Call from HandleFormSubmission method :

                    if (ModelState.IsValid)
                    {
                        ForwardNext(form, model, model.FormState);
                    }
    
  • Tony 104 posts 162 karma points
    Dec 04, 2019 @ 11:35
    Tony
    0

    Sorry ash, Ive come back to this after stepping away for a bit. Whereabouts in the handleformsubmit does this go?

    if (ModelState.IsValid)
    {
         ForwardNext(form, model, model.FormState);
    }
    

    It's just that the forward next method saves the form and marks the submithandled to true, but doesn't actually call the HandleForm method?

    Do you have your code for that method?

  • Tony 104 posts 162 karma points
    Oct 23, 2019 @ 16:38
    Tony
    0

    One other issue Ive found is that Request["next"] is always null, Ive tried Request["next"] (as 'next ' is the actual id of the button), but it still returns null which is odd

  • Ash 18 posts 88 karma points
    Oct 23, 2019 @ 17:00
    Ash
    0

    If u open chrome dev tools and look at the request and see if this 'next' is present in the request.

  • Tony 104 posts 162 karma points
    Oct 23, 2019 @ 21:11
    Tony
    0

    Unfortunately not, when I dive into the request in Visual studio, its there for the _previous when I go back but not when I move forward with _next.

  • Ash 18 posts 88 karma points
    Oct 24, 2019 @ 09:58
    Ash
    0

    Have you found solution to this yet on 7.2?

    If not ,

    Downgrade to 7.1.x you should be able use "next"

  • Tony 104 posts 162 karma points
    Oct 24, 2019 @ 10:39
    Tony
    0

    Nope still havnt managed to get it working. Ive downgraded as well.

    This is what Im seeing, in the image it shows the first three Request values I looked for in the immediate window (after it had hit the breakpoint from clicking the next button), as you can see all teh values are null. I then ran it through and clicked the previous button, which is then populated in teh request. So frustrating.!

    enter image description here

  • Ash 18 posts 88 karma points
    Oct 24, 2019 @ 17:03
    Ash
    0

    If u want default operation to save data upon every next button press, u don't need this condition. Just check for prev is not null and implement forwardnext and backwardprevious methods as I described above.

  • Ash 18 posts 88 karma points
    Oct 24, 2019 @ 17:07
    Ash
    0

    however if u install 7.1.3 version of forms u should get Request["next"]

  • Satpal Gahir 18 posts 88 karma points
    Nov 26, 2019 @ 17:55
    Satpal Gahir
    0

    @ash how did you resolve the redirect?

    im trying this on umbraco 8.2. things are saving nicely to the database, but not redirecting to the right page/step.

  • Ash 18 posts 88 karma points
    Nov 26, 2019 @ 18:09
    Ash
    0

    Check my posts above with code and implement forward next methods

  • Tony 104 posts 162 karma points
    Dec 04, 2019 @ 12:16
    Tony
    1

    Still having issues with this, I dont get the Request ['next'] value at all, Ive tried installing every version from v7 up to 7.2 and its not there. I cant get the code here working to save the form because of this

    Why is this not part of the core product like it was back in Contour with the partially submitted workflow? This is standard functionality and it really causing problems with us now.!

  • Satpal Gahir 18 posts 88 karma points
    Dec 04, 2019 @ 13:07
    Satpal Gahir
    0

    i couldnt get the forward and back working too. so i deferred it to the main form handler. the main handler will do the logic to move on/back and hold state.

    So i've just SaveForm() (as partially submitted) and then HandleForm(model).

    im using forms 8.2 on umbraco 8.3 though.

    You can download Telerik JustDecompile and it will help read the Umbraco.Forms.X.dll to help you trace what actually is going on.

  • Dan 59 posts 183 karma points
    Jun 15, 2021 @ 07:19
    Dan
    0

    Hi Satpal Gahir

    I know this is a long shot but do you still have the code you achieved this feature in, I would like to see some of it if possible.

    I have version Umbraco Forms: 8.6.0

  • Duncan Turner 26 posts 127 karma points
    Jul 09, 2021 @ 10:10
    Duncan Turner
    0

    I managed to get this working on Umbraco 7, Forms Version 7.1.3.

    I like many others had issues with the code above. What I did was look at Umbraco 8 and it's version of Forms, this worked. Using Telerick JustDecompile I implemented the newer version of the code to my older version of Umbraco.

    public class FormsController : UmbracoFormsController
    {
        private readonly IScopeProvider _scopeProvider;
    
        public string RecordId { get; set; }
    
        [HttpPost]
        [ValidateCaptcha]
        [ValidateFormsAntiForgeryToken]
        [ValidateInput(false)]
        public ActionResult HandleFormSubmission(FormViewModel model, bool captchaIsValid)
        {
            ActionResult umbracoPage;
            var form = BaseGetForm(model.FormId);
            model.Build(form);
            if (!HoneyPotIsEmpty(model))
                model.SubmitHandled = true;
            else
            {
                PrePopulateForm(form, ControllerContext, model, null);
                model.FormState = FormPrePopulate == null ? ExtractCurrentPageState(model, ControllerContext, form) : ExtractAllPagesState(model, ControllerContext, form);
                StoreFormState(model.FormState, model);
                OnFormHandled(form, model);
                ResumeFormState(model, model.FormState, false);
    
                var prevClicked = (!string.IsNullOrEmpty(Request["__prev"]) || !string.IsNullOrEmpty(Request["PreviousClicked"])) && model.FormStep > 0;
                if (prevClicked)
                    BackwardPrevious(form, model, model.FormState);
                else
                    ForwardNext(form, model, model.FormState);
    
                model.IsFirstPage = model.FormStep == 0;
                model.IsLastPage = model.FormStep == form.Pages.Count - 1;
            }
    
            OnFormHandled(form, model);
            StoreFormModel(model);
    
            if (!model.SubmitHandled ? true : form.GoToPageOnSubmit <= 0)
                umbracoPage = CurrentUmbracoPage();
            else
            {
                ClearFormModel();
                ClearFormState(model);
                umbracoPage = RedirectToUmbracoPage(form.GoToPageOnSubmit);
            }
    
            return umbracoPage;
        }
    
        protected virtual void OnFormHandled(Form form, FormViewModel model)
        {
        }
        private void ResumeFormState(FormViewModel model, Dictionary<string, object[]> state, bool editSubmission = false)
        {
            if (state != null)
            {
                foreach (PageViewModel page in model.Pages)
                {
                    foreach (FieldsetViewModel fieldset in page.Fieldsets)
                    {
                        foreach (FieldsetContainerViewModel container in fieldset.Containers)
                        {
                            foreach (FieldViewModel field in container.Fields)
                            {
                                if (editSubmission && field.FieldType.Id == Guid.Parse("A72C9DF9-3847-47CF-AFB8-B86773FD12CD"))
                                {
                                    var providerInstance = FieldTypeProviderCollection.Instance.GetProviderInstance(Guid.Parse("DA206CAE-1C52-434E-B21A-4A7C198AF877"));
                                    field.FieldType = providerInstance;
                                    field.HideLabel = true;
                                }
                                if (!state.ContainsKey(field.Id))
                                {
                                    continue;
                                }
                                field.Values = state[field.Id];
                            }
                        }
                    }
                }
            }
        }
        private static string Base64Encode(string plainText)
        {
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText));
        }
        private void StoreFormState(Dictionary<string, object[]> state, FormViewModel model)
        {
            string str = JsonConvert.SerializeObject(state);
            model.RecordState = Base64Encode(str.EncryptWithMachineKey());
        }
        private Dictionary<string, object[]> ExtractAllPagesState(FormViewModel model, ControllerContext context, Form form)
        {
            object[] objArray;
            Dictionary<string, object[]> strs = RetrieveFormState(model);
            if (strs == null)
            {
                return null;
            }
            foreach (Field allField in form.AllFields)
            {
                object[] objArray1 = new object[0];
                object[] objArray2 = form.AllFields.First((Field f) => f.Id == allField.Id).Values == null ? new object[0] : form.AllFields.First((Field f) => f.Id == allField.Id).Values.ToArray();
                if (!context.HttpContext.Request.Form.AllKeys.Contains(allField.Id.ToString()))
                {
                    objArray1 = objArray2;
                }
                else
                {
                    string[] values = context.HttpContext.Request.Form.GetValues(allField.Id.ToString());
                    bool flag = true;
                    if (values != null)
                    {
                        string[] strArrays = values;
                        int num = 0;
                        while (num < (int)strArrays.Length)
                        {
                            if (string.IsNullOrEmpty(strArrays[num]))
                            {
                                num++;
                            }
                            else
                            {
                                flag = false;
                                break;
                            }
                        }
                        if (flag)
                        {
                            objArray = objArray2;
                        }
                        else
                        {
                            objArray = values;
                        }
                        objArray1 = objArray;
                    }
                }
                if (!strs.ContainsKey(allField.Id.ToString()))
                {
                    strs.Add(allField.Id.ToString(), objArray1);
                }
                else
                {
                    strs[allField.Id.ToString()] = objArray1;
                }
            }
            return strs;
        }
        private Dictionary<string, object[]> ExtractCurrentPageState(FormViewModel model, ControllerContext context, Form form)
        {
            Dictionary<string, object[]> strs = RetrieveFormState(model);
            if (strs != null)
            {
                foreach (FieldsetViewModel fieldset in model.CurrentPage.Fieldsets)
                {
                    foreach (FieldsetContainerViewModel container in fieldset.Containers)
                    {
                        foreach (FieldViewModel fieldViewModel in container.Fields)
                        {
                            object[] values = new object[0];
                            if (context.HttpContext.Request.Form.AllKeys.Contains(fieldViewModel.Id))
                            {
                                values = context.HttpContext.Request.Form.GetValues(fieldViewModel.Id);
                            }
                            Field field1 = form.AllFields.First((Field field) => field.Id.ToString() == fieldViewModel.Id);
                            values = field1.FieldType.ProcessSubmittedValue(field1, values, context.HttpContext).ToArray();
                            if (!strs.ContainsKey(fieldViewModel.Id))
                            {
                                strs.Add(fieldViewModel.Id, values);
                            }
                            else
                            {
                                strs[fieldViewModel.Id] = values;
                            }
                        }
                    }
                }
            }
            return strs;
        }
        private static string Base64Decode(string base64EncodedData)
        {
            byte[] numArray = Convert.FromBase64String(base64EncodedData);
            return Encoding.UTF8.GetString(numArray);
        }
        private Dictionary<string, object[]> RetrieveFormState(FormViewModel model)
        {
            if (string.IsNullOrEmpty(model.RecordState))
            {
                return new Dictionary<string, object[]>();
            }
            string str = Base64Decode(model.RecordState);
            return JsonConvert.DeserializeObject<Dictionary<string, object[]>>(str.DecryptWithMachineKey());
        }
        private void PrePopulateForm(Form form, ControllerContext context, FormViewModel formViewModel, Record record = null)
        {
            Dictionary<string, object[]> strs = RetrieveFormState(formViewModel);
            object[] value = new object[0];
            if (FormPrePopulate != null)
            {
                foreach (Field allField in form.AllFields)
                {
                    if (!context.HttpContext.Request.Form.AllKeys.Contains(allField.Id.ToString()))
                    {
                        KeyValuePair<string, object[]> keyValuePair = strs.FirstOrDefault((KeyValuePair<string, object[]> v) => v.Key == allField.Id.ToString());
                        value = keyValuePair.Value;
                        if (value == null)
                        {
                            continue;
                        }
                        object[] objArray = value;
                        for (int i = 0; i < (int)objArray.Length; i++)
                        {
                            object obj = objArray[i];
                            if (allField.Values == null)
                            {
                                allField.Values = new List<object>();
                            }
                            else if (allField.Settings.Keys.Contains("DefaultValue"))
                            {
                                allField.Values.Clear();
                            }
                            if (obj.Equals(""))
                            {
                                allField.Values = null;
                            }
                            else
                            {
                                allField.Values.Add(obj);
                            }
                        }
                    }
                    else
                    {
                        value = context.HttpContext.Request.Form.GetValues(allField.Id.ToString());
                        if (allField.Values == null)
                        {
                            allField.Values = new List<object>();
                        }
                        else if (allField.Settings.Keys.Contains("DefaultValue"))
                        {
                            allField.Values.Clear();
                        }
                        object[] objArray1 = value;
                        for (int j = 0; j < (int)objArray1.Length; j++)
                        {
                            object obj1 = objArray1[j];
                            if (obj1.Equals(""))
                            {
                                allField.Values = null;
                            }
                            else
                            {
                                allField.Values.Add(obj1);
                            }
                        }
                    }
                    if (!strs.ContainsKey(allField.Id.ToString()))
                    {
                        strs.Add(allField.Id.ToString(), value);
                    }
                    else
                    {
                        strs[allField.Id.ToString()] = value;
                    }
                }
                bool? nullable = null;
                using (IScope scope = _scopeProvider.CreateScope(IsolationLevel.Unspecified, RepositoryCacheMode.Unspecified, null, nullable, false))
                {
                    scope.Events.Dispatch<FormEventArgs>(FormPrePopulate, this, new FormEventArgs(form), "PrePopulatingForm");
                    DistributedCache.Instance.RefreshFormsCache(form);
                    DistributedCache.Instance.RefreshAllPrevalueRuntimeCache();
                    scope.Complete();
                }
                if (record != null)
                {
                    foreach (Field field in form.AllFields)
                    {
                        object[] array = new object[0];
                        array = field.FieldType.ConvertToRecord(field, array, ControllerContext.HttpContext).ToArray();
                        if (record.RecordFields.ContainsKey(field.Id))
                        {
                            continue;
                        }
                        RecordField recordField = new RecordField(field);
                        recordField.Values.AddRange(array);
                        record.RecordFields.Add(field.Id, recordField);
                    }
                    foreach (KeyValuePair<Guid, RecordField> values in record.RecordFields)
                    {
                        foreach (Field allField1 in form.AllFields)
                        {
                            if (!(allField1.Id == values.Key) || allField1.Values == null)
                            {
                                continue;
                            }
                            values.Value.Values = allField1.Values;
                        }
                    }
                }
            }
        }
        private bool HoneyPotIsEmpty(FormViewModel model)
        {
            var request = Request;
            Guid formId = model.FormId;
            return string.IsNullOrEmpty(request[formId.ToString().Replace("-", string.Empty)]);
        }
        private void ClearFormModel()
        {
            TempData.Remove("umbracoformsform");
        }
        private void ClearFormState(FormViewModel model)
        {
            model.RecordState = string.Empty;
        }
    
        private bool IsMemberSignedIn()
        {
            var memberShipHelper = new MembershipHelper(UmbracoWeb.UmbracoContext.Current);
            var userName = memberShipHelper.GetCurrentMember();
            if (userName != null) return true;
    
            return false;
        }
        private void SaveForm(Form form, FormViewModel model, Dictionary<string, object[]> state, ControllerContext context)
        {
            using (ApplicationContext.ProfilingLogger.DebugDuration<FormsController>(string.Format("Umbraco Forms: Submitting Form '{0}' with id '{1}'", form.Name, form.Id)))
            {
                var record = new Record();
    
                if (Session["Forms_RecordID"] != null)
                    record = GetRecord(new Guid(Session["Forms_RecordID"].ToString()), form);
    
                if (model.RecordId != Guid.Empty)
                    record = GetRecord(model.RecordId, form);
    
                record.Form = form.Id;
                record.State = FormState.PartiallySubmitted;
                record.UmbracoPageId = CurrentPage.Id;
                record.IP = HttpContext.Request.UserHostAddress;
    
                record.CurrentPage = CurrentPage.Version;
    
                if (HttpContext.User != null && HttpContext.User.Identity.IsAuthenticated && Membership.GetUser() != null)
                    record.MemberKey = Membership.GetUser().ProviderUserKey.ToString();
    
                GetFieldsAndValues(form, state, context, record);
    
                record.RecordData = record.GenerateRecordDataAsJson();
    
                using (var rs = new RecordStorage())
                {
                    if (record.Id <= 0)
                        rs.InsertRecord(record, form);
                    else
                        rs.UpdateRecord(record, form);
                }
    
                RecordService.Instance.AddRecordIdToTempData(record, ControllerContext);
    
                if (Session["Forms_RecordID"] == null)
                    Session["Forms_RecordID"] = record.UniqueId.ToString();
    
            }
    
        }
    
        private static void GetFieldsAndValues(Form form, Dictionary<string, object[]> state, ControllerContext context, Record record)
        {
            foreach (Field allField in form.AllFields)
            {
    
                object[] objArray = new object[0];
    
                if (state != null && state.ContainsKey(allField.Id.ToString()))
                    objArray = state[allField.Id.ToString()];
    
                object[] array = allField.FieldType.ConvertToRecord(allField, objArray, context.HttpContext).ToArray();
    
                if (record.RecordFields.ContainsKey(allField.Id))
                {
                    record.RecordFields[allField.Id].Values.Clear();
                    record.RecordFields[allField.Id].Values.AddRange(array);
                }
                else
                {
                    var recordField = new RecordField(allField);
                    recordField.Values.AddRange(array);
                    record.RecordFields.Add(allField.Id, recordField);
                }
    
            }
        }
    
        private Record GetRecord(Guid recordId, Form form)
        {
            Record recordByUniqueId;
            using (var recordStorage = new RecordStorage())           
                recordByUniqueId = recordStorage.GetRecordByUniqueId(recordId, form);
    
            return recordByUniqueId;
        }
        private void ForwardNext(Form form, FormViewModel model, Dictionary<string, object[]> state)
        {
            var formStep = model;
            formStep.FormStep++;
            if (IsMemberSignedIn())
            {
                SaveForm(form, model, model.FormState, ControllerContext); 
            }
    
            var isLastPage = model.FormStep == form.Pages.Count();
            if (isLastPage)
            {
                model.SubmitHandled = true;  
                Session["Forms_RecordID"] = null;
            }
        }
    
        protected void BackwardPrevious(Form form, FormViewModel model, Dictionary<string, object[]> state)
        {
            var formStep = model;
            formStep.FormStep--;
            if (model.FormStep < 0)
                model.FormStep = 0;
        }
    
        private void StoreFormModel(FormViewModel model)
        {
            TempData["umbracoformsform"] = model;
        }
    
    
        private MethodInfo getFormMethod;
    
        private Form BaseGetForm(Guid formId)
        {
            if (getFormMethod == null)
                getFormMethod = typeof(UmbracoFormsController).GetMethod("GetForm", BindingFlags.NonPublic | BindingFlags.Instance);
    
            var obj = getFormMethod.Invoke(this, new object[] { formId });
            return obj as Form;
        }
    
        public static event EventHandler<FormEventArgs> FormPrePopulate;
    
        public static event EventHandler<FormValidationEventArgs> FormValidate;
    
    }
    

    Hope this helps?

    Cheers

  • Stuart Paterson 43 posts 204 karma points
    Aug 27, 2021 @ 08:26
    Stuart Paterson
    0

    Hi Duncan,

    Discovered this post last night as I'm trying to implement a Save & Continue function on Umbraco Forms (Umbraco v8, Forms v8.7.6).

    I'm seeing all sorts of errors around RecordStorage and RecordService not being accessible due it's protection level. DistributedCache does not contain a definition for instance so I'm guessing this is something which has changed in Forms? (very new to Umbraco Forms).

    I'm trying to implement a button/link to Save & Continue later at any stage of the form and on any device by providing site visitors with a unique link (without providing login details) where they can return to complete their form and then submit.

    Any help/advice would be greatly appreciated.

    Cheers, Stuart

  • Duncan Turner 26 posts 127 karma points
    Aug 27, 2021 @ 09:17
    Duncan Turner
    0

    Hi Stuart,

    So the fix I posted was for V7 Umbraco which uses and earlier version on Forms. Umbraco changed quite a lot in V8 as far as my understanding goes. However to backward 'engineer' this for my purposes I had to work with a V8 instance...

    BTW hopefully you can take something from it, sorry for the presentation.. Didn't copy + paste very well.

    Code from V8 project that may help you;

    using Newtonsoft.Json;
    

    using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Data; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Security.Principal; using System.Text; using System.Threading; using System.Web; using System.Web.Mvc; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Scoping; using Umbraco.Forms.Core; using Umbraco.Forms.Core.Attributes; using Umbraco.Forms.Core.Data.Storage; using Umbraco.Forms.Core.Enums; using Umbraco.Forms.Core.Extensions; using Umbraco.Forms.Core.Models; using Umbraco.Forms.Core.Persistence.Dtos; using Umbraco.Forms.Core.Providers; using Umbraco.Forms.Core.Services; using Umbraco.Forms.Mvc; using Umbraco.Forms.Mvc.BusinessLogic; using Umbraco.Forms.Mvc.Models; using Umbraco.Forms.Web.Models; using Umbraco.Web; using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using Umbraco.Web.Security;

    namespace UmbracoV8Concept01.App_Code.Controllers { public class FormsController : SurfaceController { private readonly IFormStorage _formStorage;

        private readonly IRecordStorage _recordStorage;
    
        private readonly IRecordService _recordService;
    
        private readonly IFacadeConfiguration _configuration;
    
        private readonly FieldCollection _fieldCollection;
    
        private readonly IFieldTypeStorage _fieldTypeStorage;
    
        private readonly IFieldPreValueSourceService _fieldPreValueSourceService;
    
        private readonly IFieldPreValueSourceTypeService _fieldPreValueSourceTypeService;
    
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
    
        private readonly IPageService _pageService;
    
        private readonly IScopeProvider _scopeProvider;
    
        private const string FormsFormKey = "umbracoformsform";
    
        public FormsController(
            IFormStorage formStorage,
            IRecordStorage recordStorage,
            IRecordService recordService,
            IFacadeConfiguration configuration,
            FieldCollection fieldCollection,
            IFieldTypeStorage fieldTypeStorage,
            IFieldPreValueSourceService fieldPreValueSourceService,
            IFieldPreValueSourceTypeService fieldPreValueSourceTypeService,
            IUmbracoContextAccessor umbracoContextAccessor,
            IPageService pageService,
            IScopeProvider scopeProvider)
        {
            _formStorage = formStorage;
            _recordStorage = recordStorage;
            _recordService = recordService;
            _configuration = configuration;
            _fieldCollection = fieldCollection;
            _fieldTypeStorage = fieldTypeStorage;
            _fieldPreValueSourceService = fieldPreValueSourceService;
            _fieldPreValueSourceTypeService = fieldPreValueSourceTypeService;
            _umbracoContextAccessor = umbracoContextAccessor;
            _pageService = pageService;
            _scopeProvider = scopeProvider;
        }
    
        private static string Base64Decode(string base64EncodedData)
        {
            byte[] numArray = Convert.FromBase64String(base64EncodedData);
            return Encoding.UTF8.GetString(numArray);
        }
    
        private static string Base64Encode(string plainText)=>
            Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText));
    
    
        private void ClearFormModel()
        {
            TempData.Remove("umbracoformsform");
        }
    
        private void ClearFormState(FormViewModel model)
        {
            model.RecordState = string.Empty;
        }
    
        private Dictionary<string, object[]> CreateStateFromRecord(Form form, Record record)
        {
            Dictionary<string, object[]> strs = new Dictionary<string, object[]>();
            foreach (KeyValuePair<Guid, RecordField> recordField in record.RecordFields)
            {
                Field field = form.AllFields.FirstOrDefault<Field>((Field x) => x.Id == recordField.Value.FieldId);
                if (field != null)
                {
                    FieldType fieldTypeByField = _fieldTypeStorage.GetFieldTypeByField(field);
                    Guid id = field.Id;
                    strs.Add(id.ToString(), fieldTypeByField.ConvertFromRecord(field, recordField.Value.Values).ToArray<object>());
                }
            }
            return strs;
        }
    
        private Dictionary<string, object[]> ExtractAllPagesState(FormViewModel model, ControllerContext context, Form form)
        {
            Dictionary<string, object[]> strs;
            Guid id;
            object[] objArray;
            Dictionary<string, object[]> strs1 = RetrieveFormState(model);
            if (strs1 != null)
            {
                foreach (Field allField in form.AllFields)
                {
                    object[] array = new object[0];
                    object[] objArray1 = form.AllFields.First((Field f) => f.Id == allField.Id).Values == null ? new object[0] : form.AllFields.First((Field f) => f.Id == allField.Id).Values.ToArray();
                    if (context.HttpContext.Request.Form.AllKeys.Contains(allField.Id.ToString()))
                    {
                        NameValueCollection nameValueCollection = context.HttpContext.Request.Form;
                        id = allField.Id;
                        string[] values = nameValueCollection.GetValues(id.ToString());
                        bool flag = true;
                        if (values != null)
                        {
                            string[] strArrays = values;
                            int num = 0;
                            while (num < (int)strArrays.Length)
                            {
                                if (string.IsNullOrEmpty(strArrays[num]))
                                    num++;
                                else
                                {
                                    flag = false;
                                    break;
                                }
                            }
                            if (flag)
                                objArray = objArray1;
                            else
                                objArray = values;
    
                            array = objArray;
                        }
                    }
                    else if (!context.HttpContext.Request.Files.AllKeys.Contains(allField.Id.ToString()))  
                        array = objArray1;                   
                    else
                    {
                        Field field = form.AllFields.First<Field>((Field f) => f.Id == allField.Id);
                        FieldType fieldTypeByField = _fieldTypeStorage.GetFieldTypeByField(field);
                        array = fieldTypeByField.ProcessSubmittedValue(field, array, context.HttpContext).ToArray();
                    }
                    if (!strs1.ContainsKey(allField.Id.ToString()))
                    {
                        id = allField.Id;
                        strs1.Add(id.ToString(), array);
                    }
                    else
                    {
                        id = allField.Id;
                        strs1[id.ToString()] = array;
                    }
                }
                strs = strs1;
            }
            else
                strs = null;
    
            return strs;
        }
    
        private Dictionary<string, object[]> ExtractCurrentPageState(FormViewModel model, ControllerContext context, Form form)
        {
            Dictionary<string, object[]> strs = RetrieveFormState(model);
            if (strs != null)
            {
                foreach (FieldsetViewModel fieldset in model.CurrentPage.Fieldsets)
                {
                    foreach (FieldsetContainerViewModel container in fieldset.Containers)
                    {
                        foreach (FieldViewModel fieldViewModel in container.Fields)
                        {
                            object[] array = new object[0];
                            if (context.HttpContext.Request.Form.AllKeys.Contains<string>(fieldViewModel.Id))
                            {
                                object[] values = context.HttpContext.Request.Form.GetValues(fieldViewModel.Id);
                                array = values;
                            }
                            Field field1 = form.AllFields.First<Field>((Field field) => field.Id.ToString() == fieldViewModel.Id);
                            FieldType fieldTypeByField = _fieldTypeStorage.GetFieldTypeByField(field1);
                            array = fieldTypeByField.ProcessSubmittedValue(field1, array, context.HttpContext).ToArray<object>();
                            if (!strs.ContainsKey(fieldViewModel.Id)) 
                                strs.Add(fieldViewModel.Id, array);
                            else
                                strs[fieldViewModel.Id] = array;
    
                        }
                    }
                }
            }
            return strs;
        }
    
        private void ExtractDataFromPages(FormViewModel model, Form form)
        {
            model.FormState = ExtractPagesState(model, ControllerContext, form);
            StoreFormState(model.FormState, model);
            ResumeFormState(model, model.FormState, false);
        }
    
        private Dictionary<string, object[]> ExtractPagesState(FormViewModel model, ControllerContext context, Form form)
        {
            Dictionary<string, object[]> strs = RetrieveFormState(model);
            if (strs != null)
            {
                foreach (PageViewModel page in model.Pages)
                {
                    foreach (FieldsetViewModel fieldset in page.Fieldsets)
                    {
                        foreach (FieldsetContainerViewModel container in fieldset.Containers)
                        {
                            foreach (FieldViewModel fieldViewModel in container.Fields)
                            {
                                object[] array = new object[0];
                                if (context.HttpContext.Request.Form.AllKeys.Contains<string>(fieldViewModel.Id))
                                {
                                    object[] values = context.HttpContext.Request.Form.GetValues(fieldViewModel.Id);
                                    array = values;
                                }
                                Field field1 = form.AllFields.First<Field>((Field field) => field.Id.ToString() == fieldViewModel.Id);
                                //FieldExtensions.PopulateDefaultValue(field1);
                                FieldType fieldTypeByField = _fieldTypeStorage.GetFieldTypeByField(field1);
                                array = fieldTypeByField.ProcessSubmittedValue(field1, array, context.HttpContext).ToArray<object>();
                                if (!strs.ContainsKey(fieldViewModel.Id))
                                    strs.Add(fieldViewModel.Id, array);
                                else
                                    strs[fieldViewModel.Id] = array;
                            }
                        }
                    }
                }
            }
            return strs;
        }
    
        private Form GetForm(Guid formId) => 
            _formStorage.GetForm(formId);
    
    
        private FormViewModel GetFormModel(Guid formId, Guid? recordId, string theme = "")
        {
            bool flag;
            IPublishedContent publishedContent;
            if (base.HttpContext.Items["pageElements"] == null)
            {
                UmbracoContext umbracoContext = _umbracoContextAccessor.UmbracoContext;
                if (umbracoContext != null)
                {
                    PublishedRequest publishedRequest = umbracoContext.PublishedRequest;
                    if (publishedRequest != null)
                    {
                        publishedContent = publishedRequest.PublishedContent;
                    }
                    else
                    {
                        publishedContent = null;
                    }
                }
                else
                {
                    publishedContent = null;
                }
                if (publishedContent != null)
                {
                    HttpContext.Items["pageElements"] = _pageService.GetPageElements();
                }
            }
            if (Session != null)
            {
                int currentMemberId = -1;
                if (Members.IsUmbracoMembershipProviderActive())
                {
                    try
                    {
                        currentMemberId = Members.GetCurrentMemberId();
                    }
                    catch (Exception exception1)
                    {
                        Exception exception = exception1;
                        Logger.Error(typeof(FormsController), "Can't get the current members Id", new object[] { exception });
                    }
                }
                if (currentMemberId <= -1)
                {
                    Session["ContourMemberKey"] = null;
                }
                else
                {
                    Session["ContourMemberKey"] = currentMemberId;
                }
            }
            FormViewModel formViewModel = RetrieveFormModel();
            if ((formViewModel == null ? false : formViewModel.FormId == formId))
            {
                if (!string.IsNullOrEmpty(theme))
                {
                    formViewModel.Theme = theme;
                }
                ResumeFormState(formViewModel, formViewModel.FormState, false);
            }
            else
            {
                Form form = GetForm(formId);
                formViewModel = new FormViewModel();
                if (!string.IsNullOrEmpty(theme))
                {
                    formViewModel.Theme = theme;
                }
                formViewModel.Build(form, _fieldTypeStorage, _fieldPreValueSourceService, _fieldPreValueSourceTypeService);
                PrePopulateForm(form, ControllerContext, formViewModel, null);
                ResumeFormState(formViewModel, formViewModel.FormState, false);
                if (formViewModel.IsFirstPage)
                {
                    ClearFormState(formViewModel);
                }
                if (!_configuration.AllowEditableFormSubmisisons)
                {
                    ExtractDataFromPages(formViewModel, form);
                }
                else
                {
                    if (!recordId.HasValue)
                    {
                        flag = false;
                    }
                    else
                    {
                        Guid? nullable = recordId;
                        Guid empty = Guid.Empty;
                        if (nullable.HasValue)
                        {
                            flag = (nullable.HasValue ? nullable.GetValueOrDefault() != empty : false);
                        }
                        else
                        {
                            flag = true;
                        }
                    }
                    if (!flag)
                    {
                        ExtractDataFromPages(formViewModel, form);
                    }
                    else
                    {
                        Record record = GetRecord(recordId.Value, form);
                        if (record != null)
                        {
                            PrePopulateForm(form, ControllerContext, formViewModel, record);
                            formViewModel.RecordId = record.UniqueId;
                            formViewModel.FormState = CreateStateFromRecord(form, record);
                            StoreFormState(formViewModel.FormState, formViewModel);
                            ResumeFormState(formViewModel, formViewModel.FormState, true);
                        }
                    }
                }
            }
            List<Guid> guids = new List<Guid>();
            if (TempData["UmbracoForms"] != null)
            {
                guids = (List<Guid>)TempData["UmbracoForms"];
            }
            if (!guids.Contains(formId))
            {
                guids.Add(formId);
            }
            //TempData.set_Item("UmbracoForms", guids);
            return formViewModel;
        }
    
        private Record GetRecord(Guid recordId, Form form) =>
            _recordStorage.GetRecordByUniqueId(recordId, form);
    
    
        private void GoBackward(FormViewModel model)
        {
            var formStep = model;
            formStep.FormStep -= 1;
            if (model.FormStep < 0)
                model.FormStep = 0;
    
        }
    
        private void GoForward(Form form, FormViewModel model, Dictionary<string, object[]> state)
        {
            var formStep = model;
            formStep.FormStep += 1;
            if (model.FormStep == form.Pages.Count())
                SubmitForm(form, model, state, ControllerContext);
    
        }
    
        [HttpPost]
        [ValidateFormsAntiForgeryToken]
        [ValidateInput(false)]
        public ActionResult HandleForm(FormViewModel model)
        {
            ActionResult umbracoPage;
            var form = GetForm(model.FormId);
            model.Build(form, _fieldTypeStorage, _fieldPreValueSourceService, _fieldPreValueSourceTypeService);
            if (!HoneyPotIsEmpty(model))
                model.SubmitHandled = true;   
            else
            {
                PrePopulateForm(form, ControllerContext, model, null);
                model.FormState = (FormPrePopulate == null ? ExtractCurrentPageState(model, ControllerContext, form) : ExtractAllPagesState(model, ControllerContext, form));
                StoreFormState(model.FormState, model);
                ResumeFormState(model, model.FormState, false);
                if ((!string.IsNullOrEmpty(Request["__prev"]) || !string.IsNullOrEmpty(Request["PreviousClicked"]) ? model.FormStep <= 0 : true))
                {
                    ValidateFormState(model, form, ControllerContext.HttpContext);
                    if (ModelState.IsValid)
                    {
                        GoForward(form, model, model.FormState);
                    }
                }
                else
                    GoBackward(model);
    
                model.IsFirstPage = model.FormStep == 0;
                model.IsLastPage = model.FormStep == form.Pages.Count - 1;
            }
            OnFormHandled(form, model);
            StoreFormModel(model);
            //return CurrentUmbracoPage();
            if ((!model.SubmitHandled ? true : form.GoToPageOnSubmit <= 0))
                umbracoPage = CurrentUmbracoPage();          
            else
            {
                ClearFormModel();
                ClearFormState(model);
                umbracoPage = RedirectToUmbracoPage(form.GoToPageOnSubmit);
            }
            return umbracoPage;
        }
    
        private bool HoneyPotIsEmpty(FormViewModel model)
        {
            var request = Request;
            Guid formId = model.FormId;
            return string.IsNullOrEmpty(request[formId.ToString().Replace("-", string.Empty)]);
        }
    
        protected virtual void OnFormHandled(Form form, FormViewModel model)
        {
        }
    
        private static void PopulateFieldValues(FormViewModel model, Form form)
        {
            object[] objArray;
            foreach (Field allField in form.AllFields)
            {
                Dictionary<string, object[]> formState = model.FormState;
                Guid id = allField.Id;
                formState.TryGetValue(id.ToString(), out objArray);
                allField.Values = (objArray != null ? objArray.ToList() : new List<object>());
            }
        }
        private void PrePopulateForm(Form form, ControllerContext context, FormViewModel formViewModel, Record record = null)
        {
            Guid id;
            Dictionary<string, object[]> strs = RetrieveFormState(formViewModel);
            object[] value = new object[0];
            if (FormPrePopulate != null)
            {
                foreach (Field allField in form.AllFields)
                {
                    if (!context.HttpContext.Request.Form.AllKeys.Contains(allField.Id.ToString()))
                    {
                        KeyValuePair<string, object[]> keyValuePair = strs.FirstOrDefault<KeyValuePair<string, object[]>>((KeyValuePair<string, object[]> v) => v.Key == allField.Id.ToString());
                        value = keyValuePair.Value;
                        if (value != null)
                        {
                            object[] objArray = value;
                            for (int i = 0; i < (int)objArray.Length; i++)
                            {
                                object obj = objArray[i];
                                if (allField.Values == null)                               
                                    allField.Values.Add(new List<object>());                              
                                else if (allField.Settings.Keys.Contains("DefaultValue"))
                                    allField.Values.Clear();
    
                                if (obj.Equals(string.Empty))                                
                                    allField.Values.Clear();                                
                                else                               
                                    allField.Values.Add(obj);
    
                            }
                        }
                        else 
                            continue;
    
                    }
                    else
                    {
                        var nameValueCollection = context.HttpContext.Request.Form;
                        id = allField.Id;
                        value = nameValueCollection.GetValues(id.ToString());
                        if (allField.Values == null) 
                            allField.Values.Add(new List<object>());                      
                        else if (allField.Settings.Keys.Contains("DefaultValue"))         
                            allField.Values.Clear();
    
    
                        object[] objArray1 = value;
                        for (int j = 0; j < objArray1.Length; j++)
                        {
                            object obj1 = objArray1[j];
                            if (obj1.Equals(string.Empty))  
                                allField.Values.Clear();                      
                            else                            
                                allField.Values.Add(obj1);                            
                        }
                    }
                    if (!strs.ContainsKey(allField.Id.ToString()))
                    {
                        id = allField.Id;
                        strs.Add(id.ToString(), value);
                    }
                    else
                    {
                        id = allField.Id;
                        strs[id.ToString()] = value;
                    }
                }
                bool? nullable = null;
                using (IScope scope = _scopeProvider.CreateScope(IsolationLevel.Unspecified, 0, null, nullable, false, true))
                {
                    scope.Events.Dispatch(FormPrePopulate, this, new FormEventArgs(form), "PrePopulatingForm");
                }
                if (record != null)
                {
                    foreach (Field field in form.AllFields)
                    {
                        object[] array = new object[0];
                        FieldType fieldTypeByField = _fieldTypeStorage.GetFieldTypeByField(field);
                        array = fieldTypeByField.ConvertToRecord(field, array, ControllerContext.HttpContext).ToArray<object>();
                        if (record.GetRecordField(field.Id) == null)
                        {
                            RecordField recordField = new RecordField(field);
                            recordField.Values.AddRange(array);
                            record.RecordFields.Add(field.Id, recordField);
                        }
                    }
                    foreach (KeyValuePair<Guid, RecordField> recordField1 in record.RecordFields)
                    {
                        foreach (Field allField1 in form.AllFields)
                        {
                            if (allField1.Id == recordField1.Value.FieldId)
                            {
                                if (allField1.Values != null)  
                                    recordField1.Value.Values.Add(allField1.Values);                               
                            }
                        }
                    }
                }
            }
        }
    
        //[ChildActionOnly]
        //public ActionResult Render(Guid formId, Guid? recordId = null, string view = "", string mode = "full")
        //{
        //    FormViewModel formModel = GetFormModel(formId, recordId, "");
        //    formModel.RenderMode = mode;
        //    if (File.Exists(base.get_Server().MapPath(string.Format("{0}/{1}/Form.cshtml", Constants.System.ViewsPath, formModel.FormId))))
        //    {
        //        view = Path.Combine(Constants.System.ViewsPath, string.Format("{0}/Form.cshtml", formModel.FormId));
        //    }
        //    else if (string.IsNullOrEmpty(view))
        //    {
        //        view = Path.Combine(Constants.System.ViewsPath, "Form.cshtml");
        //    }
        //    else if ((view.StartsWith("~") ? false : !view.StartsWith("/")))
        //    {
        //        view = Path.Combine(Constants.System.ViewsPath, view);
        //    }
        //    return PartialView(view, formModel);
        //}
    
        [ChildActionOnly]
        public ActionResult RenderForm(Guid formId, Guid? recordId = null, string theme = "", bool includeScripts = true)
        {
            FormViewModel formModel = GetFormModel(formId, recordId, theme);
            formModel.RenderScriptFiles = includeScripts;
            return PartialView(FormThemeResolver.GetFormRender(formModel), formModel);
        }
    
        [ChildActionOnly]
        public ActionResult RenderFormScripts(Guid formId, string theme = "")
        {
            FormViewModel formModel = GetFormModel(formId, null, theme);
            return PartialView(FormThemeResolver.GetScriptView(formModel), formModel);
        }
    
        private void ResumeFormState(FormViewModel model, Dictionary<string, object[]> state, bool editSubmission = false)
        {
            if (state != null)
            {
                foreach (PageViewModel page in model.Pages)
                {
                    foreach (FieldsetViewModel fieldset in page.Fieldsets)
                    {
                        foreach (FieldsetContainerViewModel container in fieldset.Containers)
                        {
                            foreach (FieldViewModel field in container.Fields)
                            {
                                if (editSubmission)
                                {
                                    if (field.FieldType.Id == Guid.Parse("A72C9DF9-3847-47CF-AFB8-B86773FD12CD"))
                                    {
                                        FieldType item = _fieldCollection[Guid.Parse("DA206CAE-1C52-434E-B21A-4A7C198AF877")];
                                        field.FieldType = item;
                                        field.HideLabel = true;
                                    }
                                }
                                if (state.ContainsKey(field.Id))
                                    field.Values = state[field.Id];
    
                            }
                        }
                    }
                }
            }
        }
    
        private FormViewModel RetrieveFormModel()
        {
            FormViewModel item;
            if (TempData.ContainsKey("umbracoformsform"))
                item = TempData["umbracoformsform"] as FormViewModel;           
            else         
                item = null;
    
            return item;
        }
    
        private Dictionary<string, object[]> RetrieveFormState(FormViewModel model)
        {
            Dictionary<string, object[]> strs;
            if (!string.IsNullOrEmpty(model.RecordState))
            {
                var str = Base64Decode(model.RecordState);
                strs = JsonConvert.DeserializeObject<Dictionary<string, object[]>>(str.DecryptWithMachineKey());
            }
            else
                strs = new Dictionary<string, object[]>();
    
            return strs;
        }
    
        private void StoreFormModel(FormViewModel model)
        {
            TempData["umbracoformsform"] = model;
        }
    
        private void StoreFormState(Dictionary<string, object[]> state, FormViewModel model)
        {
            string str = JsonConvert.SerializeObject(state);
            model.RecordState = Base64Encode(str.EncryptWithMachineKey());
        }
    
        private void SubmitForm(Form form, FormViewModel model, Dictionary<string, object[]> state, ControllerContext context)
        {
            Guid id;
            bool flag;
            string str;
            using (DisposableTimer disposableTimer = Current.ProfilingLogger.DebugDuration<FormsController>(string.Format("Umbraco Forms: Submitting Form '{0}' with id '{1}'", form.Name, form.Id)))
            {
                model.SubmitHandled = true;
                Record record = new Record();
                if (model.RecordId != Guid.Empty)
                {
                    record = GetRecord(model.RecordId, form);
                }
                record.Form = form.Id;
                record.State = FormState.Submitted;
                record.UmbracoPageId = CurrentPage.Id;
                record.IP = HttpContext.Request.UserHostAddress;
    
                bool isAuthenticated = false;
                string name = null;
                var hUser = HttpContext.User;
                if ((HttpContext == null || hUser == null ? false : hUser.Identity != null))
                {
                    isAuthenticated = hUser.Identity.IsAuthenticated;
                    name = hUser.Identity.Name;
                }
                if ((!isAuthenticated ? false : !string.IsNullOrEmpty(name)))
                {
                    var user = Membership.GetUser(name);
                    if (user != null)
                    {
                        var record1 = record;
                        object providerUserKey = user.ProviderUserKey;
                        if (providerUserKey != null)
                            str = providerUserKey.ToString();                  
                        else                     
                            str = null;
    
                        record1.MemberKey = str;
                    }
                }
                foreach (var allField in form.AllFields)
                {
                    object[] item = new object[0];
                    if (state == null)
                        flag = false;                    
                    else
                    {
                        id = allField.Id;
                        flag = state.ContainsKey(id.ToString());
                    }
                    if (flag)
                    {
                        id = allField.Id;
                        item = state[id.ToString()];
                    }
                    var fieldTypeByField = _fieldTypeStorage.GetFieldTypeByField(allField);
                    item = fieldTypeByField.ConvertToRecord(allField, item, context.HttpContext).ToArray<object>();
                    if (record.GetRecordField(allField.Id) == null)
                    {
                        var recordField = new RecordField(allField);
                        recordField.Values.AddRange(item);
                        record.RecordFields.Add(allField.Id, recordField);
                    }
                    else
                    {
                        var recordFields = record.GetRecordField(allField.Id).Values;
                        recordFields.Clear();
                        recordFields.AddRange(item);
                    }
                }
                ClearFormState(model);
                _recordService.Submit(record, form);
                _recordService.AddRecordIdToTempData(record, ControllerContext);
            }
        }
    
        private void ValidateFormState(FormViewModel model, Form form, HttpContextBase context)
        {
            PopulateFieldValues(model, form);
            Dictionary<Guid, string> dictionary = form.AllFields.ToDictionary<Field, Guid, string>((Field f) => f.Id, (Field f) => string.Join<object>(", ", f.Values ?? new List<object>()));
            foreach (FieldSet fieldSet in form.Pages[model.FormStep].FieldSets)
            {
                if ((fieldSet.Condition == null ? false : fieldSet.Condition.Enabled))
                {
                    if (!FieldConditionEvaluation.IsVisible(fieldSet.Condition, form, dictionary))
                        continue;                   
                }
                foreach (Field field in fieldSet.Containers.SelectMany((FieldsetContainer c) => c.Fields))
                {
                    var nameValueCollection = context.Request.Form;
                    var id = field.Id;
                    string[] values = nameValueCollection.GetValues(id.ToString()) ?? Array.Empty<string>();
                    var fieldTypeByField = _fieldTypeStorage.GetFieldTypeByField(field);
                    object obj = fieldTypeByField.ValidateField(form, field, values, HttpContext, _formStorage);
                    if (obj == null)
                        obj = new string[0];                    
                    foreach (string str in (IEnumerable<string>)obj)
                    {
                        string str1 = str;
                        if (string.IsNullOrWhiteSpace(str))
                        {
                            //str1 = StringExtensions.ParsePlaceHolders( form.InvalidErrorMessage ?? string.Empty,field.Caption);
                            str1 = field.Caption;
                        }
                        var modelState = ModelState;
                        id = field.Id;
                        modelState.AddModelError(id.ToString(), str1);
                    }
                }
            }
            FormValidate?.Invoke(this, new FormValidationEventArgs(form));
        }
    
        public static event EventHandler<FormEventArgs> FormPrePopulate;
    
        public static event EventHandler<FormValidationEventArgs> FormValidate;
    }
    

    }

  • Duncan Turner 26 posts 127 karma points
    Aug 27, 2021 @ 09:21
    Duncan Turner
    0

    You also might have to add a routes file in the App_Code/Routes/

    using System;
    

    using System.Collections.Generic; using System.Linq; using System.Web; using Umbraco.Core.Composing; using Umbraco.Forms.Core; using Umbraco.Forms.Web.Controllers;

    namespace UmbracoV8Concept01.App_Code { public class CustomRoutesComposer : ComponentComposer

    public class CustomRoutesComponent : IComponent
    {
        public void Initialize()
        {
            UmbracoFormsController.FormPrePopulate += (object sender, FormEventArgs e) =>
            {
                // nothing needed here, it just needs to exist to save all form data when submitting a form with multiple form pages
            };
        }
    
        public void Terminate()
        {
            throw new NotImplementedException();
        }
    }
    

    }

  • Stuart Paterson 43 posts 204 karma points
    Aug 27, 2021 @ 10:14
    Stuart Paterson
    0

    Wow...Duncan thank you for posting so quickly, I'll give this a go and post back with how I get on, I've popped the controller in and no errors on build so that's a good sign :)

    Thanks, Stuart

Please Sign in or register to post replies

Write your reply to:

Draft