Hide menu Last updated: Jan 10 2017

Example: Creating custom report

(Thanks to Henrik Fransas at NetRelations for this example.)

In Episerver you can create custom reports as well as using the built-in reports in the admin view. This topic gives an example on how to create a custom report of existing pages on a website and how to export the report to Excel using EPPlus.

  1. Start by creating a Controller that looks like this:
    namespace AlloyExample.Controllers
            Area = EPiServer.PlugIn.PlugInArea.ReportMenu, 
            Url = "~/existingpagesreport",
            Category = "Existing Pages",
            DisplayName = "Pages By PageType")]
        [Authorize(Roles = "Administrators, WebAdmins")]
        public class ExistingPagesReportController : Controller
            public ActionResult Index()
                return View();

    This controller is a GuiPlugin which will be shown in the report menu and use the url /existingpagesreport. The controller is protected so you have to be part of the administrator group to use it.
  2. For the url to work, add a route to it in global.asax.cs:
    protected override void RegisterRoutes(RouteCollection routes)
                    new { controller = "ExistingPagesReport", action = "Index" });

  3. Create a simple view and make sure it shows up in the report center.
  4. Add a ViewModel with the necessary properties:
    public class ExistingPagesReportViewModel
            public IEnumerable<PageType> PageTypes { get; set; }
            public PageDataCollection Pages { get; set; }
            public string SelectedPageType { get; set; }

  5. Create a helper class to make a request against Episerver like this:
    public static class ExistingPagesHelper
            public static IEnumerable<PageType> GetAllPageTypes()
                var contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();
                return contentTypeRepository.List().OfType<PageType>();
            public static void SetPagesForPageTypeName(ExistingPagesReportViewModel model)
                var criterias = new PropertyCriteriaCollection();
                var criteria = new PropertyCriteria();
                criteria.Condition = CompareCondition.Equal;
                criteria.Name = "PageTypeID";
                criteria.Type = PropertyDataType.PageType;
                criteria.Value = model.SelectedPageType;
                criteria.Required = true;
                var pages = DataFactory.Instance.FindPagesWithCriteria(ContentReference.RootPage, criterias);
                model.Pages = pages;

  6. Update your index action to create an instance of the View model and assign all page types to it:
    public ActionResult Index()
               var model = new ExistingPagesReportViewModel { PageTypes = ExistingPagesHelper.GetAllPageTypes() };
                return View(model);

  7. Create a view and add Episerver's CSS and JavaScript to it to have the same look-and-feel as other built-in reports in Episerver. The complete view looks like this:
    @model ExistingPagesReportViewModel
    @using EPiServer.DataAbstraction
    @using EPiServer.Framework.Web.Resources
        Layout = null;
    <!DOCTYPE html>
        <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
        <!-- Shell -->
        <!-- LightTheme -->
        <link href="/EPiServer/CMS/App_Themes/Default/Styles/system.css" type="text/css" rel="stylesheet">
        <link href="/EPiServer/CMS/App_Themes/Default/Styles/ToolButton.css" type="text/css" rel="stylesheet">
        <div class="epi-contentContainer epi-padding">
            <div class="epi-contentArea">
                <div class="EP-systemImage" style="background-image: url('/App_Themes/Default/Images/ReportCenter/PublishedPages.gif');">
                    <h1 class="EP-prefix">
                        Existing Pages
                    <p class="EP-systemInfo">
                        This report displays pages that exists on the site.
                <div id="FullRegion_ValidationSummary" class="EP-validationSummary" style="color: Black; display: none;">
            @using (Html.BeginForm("ListPages", "ExistingPagesReport", FormMethod.Post))
                <script src="/Util/javascript/episerverscriptmanager.js" type="text/javascript"></script>
                <script src="/EPiServer/CMS/javascript/system.js" type="text/javascript"></script>
                <script src="/EPiServer/CMS/javascript/dialog.js" type="text/javascript"></script>
                <script src="/EPiServer/CMS/javascript/system.aspx" type="text/javascript"></script>
                <input type="hidden" id="doExport" name="doExport" value="False">
                <div class="epi-formArea">
                            Report Criteria
                        <div class="epi-size10">
                            <label for="pageTypes">Select PageType</label>
                            <select name="pageType" id="pageType">
                                @foreach (var type in Model.PageTypes.Where(w => w.ID != 1).OrderBy(o => o.Name))
                                    <option value="@type.ID" @(type.ID.ToString() == Model.SelectedPageType ? "selected=selected" : "") >@type.Name</option>
                    <div class="epitoolbuttonrow">
                        <span class="epi-cmsButton"><input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Report" type="submit" name="showReport" id="showReport" value="Show Report" onmouseover="EPi.ToolButton.MouseDownHandler(this)" onmouseout="EPi.ToolButton.ResetMouseDownHandler(this)" /></span>
                        <span class="epi-cmsButton"><input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Report" type="submit" name="exportReport" id="exportReport" value="Export Report" onmouseover="EPi.ToolButton.MouseDownHandler(this)" onmouseout="EPi.ToolButton.ResetMouseDownHandler(this)" /></span>
            @if (Model.Pages != null && Model.Pages.Count > 0)
                <div class="epi-floatLeft epi-marginVertical-small">Number of Hits: @Model.Pages.Count</div>
                <div class="epi-contentArea epi-clear">
                        <table class="epi-default epi-default-legacy" cellspacing="0" id="FullRegion_MainRegion_ReportView" style="border-style: None; width: 100%; border-collapse: collapse;">
                                <th scope="col">Page Id</th>
                                <th scope="col">Page Name</th>
                                <th scope="col">Page Url</th>
                                <th scope="col">Published Date</th>
                            @foreach (var page in Model.Pages)
                                    <td style="width: 27%;">@page.ContentLink.ID</td>
                                    <td>@(page.StartPublish.HasValue ? page.StartPublish.Value.ToString("yyyy-MM-dd HH:mm") : "Not published")</td>
        <script type="text/javascript">
            document.getElementById("exportReport").onclick = function () {
                document.getElementById("doExport").value = "True";
            document.getElementById("showReport").onclick = function () {
                document.getElementById("doExport").value = "False";

    This code has two submit buttons but only one form action; the JavaScript function connected to the click event on the export and search submit buttons updates a hidden value telling the controller to do an export of the data or not.

    The action in the controller that handles this looks like this:

            public ActionResult ListPages(FormCollection form)
                var model = new ExistingPagesReportViewModel
                    PageTypes = ExistingPagesHelper.GetAllPageTypes(),
                    SelectedPageType = form["pageType"]
                var doExport = false;
                if (bool.TryParse(form["doExport"], out doExport) && doExport && model.Pages != null && model.Pages.Count > 0)
                    Export(model.Pages, System.Web.HttpContext.Current.Response);
                return View("Index", model);
    It returns the view if the export values are not true.
  8. To export the report to Excel, use EPPlus and create a function in the same class like this:
    public void Export(PageDataCollection pagesToExport, HttpResponse response)
                using (var package = new ExcelPackage())
                    ExcelWorksheet ws = package.Workbook.Worksheets.Add("pages");
                    ws.Cells[1, 1].Value = "PageId";
                    ws.Cells[1, 2].Value = "PageName";
                    ws.Cells[1, 3].Value = "PageUrl";
                    ws.Cells[1, 4].Value = "Published Date";
                    ws.Row(1).Style.Font.Bold = true;
                    ws.Row(1).Style.Locked = true;
                    int row = 2;
                    foreach (var page in pagesToExport)
                        ws.Cells[row, 1].Value = page.ContentLink.ID;
                        ws.Cells[row, 2].Value = page.PageName;
                        ws.Cells[row, 3].Value = Url.ContentUrl(page.ContentLink);
                        ws.Cells[row, 4].Value = page.StartPublish.HasValue ? page.StartPublish.Value.ToString("yyyy-MM-dd HH:mm") : "Not published";
                    response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                    response.AddHeader("content-disposition", string.Format("attachment; filename=pages{0}.xlsx", DateTime.Now.ToString("yyyyMMdd")));