tirsdag den 8. november 2011

Extracting data from Sitecore WFFM Module in C#

Many of my customers uses the WFFM (Web form for Marketeers) module from sitecore.
The module lacks some features if you ask me.

For example :
  • The possibility to add a css class around the entire form when it is rendered. This can only be done for each field and not for the entire form. There is a workaround for this, that i might blog about later, but out of the box this is not possible.
  • The possibilty to send forms values to the Success page. When the Success action is set to a redirect for a form, it is not possible to extract the form data from the success page out of the box. Frontend developers often need this, when implementing Google Analytics trackings for a form submit, and would wish for sitecore to add this feature in a future release.
  • The API is not very logical and self explaning. This could use some work and i hope sitecore will consider speding some time making it more fleksible and documenting it. I've not been able to find any API documentation on SDN.

Anyway, it is a very big improvement, compared to the old Forms module, and i use it as much a possible. I like that is has integrated DMS saveactions, so setting up goels etc is quite easy.

Well, that what just some of my comments to the module itself.
What i wanted to show you, is how you can extract forms data from C#.

It is very easy to implement a custom saveaction or export action. The problem here, is that there methods can not be used in, let's say a Sitecore Job that runs every night at 03.00 am.

Both the saveaction and export actions gets the data from args, witch the Forms module will provide you with.

But what if you just want to run a nightly job, that extracts all form data from a form, and processes it in some way. For example creates and XML document that you could the save on a drive, send to an FTP server etc...

The Example below will show you how to extract form data without having access to any arguments.

Our example form is a product registration form.
I use a custom class to convert the form data into a more fleksible and more simpel format. Each form field contains more information that just the Value of the field. But let's just keep it simpel for now.

The example below extracts all saved records from a form and saves them in a simpel XML format.
It then uploads this XML file to an FTP Server without the use of any 3. party librarys, other than Sitecore of cause.

I hope this will help you if you want to do the same thing. It spend quite some time trying to find out how exactly to access the form data.

These 3 lines are the key to doing just that.
            args.Add(new GridFilter("storageName", string.Empty, GridFilter.FilterOperator.Contains));
            args.Add(new GridFilter("dataKey", formID, GridFilter.FilterOperator.Contains));
            var submits = DataManager.GetForms().GetPage(new PageCriteria(0, 0x7ffffffe), null, args);



//This example has been tested with Sitecore 6.5.0.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sitecore.Web.UI.Grids;
using Sitecore.Forms.Data;
using Sitecore.Form.DataViewer;
using System.Xml;
using System.Xml.Serialization;
using System.Text;
using System.Net;
using System.Configuration;
using System.IO;

namespace SitecoreTipsAndTricks
{
    [Serializable]
    public class ProductRegistration
    {
        public string Product { get; set; }
        public string Serialnumber { get; set; }
        public string Firstname { get; set; }
        public string SurName { get; set; }
        public string Email { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string Zip { get; set; }
        public string Country { get; set; }
        public string NewsletterSignup { get; set; }
      
      
    }

    public class SitecoreJobs
    {

