In my Experiments with Field Data Encryption in Sitecore article, I briefly mentioned designing a custom Web Forms for Marketers (WFFM) DataProvider that uses the decorator pattern for encrypting and decrypting field values before saving and retrieving field values from the WFFM database.
From the time I penned that article up until now, I have been feeling a bit guilty that I may have left you hanging by not going into the mechanics around how I did that — I built that DataProvider for my company to be used in one of our healthcare specific content management modules.
To make up for not showing you this, I decided to build another custom WFFM DataProvider — one that will replace periods with smiley faces and the word “Sitecore” with “Sitecore®”, case insensitively.
First, I defined an interface for utility classes that will manipulate objects for us. All manipulators will consume an object of a specified type, manipulate that object in some way, and then return the maniputed object to the manipulator object’s client:
namespace Sitecore.Sandbox.Utilities.Manipulators.Base { public interface IManipulator<T> { T Manipulate(T source); } }
The first manipulator I built is a string manipulator. Basically, this object will take in a string and replace a specified substring with another:
namespace Sitecore.Sandbox.Utilities.Manipulators.Base { public interface IStringReplacementManipulator : IManipulator<string> { } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Sitecore.Diagnostics; using Sitecore.Sandbox.Utilities.Manipulators.Base; namespace Sitecore.Sandbox.Utilities.Manipulators { public class StringReplacementManipulator : IStringReplacementManipulator { private string PatternToReplace { get; set; } private string ReplacementString { get; set; } private bool IgnoreCase { get; set; } private StringReplacementManipulator(string patternToReplace, string replacementString) : this(patternToReplace, replacementString, false) { } private StringReplacementManipulator(string patternToReplace, string replacementString, bool ignoreCase) { SetPatternToReplace(patternToReplace); SetReplacementString(replacementString); SetIgnoreCase(ignoreCase); } private void SetPatternToReplace(string patternToReplace) { Assert.ArgumentNotNullOrEmpty(patternToReplace, "patternToReplace"); PatternToReplace = patternToReplace; } private void SetReplacementString(string replacementString) { ReplacementString = replacementString; } private void SetIgnoreCase(bool ignoreCase) { IgnoreCase = ignoreCase; } public string Manipulate(string source) { Assert.ArgumentNotNullOrEmpty(source, "source"); RegexOptions regexOptions = RegexOptions.None; if (IgnoreCase) { regexOptions = RegexOptions.IgnoreCase; } return Regex.Replace(source, PatternToReplace, ReplacementString, regexOptions); } public static IStringReplacementManipulator CreateNewStringReplacementManipulator(string patternToReplace, string replacementString) { return new StringReplacementManipulator(patternToReplace, replacementString); } public static IStringReplacementManipulator CreateNewStringReplacementManipulator(string patternToReplace, string replacementString, bool ignoreCase) { return new StringReplacementManipulator(patternToReplace, replacementString, ignoreCase); } } }
Clients of this class can choose to have substrings replaced in a case sensitive or insensitive manner.
By experimenting in a custom WFFM DataProvider and investigating code via .NET reflector in the WFFM assemblies, I discovered I had to create a custom object that implements Sitecore.Forms.Data.IField.
Out of the box, WFFM uses Sitecore.Forms.Data.DefiniteField — a class that implements Sitecore.Forms.Data.IField, albeit this class is declared internal and cannot be reused outside of the Sitecore.Forms.Core.dll assembly.
When I attempted to change the Value property of this object in a custom WFFM DataProvider, changes did not stick for some reason — a reason that I have not definitely ascertained.
However, to get around these lost Value property changes, I created a custom object that implements Sitecore.Forms.Data.IField:
using System; using Sitecore.Forms.Data; namespace Sitecore.Sandbox.Utilities.Manipulators.DTO { public class WFFMField : IField { public string Data { get; set; } public Guid FieldId { get; set; } public string FieldName { get; set; } public IForm Form { get; set; } public Guid Id { get; internal set; } public string Value { get; set; } } }
Next, I built a WFFM Field collection manipulator — an IEnumerable of Sitecore.Forms.Data.IField defined in Sitecore.Forms.Core.dll — using the DTO defined above:
using System.Collections.Generic; using Sitecore.Forms.Data; namespace Sitecore.Sandbox.Utilities.Manipulators.Base { public interface IWFFMFieldsManipulator : IManipulator<IEnumerable<IField>> { } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sitecore.Diagnostics; using Sitecore.Forms.Data; using Sitecore.Sandbox.Utilities.Manipulators.Base; using Sitecore.Sandbox.Utilities.Manipulators.DTO; namespace Sitecore.Sandbox.Utilities.Manipulators { public class WFFMFieldsManipulator : IWFFMFieldsManipulator { private IEnumerable<IStringReplacementManipulator> FieldValueManipulators { get; set; } private WFFMFieldsManipulator(params IStringReplacementManipulator[] fieldValueManipulator) : this(fieldValueManipulator.AsEnumerable()) { } private WFFMFieldsManipulator(IEnumerable<IStringReplacementManipulator> fieldValueManipulators) { SetFieldValueManipulator(fieldValueManipulators); } private void SetFieldValueManipulator(IEnumerable<IStringReplacementManipulator> fieldValueManipulators) { Assert.ArgumentNotNull(fieldValueManipulators, "fieldValueManipulators"); foreach (IStringReplacementManipulator fieldValueManipulator in fieldValueManipulators) { Assert.ArgumentNotNull(fieldValueManipulator, "fieldValueManipulator"); } FieldValueManipulators = fieldValueManipulators; } public IEnumerable<IField> Manipulate(IEnumerable<IField> fields) { IList<IField> maniuplatdFields = new List<IField>(); foreach (IField field in fields) { maniuplatdFields.Add(MainpulateFieldValue(field)); } return maniuplatdFields; } private IField MainpulateFieldValue(IField field) { IField maniuplatedField = CreateNewWFFMField(field); if (maniuplatedField != null) { maniuplatedField.Value = ManipulateString(maniuplatedField.Value); } return maniuplatedField; } private static IField CreateNewWFFMField(IField field) { if(field != null) { return new WFFMField { Data = field.Data, FieldId = field.FieldId, FieldName = field.FieldName, Form = field.Form, Id = field.Id, Value = field.Value }; } return null; } private string ManipulateString(string stringToManipulate) { if (string.IsNullOrEmpty(stringToManipulate)) { return string.Empty; } string manipulatedString = stringToManipulate; foreach(IStringReplacementManipulator fieldValueManipulator in FieldValueManipulators) { manipulatedString = fieldValueManipulator.Manipulate(manipulatedString); } return manipulatedString; } public static IWFFMFieldsManipulator CreateNewWFFMFieldsManipulator(params IStringReplacementManipulator[] fieldValueManipulators) { return new WFFMFieldsManipulator(fieldValueManipulators); } public static IWFFMFieldsManipulator CreateNewWFFMFieldsManipulator(IEnumerable<IStringReplacementManipulator> fieldValueManipulators) { return new WFFMFieldsManipulator(fieldValueManipulators); } } }
This manipulator consumes a collection of string manipulators and delegates to these for making changes to WFFM field values.
Now, it’s time to create a custom WFFM DataProvider that uses our manipulators defined above.
In my local sandbox Sitecore instance, I’m using SQLite for my WFFM module, and must decorate an instance of Sitecore.Forms.Data.DataProviders.SQLite.SQLiteWFMDataProvider — although this approach would be the same using MS SQL or Oracle since all WFFM DataProviders should inherit from the abstract class Sitecore.Forms.Data.DataProviders.WFMDataProviderBase:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sitecore.Diagnostics; using Sitecore.Forms.Data; using Sitecore.Forms.Data.DataProviders; using Sitecore.Forms.Data.DataProviders.SQLite; using Sitecore.Sandbox.Translation.Base; using Sitecore.Sandbox.Utilities.Manipulators.Base; using Sitecore.Sandbox.Utilities.Manipulators; namespace Sitecore.Sandbox.WFFM.Data.DataProviders { public class FieldValueManipulationSQLiteWFMDataProvider : WFMDataProviderBase { private static readonly IStringReplacementManipulator RegisteredTrademarkManipulator = StringReplacementManipulator.CreateNewStringReplacementManipulator("sitecore", " Sitecore®", true); private static readonly IStringReplacementManipulator PeriodsToSmiliesManipulator = StringReplacementManipulator.CreateNewStringReplacementManipulator("\\.", " :)"); private static readonly IWFFMFieldsManipulator FieldsManipulator = WFFMFieldsManipulator.CreateNewWFFMFieldsManipulator(RegisteredTrademarkManipulator, PeriodsToSmiliesManipulator); private WFMDataProviderBase InnerProvider { get; set; } public FieldValueManipulationSQLiteWFMDataProvider() : this(CreateNewSQLiteWFMDataProvider()) { } public FieldValueManipulationSQLiteWFMDataProvider(string connectionString) : this(CreateNewSQLiteWFMDataProvider(connectionString)) { } public FieldValueManipulationSQLiteWFMDataProvider(WFMDataProviderBase innerProvider) { SetInnerProvider(innerProvider); } private void SetInnerProvider(WFMDataProviderBase innerProvider) { Assert.ArgumentNotNull(innerProvider, "innerProvider"); InnerProvider = innerProvider; } private static WFMDataProviderBase CreateNewSQLiteWFMDataProvider() { return new SQLiteWFMDataProvider(); } private static WFMDataProviderBase CreateNewSQLiteWFMDataProvider(string connectionString) { Assert.ArgumentNotNullOrEmpty(connectionString, "connectionString"); return new SQLiteWFMDataProvider(connectionString); } public override void ChangeStorage(Guid formItemId, string newStorage) { InnerProvider.ChangeStorage(formItemId, newStorage); } public override void ChangeStorageForForms(IEnumerable<Guid> ids, string storageName) { InnerProvider.ChangeStorageForForms(ids, storageName); } public override void DeleteForms(IEnumerable<Guid> formSubmitIds) { InnerProvider.DeleteForms(formSubmitIds); } public override void DeleteForms(Guid formItemId, string storageName) { InnerProvider.DeleteForms(formItemId, storageName); } public override IEnumerable<IPool> GetAbundantPools(Guid fieldId, int top, out int total) { return InnerProvider.GetAbundantPools(fieldId, top, out total); } public override IEnumerable<IForm> GetForms(QueryParams queryParams, out int total) { return InnerProvider.GetForms(queryParams, out total); } public override IEnumerable<IForm> GetFormsByIds(IEnumerable<Guid> ids) { return InnerProvider.GetFormsByIds(ids); } public override int GetFormsCount(Guid formItemId, string storageName, string filter) { return InnerProvider.GetFormsCount(formItemId, storageName, filter); } public override IEnumerable<IPool> GetPools(Guid fieldId) { return InnerProvider.GetPools(fieldId); } public override void InsertForm(IForm form) { ManipulateFields(form); InnerProvider.InsertForm(form); } public override void ResetPool(Guid fieldId) { InnerProvider.ResetPool(fieldId); } public override IForm SelectSingleForm(Guid fieldId, string likeValue) { return InnerProvider.SelectSingleForm(fieldId, likeValue); } public override bool UpdateForm(IForm form) { ManipulateFields(form); return InnerProvider.UpdateForm(form); } private static void ManipulateFields(IForm form) { Assert.ArgumentNotNull(form, "form"); Assert.ArgumentNotNull(form.Field, "form.Field"); form.Field = FieldsManipulator.Manipulate(form.Field); } } }
This custom DataProvider creates an instance of Sitecore.Forms.Data.DataProviders.SQLite.SQLiteWFMDataProvider and delegates method calls to it.
However, before delegating to insert and update method calls, forms fields are manipulated via our manipulators objects — our manipulators will replace periods with smiley faces, and the word “Sitecore” with “Sitecore®”.
I then had to configure WFFM to use my custom DataProvider above in /App_Config/Include/forms.config:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/"> <sitecore> <!-- A bunch of stuff here --> <!-- SQLite --> <formsDataProvider type="Sitecore.Sandbox.WFFM.Data.DataProviders.FieldValueManipulationSQLiteWFMDataProvider,Sitecore.Sandbox"> <param desc="connection string">Data Source=/data/sitecore_webforms.db;version=3;BinaryGUID=true</param> </formsDataProvider> <!-- A bunch of stuff here --> </sitecore> </configuration>
For testing, I build a random WFFM form containing three fields, and created a page item to hold this form.
I then navigated to my form page and filled it in:
I then clicked the Submit button:
I then opened up the Form Reports for my form:
As you can see, it all gelled together nicely. 🙂
[…] decided to reuse my concept of manipulator from my Manipulate Field Values in a Custom Sitecore Web Forms for Marketers DataProvider article, and created a new manipulator to wrap specified tags in marquee […]
[…] a previous post on manipulating field values for WFFM forms, I had to define a new class that implements […]
Thanks for this! I was able to implement it without trouble.