Quantcast
Channel: Blog posts by Henrik Fransas
Viewing all articles
Browse latest Browse all 70

Create custom report in MVC with Excel Export

$
0
0

Lately I have seen a couple of questions about how to make a excel export from a custom report and also if it is possible to make a custom report in MVC.

I had never done any custom reports in MVC so I decided to try this things out and it showed that is was pretty simple.

For the excel-export function I am using the external packaged EPPlus.

I decided to make a very simple report on the pages that are indexed in Find and the code in this example is NOT production friendly since it is only there to guide on how it could be done. So feel free to get inspired from it, but do NOT use it by just copying it into production code.

First I created a Controller that looks like this:

namespace AlloyExample.Controllers
{
    [EPiServer.PlugIn.GuiPlugIn(
        Area = EPiServer.PlugIn.PlugInArea.ReportMenu, 
        Url = "~/EpiserverFindReport",
        Category = "Episerver Find Reports",
        RequiredAccess = AccessLevel.Administer,
        DisplayName = "Episerver Find report")]
    [Authorize(Roles = "Administrators, WebAdmins")]
    public class EpiserverFindReportController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

Here I define that this controller also is a GuiPlugin that should be showed in the reportmenu and I give it a url that is /EpiserverFindReport. I also protect it so you have to be part of the administrator group to be able to use it.

For this url to work, we need to add a route to it so I add this in global.asax.cs

protected override void RegisterRoutes(RouteCollection routes)
        {
            base.RegisterRoutes(routes);
            routes.MapRoute("episerverfindreport", "EpiserverFindReport/{action}",
            new { controller = "EpiserverFindReport", action = "Index" });
        }

After that I created a simple view and just tried out that it showed up in the report center.

To make it a little more interesting I first added a ViewModel with the properties I needed. It looks like this:

public class EpiserverFindReportViewModel
    {
        public List<string> PageTypes { get; set; }
        public List<SitePageData> Pages { get; set; }
        public int TotalCount { get; set; }
    }

Then I created a helper class to make my Find request and that looks like this:

public static class EpiFindHelper
    {
        public static List<string> GetAllPageTypeNames()
        {
            var pagetypeNames = SearchClient.Instance
            .Search<SitePageData>()
            .TermsFacetFor(f => f.PageTypeName, f=>f.Size = 50)
            .Take(0)
            .Select(s => new { PageTypeName = s.PageTypeName })
            .GetResult();
            return pagetypeNames.TermsFacetFor(f => f.PageTypeName).Terms.Select(s => s.Term).ToList();
        }
        public static void SetPagesForPageTypeName(EpiserverFindReportViewModel model, string pageTypeName)
        {
            var pages = SearchClient.Instance
            .Search<SitePageData>()
            .Filter(f=>f.PageTypeName.Match(pageTypeName))
            .Take(1000)
            .GetContentResult();
            model.Pages = pages.Items.ToList();
            model.TotalCount = pages.TotalMatching;
        }
    }

After that I updated my index action so it created an instance of the view model and assigned all pagetypes to it. So now it looks like this.

public ActionResult Index()
        {
            var model = new EpiserverFindReportViewModel {PageTypes = EpiFindHelper.GetAllPageTypeNames()};
            return View(model);
        }

Now I created my view and since I wanted it to feel the same way as the built in reports I added Epi’s css to it. The complete view looks like this:

@model AlloyExample.Controllers.EpiserverFindReportViewModel
@using EPiServer.Framework.Web.Resources
@{
    Layout = null;
}<!DOCTYPE html><html><head><title>@ViewBag.Title</title><meta http-equiv="X-UA-Compatible" content="IE=Edge" /><!-- Shell -->
    @Html.Raw(ClientResources.RenderResources("ShellCore"))<!-- LightTheme -->
    @Html.Raw(ClientResources.RenderResources("ShellCoreLightTheme"))</head><body>
    @Html.Raw(Html.ShellInitializationScript())<div class="epi-contentContainer epi-padding"><div class="epi-contentArea"><div class="EP-systemImage"><h1 class="EP-prefix">
                Indexed Pages</h1><p class="EP-systemInfo">
                This report displays pages that has been indexed in Find.</p></div><div id="FullRegion_ValidationSummary" class="EP-validationSummary" style="color: Black; display: none;"></div></div>
    @using (Html.BeginForm("ListPages", "EpiserverFindReport", FormMethod.Post))
    {<input type="hidden" id="doExport" name="doExport" value="False"><div class="epi-formArea"><fieldset><legend>
                    Report Criteria</legend><div class="epi-size10"><label for="pageTypes">Select PageType</label>
                    @Html.DropDownListFor(x => x.PageTypes, new SelectList(Model.PageTypes), new {id = "pageTypes", name = "pageTypes"})</div></fieldset><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></div></div>
    }
    @if (Model.Pages != null && Model.Pages.Count > 0)
    {<div class="epi-floatLeft epi-marginVertical-small">Number of Hits: @Model.TotalCount</div><div class="epi-contentArea epi-clear"><div><table class="epi-default epi-default-legacy" cellspacing="0" id="FullRegion_MainRegion_ReportView" style="border-style: None; width: 100%; border-collapse: collapse;"><tr><th scope="col">Page Id</th><th scope="col">Page Name</th><th scope="col">Page Url</th></tr>
                    @foreach (var page in Model.Pages)
                    {<tr><td style="width: 27%;">@page.ContentLink.ID</td><td>@page.PageName</td><td>@Url.ContentUrl(page.ContentLink)</td></tr>
                    }</table></div></div>
    }</div><script type="text/javascript">
        document.getElementById("exportReport").onclick = function () {
            document.getElementById("doExport").value = "True";
            //                            ^
        };</script></body></html>

As you can see I have two submit buttons but only one form actions and because of that I have connected javascript function to the click event on the export submit button that updates a hidden value telling the controller to do an export of the data.

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

[HttpPost]
        public ActionResult ListPages(FormCollection form)
        {
            var model = new EpiserverFindReportViewModel
            {
                PageTypes = EpiFindHelper.GetAllPageTypeNames()
            };
            EpiFindHelper.SetPagesForPageTypeName(model, form["pageTypes"]);
            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);
        }

As you can see it returns the view of the export values is not true. To do the export I use EPPlus and for this simple export I just created a function in the same class that looks like this:

public void Export(IEnumerable<SitePageData> 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.Row(1).Style.Font.Bold = true;
                ws.Row(1).Style.Locked = true;
                int row = 2;
                foreach (SitePageData 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);
                    ++row;
                }
                response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                response.AddHeader("content-disposition", string.Format("attachment; filename=pages{0}.xlsx", DateTime.Now.ToString("yyyyMMdd")));
                response.BinaryWrite(package.GetAsByteArray());
                response.Flush();
                response.End();
            }
        }

This is a simple example on how to make a custom report in MVC with an Excel export function in it. Hope that it will help you get started and make your own great reports!

Image CompleteSolution.PNG

Happy coding!!


Viewing all articles
Browse latest Browse all 70

Trending Articles