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.