Ever needed to programmatically access Sitefinity form response data? Maybe you're building an integration, or you're on a version before 15 where the built-in export crashes when you have too many rows.

The tricky part is MVC forms dynamically generate their entry types, so you can't just reflect on a known class. The field names are buried in Sitefinity's metadata system.

The Solution: MetadataManager

Use MetadataManager combined with TypeResolutionService to discover the actual form fields at runtime:

var formEntryType = TypeResolutionService.ResolveType(form.EntriesTypeName);
var metadataManager = MetadataManager.GetManager();
var metaType = metadataManager.GetMetaType(formEntryType);

foreach (var field in metaType.Fields)
{
    if (!field.IsInternal)
        // This is an actual form field
}

This gives you all the custom fields defined on the form without hardcoding anything.

Full Handler Implementation

I built an HTTP handler that takes a form name and date range, discovers all the fields, and exports everything to a proper Excel file using OpenXML.

FormDownloadHandler.ashx.csView on GitHub
using System;
using System.Web;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using Telerik.Sitefinity.Modules.Forms;
using Telerik.Sitefinity.Security;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using Telerik.Sitefinity.Model;
using Telerik.Sitefinity.Forms.Model;
using Telerik.Sitefinity.Utilities.TypeConverters;
using Telerik.Sitefinity.Data.Metadata;

namespace OAP.Handlers
{
    public class FormDownloadHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            // Check if the current user is an administrator
            if (!SecurityManager.IsBackendUser())
            {
                context.Response.StatusCode = 403;
                context.Response.Write("Access denied. Administrator privileges required.");
                return;
            }

            // Get parameters from query string
            var formName = context.Request.QueryString["Name"];
            var fromDateStr = context.Request.QueryString["FromDate"];

            if (string.IsNullOrEmpty(formName) || string.IsNullOrEmpty(fromDateStr))
            {
                context.Response.StatusCode = 400;
                context.Response.Write("Missing required parameters: Name and FromDate");
                return;
            }

            DateTime fromDate;

            if (!DateTime.TryParse(fromDateStr, out fromDate))
            {
                context.Response.StatusCode = 400;
                context.Response.Write("Invalid FromDate format");
                return;
            }

            try
            {
                var formsManager = FormsManager.GetManager();

                // Get the form description
                var form = formsManager.GetForms().FirstOrDefault(f => f.Name.ToLower() == formName.ToLower());
                if (form == null)
                {
                    context.Response.StatusCode = 404;
                    context.Response.Write("Form not found");
                    return;
                }

                // Get form entries filtered by date
                var entries = formsManager.GetFormEntries(form)
                    .Where(e => e.SubmittedOn >= fromDate)
                    .OrderBy(e => e.SubmittedOn)
                    .ToList();

                if (!entries.Any())
                {
                    context.Response.StatusCode = 204;
                    context.Response.Write("No form entries found for the specified date range");
                    return;
                }

                // Get field names using MetadataManager (works for MVC forms)
                var distinctFields = GetFormFieldsFromMetadata(form);

                // Add standard columns including UserId and Username
                var headers = new List<string> { "SubmittedOn", "IpAddress", "UserId", "Username" };
                headers.AddRange(distinctFields);

                // Create Excel file using DocumentFormat.OpenXml
                byte[] fileBytes;
                using (var stream = new MemoryStream())
                {
                    using (var document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook))
                    {
                        // Add workbook part
                        var workbookPart = document.AddWorkbookPart();
                        workbookPart.Workbook = new Workbook();

                        // Add worksheet part
                        var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
                        worksheetPart.Worksheet = new Worksheet(new SheetData());

                        // Add sheets to workbook
                        var sheets = workbookPart.Workbook.AppendChild(new Sheets());
                        var sheet = new Sheet()
                        {
                            Id = workbookPart.GetIdOfPart(worksheetPart),
                            SheetId = 1,
                            Name = "Form Responses"
                        };
                        sheets.Append(sheet);

                        var sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

                        // Write header row
                        var headerRow = new Row() { RowIndex = 1 };
                        for (int i = 0; i < headers.Count; i++)
                        {
                            var cell = new Cell()
                            {
                                CellReference = GetCellReference(1, i),
                                DataType = CellValues.String,
                                CellValue = new CellValue(headers[i])
                            };
                            headerRow.Append(cell);
                        }
                        sheetData.Append(headerRow);

                        // Write data rows
                        uint rowIndex = 2;
                        foreach (var entry in entries)
                        {
                            var dataRow = new Row() { RowIndex = rowIndex };

                            // SubmittedOn
                            dataRow.Append(new Cell()
                            {
                                CellReference = GetCellReference(rowIndex, 0),
                                DataType = CellValues.String,
                                CellValue = new CellValue(entry.SubmittedOn.ToString("yyyy-MM-dd HH:mm:ss"))
                            });

                            // IpAddress
                            dataRow.Append(new Cell()
                            {
                                CellReference = GetCellReference(rowIndex, 1),
                                DataType = CellValues.String,
                                CellValue = new CellValue(entry.IpAddress ?? "")
                            });

                            // UserId
                            dataRow.Append(new Cell()
                            {
                                CellReference = GetCellReference(rowIndex, 2),
                                DataType = CellValues.String,
                                CellValue = new CellValue(entry.UserId.ToString())
                            });

                            // Username
                            dataRow.Append(new Cell()
                            {
                                CellReference = GetCellReference(rowIndex, 3),
                                DataType = CellValues.String,
                                CellValue = new CellValue(entry.Username ?? "")
                            });

                            // Form fields (offset by 4 now)
                            for (int i = 0; i < distinctFields.Count; i++)
                            {
                                var field = distinctFields[i];
                                var value = entry.GetValue(field)?.ToString() ?? "";
                                dataRow.Append(new Cell()
                                {
                                    CellReference = GetCellReference(rowIndex, i + 4),
                                    DataType = CellValues.String,
                                    CellValue = new CellValue(value)
                                });
                            }

                            sheetData.Append(dataRow);
                            rowIndex++;
                        }

                        workbookPart.Workbook.Save();
                    }

                    fileBytes = stream.ToArray();
                }

