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.
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.