I am working on a Sitecore 10 solution, where we use the Sitecore Forms, and a strange issue occurred when generating the Forms report. Depending on the data submitted by the users, the columns in Excel appeared in a different order. This was very disturbing for the customer, who expected the same report format every time they did the export.
Upon investigating, I have discovered that, for non-mandatory fields left empty by the user, no record was saved in the FieldData Sql table. Therefore, when an export was performed, if the first submitted record had empty fields, the corresponding columns were added last in the Excel file. I have reported this to Sitecore and they said it is a known bug, reference number 435640. They also provided me with guidance into creating my own custom CsvExportProvider class.
So, I have created a new class, CustomCsvExportProvider, that inherits from CsvExportProvider. Then, by using as reference the code in the original class, I have added the GenerateFileContent() method, that I have adjusted, and the EscapeCsvDelimiters() method because it is a private one. Also, I have created a CustomComparer class that I used to sort the columns in the way I wanted. The initial solution was to use the Sort order values of each field for sorting.
Let’s look at the code below:
public class CustomCsvExportProvider : CsvExportProvider
{
private readonly IFormDataProvider _formDataProvider;
public CustomCsvExportProvider(IFormDataProvider formDataProvider) : base(formDataProvider)
{
Assert.ArgumentNotNull(formDataProvider, nameof(formDataProvider));
_formDataProvider = formDataProvider;
}
protected override string GenerateFileContent(IEnumerable<FormEntry> formEntries)
{
Assert.ArgumentNotNull(formEntries, nameof(formEntries));
var formData = formEntries.OrderByDescending(item => item.Created);
var fieldColumnsList = new List<FieldData>();
var stringBuilder = new StringBuilder();
foreach (var formEntry in formData)
{
fieldColumnsList.AddRange(formEntry.Fields.Where(x => fieldColumnsList.All(c => c.FieldItemId != x.FieldItemId)));
}
if (fieldColumnsList.Count == 0)
{
return string.Empty;
}
fieldColumnsList.Sort(new CustomComparer());
[...]
}
private static string EscapeCsvDelimiters(string fieldValue)
{
[...]
}
public class CustomComparer : IComparer<FieldData>
{
public int Compare(FieldData x, FieldData y)
{
var xItem = Sitecore.Context.Database.GetItem(new Sitecore.Data.ID(x.FieldItemId));
var yItem = Sitecore.Context.Database.GetItem(new Sitecore.Data.ID(y.FieldItemId));
var xSort = 0;
if (xItem != null)
{
int.TryParse(xItem.Fields["__Sortorder"].Value, out xSort);
}
var ySort = 0;
if (yItem != null)
{
int.TryParse(yItem.Fields["__Sortorder"].Value, out ySort);
}
return xSort.CompareTo(ySort);
}
}
}
In order to get the Export to call the new class, it needs to be registered in a config file:
<services>
<register patch:instead="*[@serviceType='Sitecore.ExperienceForms.Data.IExportDataProvider, Sitecore.ExperienceForms']" serviceType="Sitecore.ExperienceForms.Data.IExportDataProvider, Sitecore.ExperienceForms"
implementationType="MyFormsProject.Providers.CustomCsvExportProvider, MyFormsProject" />
</services>
So, after setting all the Sort order values properly, the export reports were always displaying the columns in the same order. However, after a while, I have discovered another issue, that looks like another Sitecore bug. When saving a form in the Forms Dashboard, all the Sort order values from its fields are reset to 0. Until I get an answer from Sitecore Support, the quickest solution would be to add another field on the Form Field template, set the same values as I set on the Sort order and adjust the CustomComparer class to use it in sorting.
The only change we need to do is in the CustomComparer class:
public class CustomComparer : IComparer<FieldData>
{
public int Compare(FieldData x, FieldData y)
{
var xItem = Sitecore.Context.Database.GetItem(new Sitecore.Data.ID(x.FieldItemId));
var yItem = Sitecore.Context.Database.GetItem(new Sitecore.Data.ID(y.FieldItemId));
if (xItem.Fields["Export Order"] == null || yItem.Fields["Export Order"] == null)
{
return 0;
}
var xSort = 0;
if (xItem != null)
{
int.TryParse(xItem.Fields["Export Order"].Value, out xSort);
}
var ySort = 0;
if (yItem != null)
{
int.TryParse(yItem.Fields["Export Order"].Value, out ySort);
}
return xSort.CompareTo(ySort);
}
}
Last but not least, don’t forget to publish the new Export Order field, as the Sitecore.Context.Database seems to be set to web instead of master.