Programmatic WFfM submissions

Recently a client of mine came up with some requirements that involved submitting data to the Web Forms for Marketers database via code. Having done a bit of Googling, I came across a Stack Overflow answer on the same subject which seemed to offer a solution. Implementing this code did indeed allow submitting data – however it didn’t trigger Save Actions. So while the data will go to the configured WFfM data store, emails or CRM integrations won’t get triggered.

That was an issue for my client, who wanted to have email notifications as well. So starting from the Stack Overflow responses, I looked into how the WFfM save actions can be correctly triggered via code.

Starting point

In order to save a form, you need to start off with the definition of a form. This can be set up in the usual way using the WFfM designer:

Basic Form

You need to add the set of fields you want to be able to submit automatically, and you need to make a note of the IDs of the form item and each of the field items.

You also need to set up the correct set of Save Actions – here I’ve set the form to save to the forms database and to send an email.

The bare minimum of code to save data, based on the Stack Overflow answers might look a bit like this:

private ID _formID = new ID("{9A4FC79F-4F26-48EC-BAD6-CB34E83F2900}");
private string _nameFieldID = "{EE4286A7-5ED0-4374-A983-4207AAE31587}";
private string _emailFieldID = "{0869EE16-316E-44B3-B399-2BA2B0DE61E6}";
private string _messageFieldID = "{B81DA4AC-A30E-44A1-AD53-3A6DD34BD708}";

public void SubmitData()
{
    var acrList = new List<AdaptedControlResult>();
            
    acrList.Add(makeAdaptedControlResult(_nameFieldID, "Name", "John Smith"));
    acrList.Add(makeAdaptedControlResult(_emailFieldID, "Email", "J.Smith@test.com"));
    acrList.Add(makeAdaptedControlResult(_messageFieldID, "Message", "Lorem ipsum dolor sit"));

    var arl = new AdaptedResultList(acrList);

    Sitecore.Forms.Data.DataManager.InsertForm(_formID, arl, Sitecore.Data.ID.NewID, null);
}

private AdaptedControlResult makeAdaptedControlResult(string fieldID, string fieldName, string fieldValue)
{
    var controlResult = new ControlResult(fieldName, fieldValue, string.Empty) 
    { 
        FieldID = fieldID, 
        FieldName = fieldName, 
        Value = fieldValue 
    };

    return new AdaptedControlResult(controlResult, true);
}

The code needs to create a list of AdaptedControlResult objects in order to simulate the postback behaviour of a real form. And then it passes these to the configured DataManager object to save it in the database.

Simple enough – but it won’t trigger the Save Actions for the form.

Extending this

Digging through the WFfM DLLs with ILSpy lead me to the SubmitActionManager class’s Execute() method. This wraps up the behaviour of calling the assorted Save Actions – but it requires you to pass in some different things. Firstly, it requires ControlResult objects to represent the posted data rather than the AdaptedControlResult class above. And secondly, it requires you to pass in the set of ActionDefinition objects that the form defines.

When you’re looking at SubmitActionManager the obvious approach to getting the set of Actions for your form is the GetActions() method on that class. Initially this appears fine – it returns a set of Actions and they seem to match up with the actions defined in your form. However when you try to use these to submit a form, it doesn’t work… I spent some time trying to work out why – but unhelpfully the Execute() method doesn’t return any useful error messages. Looking at the code it discards the errors that are returned from internal call to ExecuteSaving().

That lead to me trying calling that method via Reflection. And this showed that any Save Action that required configuration (of which the email sending one is a prime example) was returning errors that implied missing configuration. So clearly GetActions() returns the right objects but without the required configuration. Back to the drawing board…

Further time spent trying to work out exactly what happens when WFfM does this itself lead me on to the configuration data being exposed by the SitecoreSimpleForm class. And this lead me towards the following basic code:

private ID _formID = new ID("{9A4FC79F-4F26-48EC-BAD6-CB34E83F2900}");
private string _nameFieldID = "{EE4286A7-5ED0-4374-A983-4207AAE31587}";
private string _emailFieldID = "{0869EE16-316E-44B3-B399-2BA2B0DE61E6}";
private string _messageFieldID = "{B81DA4AC-A30E-44A1-AD53-3A6DD34BD708}";

