what would be the correct way to intercept NormalizeData method (which is private) of CSVDataExporter class to add additional normalization, without creating additional option for form submissions export ("Exaport as...")?
You could inherit CSVDataExporter and create your own implementation. NormalizeData is protected so you can override the implementation. Then replace the existing CSVDataExporter in the IOC container with your custom implemementation.
Hi David, thanks for replying.
I tried lots of ways to modify CSVDataExporter, but none of them really worked as I want. Not sure if I did it correctly though, but for now I just made a new TXTDataExporter class which inherits CSVDataExporter, and I'm doing my custom (first) normalization inside Export method of derived class and then return base's (CSVDataExporter) Export method (where dataTable getting normalized by native normalization method of CSVDataExporter), exports normal .txt of "text/plain".
I tried your idea, but it still gives me 2 same options.
I also noticed, that if my class has same MimeType or Name as CSVDataExporter, both options call my derived class Export.
I just want to intercept in CSVDataExporter's Export method now and normalize dataTable before. Without having duplicated CSV option in Export As or without having another (like TXT) option there as well.
You should create your own implementation of DataExportingService.GetAllRegisteredExporters()
Ejecting and removing CSVDataExporter for DataExporterBase won't do the trick (Episerver.Forms 4.22).
Here's some code based on Alloy and Epi 22.214.171.124.
public class DataExportingServiceInterceptor : DataExportingService
public override IEnumerable<DataExporterBase> GetAllRegisteredExporters()
var exporters = base.GetAllRegisteredExporters();
foreach (var exporter in exporters)
if (exporter.GetType() == typeof(CSVDataExporter))
yield return exporter;
Inside DependencyResolverInitialization.ConfigureContainer(), you need to add:
(locator, defaultImplementation) => new DataExportingServiceInterceptor());
And this is how it looks in edit mode:
In case several Exporters have same name, same MIMEType or FileExtension the Expoter which has lowest order will be chosen (The default Order of CSVDataExporter is 1000).
Unfortunately, the NormalizeData method in CSVDataExporter is not marked as virtual and so we have to override Export method.
Use the code below and implement your logic in MyNormalizeData method and it should work.
public class CustomCSVDataExporter : CSVDataExporter
/// Order of the Exporter, default value is 1000. In case several Exporter have same name, same MIMEType or FileExtension, this Order is matter.
public override int Order => 1;
public override string Export(DataTable dataTable)
var writer = new StringWriter();
foreach (DataRow row in dataTable.Rows)
var sb = new StringBuilder();
foreach (DataColumn column in dataTable.Columns)
sb.Append(MyNormalizeData(row[column.ColumnName] as string))
writer.Write(sb.ToString().TrimEnd(Separator)); // remove the end comma by the last AppendFormat
private string MyNormalizeData(string value)
// TODO: your code goes here .......
Thank you guys, I will difenetly come back to that issue as for now just went with additional Export As .TXT option, however intercepting existing CSV would be a "cleaner way". Cheers!