                context.Response.Clear();
                context.Response.Buffer = true;
                context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                context.Response.AddHeader("Content-Disposition", $"attachment; filename=\"form_{formName}_{DateTime.Now:yyyyMMdd}.xlsx\"");
                context.Response.BinaryWrite(fileBytes);
                context.Response.Flush();
                context.ApplicationInstance.CompleteRequest();
            }
            catch (Exception ex)
            {
                context.Response.StatusCode = 500;
                context.Response.Write($"Error: {ex.Message} - {ex.StackTrace}");
            }
        }

        /// <summary>
        /// Gets form field names from MetadataManager - works for MVC forms
        /// </summary>
        private List<string> GetFormFieldsFromMetadata(FormDescription form)
        {
            var fieldNames = new List<string>();

            try
            {
                // Resolve the form entry type
                var formEntryType = TypeResolutionService.ResolveType(form.EntriesTypeName);
                if (formEntryType == null)
                    return fieldNames;

                // Get metadata for this type
                var metadataManager = MetadataManager.GetManager();
                var metaType = metadataManager.GetMetaType(formEntryType);

                if (metaType != null && metaType.Fields != null)
                {
                    // Get non-internal fields (the actual form fields)
                    foreach (var field in metaType.Fields)
                    {
                        // Skip internal/system fields
                        if (field.IsInternal)
                            continue;

                        // Skip common system field names
                        if (IsSystemField(field.FieldName))
                            continue;

                        fieldNames.Add(field.FieldName);
                    }
                }
            }
            catch
            {
                // Fallback: try to get fields from reflection on the type
                fieldNames = GetFormFieldsFromReflection(form);
            }

            return fieldNames;
        }

        /// <summary>
        /// Fallback: Get field names using reflection on the entry type
        /// </summary>
        private List<string> GetFormFieldsFromReflection(FormDescription form)
        {
            var fieldNames = new List<string>();

            try
            {
                var formEntryType = TypeResolutionService.ResolveType(form.EntriesTypeName);
                if (formEntryType == null)
                    return fieldNames;

                // Get properties that are not inherited from FormEntry base class
                var baseType = typeof(FormEntry);
                var baseProps = baseType.GetProperties().Select(p => p.Name).ToList();

                foreach (var prop in formEntryType.GetProperties())
                {
                    // Skip base FormEntry properties
                    if (baseProps.Contains(prop.Name))
                        continue;

                    // Skip system fields
                    if (IsSystemField(prop.Name))
                        continue;

                    fieldNames.Add(prop.Name);
                }
            }
            catch
            {
                // If all else fails, return empty
            }

            return fieldNames;
        }

        /// <summary>
        /// Check if field name is a system field that should be excluded
        /// </summary>
        private bool IsSystemField(string fieldName)
        {
            var systemFields = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            {
                "Id",
                "ApplicationName",
                "LastModified",
                "DateCreated",
                "Owner",
                "SubmittedOn",
                "IpAddress",
                "UserId",
                "ReferralCode",
                "Username",
                "Provider"
            };

            return systemFields.Contains(fieldName);
        }

        private static string GetCellReference(uint row, int col)
        {
            var columnLetter = GetColumnLetter(col);
            return $"{columnLetter}{row}";
        }

        private static string GetColumnLetter(int col)
        {
            var dividend = col + 1;
            var columnLetter = string.Empty;

            while (dividend > 0)
            {
                var modulo = (dividend - 1) % 26;
                columnLetter = Convert.ToChar(65 + modulo) + columnLetter;
                dividend = (dividend - modulo) / 26;
            }

            return columnLetter;
        }

        public bool IsReusable
        {
            get { return false; }
        }
    }
}

Usage

Just drop it in and call it:

/FormDownload.ashx?Name=ContactForm&FromDate=2026-01-01

It's restricted to backend users only, so random visitors can't download your form data.

The MetadataManager approach also has a reflection fallback in case the metadata lookup fails, so it works across different Sitefinity versions.