public void SubmitData()
{
    var results = new List<ControlResult>();
    results.Add(makeControlResult(_nameFieldID, "Name", "Bob Jones"));
    results.Add(makeControlResult(_emailFieldID, "Email", "B.Jones@test.com"));
    results.Add(makeControlResult(_messageFieldID, "Message", "Dolor sit amet."));

    var formItem = Sitecore.Context.Database.GetItem(_formID);

    var simpleForm = new SitecoreSimpleForm(formItem);
    var saveActionXml = simpleForm.FormItem.SaveActions;
    var actionList = Sitecore.Form.Core.ContentEditor.Data.ListDefinition.Parse(saveActionXml);

    var actionDefinitions = new List<ActionDefinition>();
    actionDefinitions.AddRange(actionList.Groups.SelectMany(x => x.ListItems).Select(li => new ActionDefinition(li.ItemID, li.Parameters) { UniqueKey = li.Unicid }));

    SubmitActionManager.Execute(_formID, results.ToArray(), actionDefinitions.ToArray());
}

private ControlResult makeControlResult(string fieldID, string fieldName, string fieldValue)
{
    return new ControlResult(fieldName, fieldValue, string.Empty)
    {
        FieldID = fieldID,
        FieldName = fieldName,
        Value = fieldValue, 
        Parameters = string.Empty
    };
}

Here the code loads the item representing the form, and uses it to construct a SitecoreSimpleForm This exposes a string property FormItem.SaveActions that contains an XML definition of the set of Save Actions attached to this form along with the configuration settings you have applied to them – like the body of your email.

This XML can be turned into a list of actions via the Sitecore.Form.Core.ContentEditor.Data.ListDefinition.Parse() operation – which effectively deserialises the XML into objects. But not the right sort of objects. So you can then use a Linq query to project these into the correct data for SubmitManager.Execute(). The data structure is nested, so a call to SelectMany() flattens the tree into a set of ListItemDefinitions and these can be projected into ActionDefinitions.

So if you run this code, you get both a result in your WFfM database:

Form Data

And you get an email sent to the appropriate place:

Form Email

Success!

If you need to retrieve any error messages from this process, you can replace the call to SubmitActionManager.Execute() with the following bit of reflection:

var execSaveMethod = typeof(SubmitActionManager).GetMethod("ExecuteSaving", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
var parameters = new object[] { _formID, results.ToArray(), actionDefinitions.ToArray(), true, Sitecore.Form.Core.Analytics.AnalyticsTracker.SessionId };
var result = (ExecuteResult)execSaveMethod.Invoke(null, parameters);

You can then look at the contents of result.Failures to see what (if any) errors were raised by your Save Actions.

Advertisements

12 thoughts on “Programmatic WFfM submissions

  1. Pingback: Programmatic WFfM submissions | Dinesh Ram Kali.
  2. First off – thank you for the code – it was a huge timesaver!
    Second – did you have any luck getting a goal to trigger? I can’t seem to get my goal to work – although my save actions (create user, etc.)
    are working just fine.

    • Thank you – glad to be of help.

      I didn’t directly test that Save Action when I was looking into this, as the scenario I was working on didn’t require analytics. But the first test I would try is this: If you set up the normal WFfM UI control pointing to the same form definition and submit the form via that route, does the goal trigger correctly? If so, then I guess there’s something goal-related missing from my code. Otherwise, that would tend to point to the form definition or the analytics configuration missing something necessary for the goal to work?

      I’ll put goal triggering on my list of stuff to investigate when I have some free time…

      • Thanks for the suggestion – I’ll try that out. I’m on 8.0 Update 3, and I’ve found other bugs in WFFM so far, so it’s entirely possible.
        For now I added my own goal code which seems to work well. The other option was to add a goal to the save actions, other than the default.

        Item goalItem = fiFullForm.Tracking.Goal;
        Sitecore.Analytics.Data.Items.PageEventItem registerthegoal = new Sitecore.Analytics.Data.Items.PageEventItem(goalItem);
        Sitecore.Analytics.Model.PageEventData eventData = Sitecore.Analytics.Tracker.Current.CurrentPage.Register(registerthegoal);
        eventData.Data = goalItem[“Description”];
        Sitecore.Analytics.Tracker.Current.Interaction.AcceptModifications();

  3. @cubeberg

    What is the extension class or dll do need to reference for fiFullForm.Tracking.Goal;

    “Tracking” seems cause issue as it not part of Sitecore Item?

    • Not sure exactly what you’re after – but the Sitecore.Forms.Core.Data.FormItem object wraps the content Item for a form, and exposes the Sitecore.Form.Core.Analytics.Tracking object? They’re both in Sitecore.Forms.Core.dll for the v2.3 release.

      • Thanks. I appreciate your reply, I am actually new to Sitecore. Please see the screenshot above listing the error I am getting on “.Tracking”. My project have reference to Sitecore.Forms.Core.dll and I have also added screenshot for version of dll I am using.

        Let me knoe if you need more info. Highly appreciate if I can solve this issue.

      • What was I doing .. I have solve it … I needed to use Sitecore.Forms.Core.Data.FormItem rather than Sitecore.Data.Items.tem

        thanks for help so far

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s