        public void FormDemo01()
        {
            XmlDocument xmldoc = new XmlDocument();
          
            /// Extract Settings from Configuration File (Ex. from the web.config)
            string formID = ConfigurationManager.AppSettings["FormDataUploadID"].ToString();
            string FTPAddress = ConfigurationManager.AppSettings["FormDataUploadFTPServer"].ToString();
            string FTPUsername = ConfigurationManager.AppSettings["FormDataUploadFTPUsername"].ToString();
            string FTPPassword = ConfigurationManager.AppSettings["FormDataUploadFTPPassword"].ToString();


            List<ProductRegistration> collection = new List<ProductRegistration>();
            List<GridFilter> args = new List<Sitecore.Web.UI.Grids.GridFilter>();
            XmlDocument doc = new XmlDocument();
            StringBuilder sb = new StringBuilder();
            XmlWriter writer = XmlWriter.Create(sb);


            /// Search sitecore for the correct form and extract all form data
            args.Add(new GridFilter("storageName", string.Empty, GridFilter.FilterOperator.Contains));
            args.Add(new GridFilter("dataKey", formID, GridFilter.FilterOperator.Contains));
            var submits = DataManager.GetForms().GetPage(new PageCriteria(0, 0x7ffffffe), null, args);
          
            /// Create a Collection to Loop
            List<IForm> formlist = submits.ToList();
           
            /// Loop all forms from Database and extract all field values
            foreach (IForm frm in formlist)
            {
                ProductRegistration r = new ProductRegistration();
                List<IField> f = frm.Field.ToList();
                r.Product = f[0].Value;
                r.Serialnumber = f[1].Value;
                r.Firstname = f[2].Value;
                r.SurName = f[3].Value;
                r.Email = f[4].Value;
                r.Street = f[5].Value;
                r.City = f[6].Value;
                r.Zip = f[7].Value;
                r.Country = f[8].Value;
                collection.Add(r);
            }


            /// Serialize the Object Collection and output it to the XML Writer.
            new XmlSerializer(typeof(List<ProductRegistration>)).Serialize(writer, collection);
            writer.Flush();


            /// Create the XML Document and store it in the Data folder.
            doc.LoadXml(sb.ToString());
            string Serverpath = System.Web.HttpContext.Current.Server.MapPath(@"~/Data");
            string Filename = string.Format("{0}.{1}", DateTime.Now.ToString().Replace("-", string.Empty), "xml");
            doc.Save(string.Format(@"{0}\{1}", Serverpath, Filename));


            /// Upload the XML File to a FTP Server. All settings are defined in Configuration File
            FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(string.Format("{0}/{1}", FTPAddress, Filename));

            request.Method = WebRequestMethods.Ftp.UploadFile;
            request.Credentials = new NetworkCredential(FTPUsername, FTPPassword);
            request.UsePassive = true;
            request.UseBinary = true;
            request.KeepAlive = false;


            /// Convert filedata to binary, and upload it to the FTP Server.
            FileStream stream = File.OpenRead(string.Format(@"{0}\{1}", Serverpath, Filename));
            byte[] buffer = new byte[stream.Length];
            stream.Read(buffer, 0, buffer.Length);
            stream.Close();
          
            /// Write the File to FTP and close the Stream.
            Stream reqStream = request.GetRequestStream();
            reqStream.Write(buffer, 0, buffer.Length);
            reqStream.Close();
        }
    }
}

6 kommentarer:

  1. Thanks, Lasse. This post definitely just saved me some time.

    SvarSlet
  2. This is helpful thanks! How do you know what the fieldName is though? StorageName I get but dataKey? What if I want to filter on another ID field?

    SvarSlet
  3. Thank you for your post. Please forgive me if this is an obvious question, as I am fairly new to development. I'd like to be able to click a button and generate an xml export of all the entries for a given form. It looks like I should be able to modify this code to do that; but I'm having trouble setting that up. If you wanted to create a simple page with a button to execute this code, how would that look? Could you point me in the right direction? Yours is the only documentation I've found on dealing with data from wffm programatically.

    Thank you.

    SvarSlet
  4. Todd ->
    1. You don't. Field names are stored in the order they exist on the form.
    2. dataKey is the Sitecore ID of the form item. My code example filters on this ID, so that i only get form entries for that specific form.

    Please clarify if i misunderstand your question :-) But i don't really see why you would filter on any other ID's other that the ID's of the form.


    Sean ->

    That is indeed possible. You should remove the code related to the FTP transfer stuff. The code example already creates an XML document with all form entries. The only thing you need is to call this method from a layout or sublayout (ASPX/ASCX) using an "On button click" event.

    And then just add response.write(doc) at the end.

    Hope this helps. Otherwise, you are always welcome to add me on skype. I'll be happy to help you.

    Best Regards
    Lasse

    SvarSlet
  5. Thanks Lasse! What I am after is a way to not loop through each form submission. So if I have a form template A and I have had say 20 submissions of that form. I know the given ID of the form submission and I just want to get that one. Make sense? I tried to use the GetSingleForm call but have been able to make that work.

    SvarSlet
  6. Todd -> This is untested, but this should work :

    List SubmitIds = new List();
    SubmitIds.Add(new Sitecore.Data.ID("7a772e9e-0673-4ac2-9923-6917864c346d");
    List formlist = DataManager.GetFormsByIds(SubmitIds).ToList();

    Please let me know of the results.

    Best Regards
    Lasse

    SvarSlet