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.
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.
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
ListItemDefinition
s and these can be projected into
ActionDefinition
s.
So if you run this code, you get both a result in your WFfM database:
And you get an email sent to the appropriate place:
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.