Asontu

Herman Scheele
.NET, C#, Sitecore developer and Data Science engineer @ theFactor.e.

profile for asontu at Stack Overflow, Q&A for professional and enthusiast programmers

Tech-blog about C#, Sitecore, T-SQL, Front-end, Python, Data Science and everything in between run by Herman Scheele

RSS Archive
15 August 2021

Sorting Sitecore Forms exports

If you’ve ever exported form submissions as CSV file from Sitecore 9+ Forms, you might’ve noticed the order in which the columns are exported seems completely random. We can fix this by implementing our own IExportDataProvider.

It seems that as of Sitecore 10.2+ the export does properly sort the columns and the solution below is no longer needed

The way I’ve implemented this is by decompiling the Sitecore native CsvExportProvider and adding the sorting code to that, but I’m not gonna post the decompiled code here publicly. So instead I’ll describe how to do that part of it.

Firstly decompile Sitecore.ExperienceForms.Client.Data.CsvExportProvider and take all the code needed to replicate the existing CSV export. The Sitecore 9.3 version of that has a GenerateFileContent(IEnumerable<FormEntry> formEntries) method that needs the sorting code added. Add the Guid formId from the IExportDataProvider’s Export() method to the signature.

GenerateFileContent() consists of two parts, one that determines which columns to export and one that iterates the submissions to generate the rows. Sorting needs to happen in between those parts, after the code checks whether fieldDatas has a .Count higher than 0, add the line SortFieldData(formId, ref fieldDatas);.

The implementation of SortFieldData() then looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static void SortFieldData(Guid formId, ref List<FieldData> fieldDatas)
{
	var formFieldIds = fieldDatas.Select(fd => fd.FieldItemId).ToList();
	var sortingDictionary = Context.Database.GetItem(new Sitecore.Data.ID(formId))
		.Descendants(i => formFieldIds.Contains(i.ID.Guid))
		.Select((item, index) => new KeyValuePair<Guid, int>(item.ID.Guid, index))
		.ToDictionary();

	var idCount = sortingDictionary.Count;
	var missingIds = formFieldIds
		.Where(id => !sortingDictionary.ContainsKey(id))
		.Select((id, index) => new KeyValuePair<Guid, int>(id, index + idCount));

	sortingDictionary.AddRange(missingIds);

	fieldDatas = fieldDatas.OrderBy(fd => sortingDictionary[fd.FieldItemId]).ToList();
}

This code uses a couple of extension methods that I use a lot in my code, so I’ll share them here too:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public static class MyExtensions
{
	public static IEnumerable<Item> Descendants(this Item root, Func<Item, bool> where)
	{
		if (root is null || where is null)
		{
			yield break;
		}

		foreach (Item i in root.Children)
		{
			if (where(i))
			{
				yield return i;
			}

			foreach (Item c in i.Descendants(where))
			{
				yield return c;
			}
		}
	}

	public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> collection)
	{
		return collection.ToDictionary(c => c.Key, c => c.Value);
	}

	public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> source, IEnumerable<KeyValuePair<TKey, TValue>> collection)
	{
		if (source is null)
		{
			throw new ArgumentNullException(nameof(source));
		}

		if (collection != null)
		{
			foreach (var (key, value) in collection)
			{
				if (!source.ContainsKey(key))
				{
					source.Add(key, value);
				}
			}
		}
	}

	public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> source, out TKey key, out TValue value)
	{
		key = source.Key;
		value = source.Value;
	}
}

The sorting magic really happens in the .Descendants() extension method, since Item.Children returns an item’s children in the order that they appear in the Content Editor, which is also the order in which they appear on the form.

After that it’s simply translating that order to a Dictionary<Guid, int> to be able to .OrderBy() and adding the fields that weren’t found at the end, so form fields that have been removed since the first submissions will become the last columns.

tags: C# - Back-end - Sitecore