Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2015/programmatic-wffm-submissions

Programmatic WFfM submissions

Published 18 May 2015
Updated 25 August 2016

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", "[email protected]"));
    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", "[email protected]"));
    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.