Skip to content

Commit

Permalink
8012: Add Excel Export functionality to DynamicForms (#8016)
Browse files Browse the repository at this point in the history
* Add Excel Export functionality to DynamicForms.

* Using the net46-targeted DLL of DocumentFormat.OpenXml instead of net40

* Updating DocumentFormat.OpenXml to latest (3.0.2) version

* Code styling

---------

Co-authored-by: Benedek Farkas <benedek.farkas@lombiq.com>
  • Loading branch information
TFleury and BenedekFarkas committed Apr 16, 2024
1 parent 530d2a9 commit 04e9c73
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,11 @@ public class SubmissionAdminController : Controller {

return Redirect(Request.UrlReferrer.ToString());
}

public ActionResult Export(string id) =>
File(
_formService.ExportSubmissions(id),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Export.xlsx");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="DocumentFormat.OpenXml, Version=3.0.2.0, Culture=neutral, PublicKeyToken=8fb06cb64d019a17, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\DocumentFormat.OpenXml.3.0.2\lib\net46\DocumentFormat.OpenXml.dll</HintPath>
</Reference>
<Reference Include="DocumentFormat.OpenXml.Framework, Version=3.0.2.0, Culture=neutral, PublicKeyToken=8fb06cb64d019a17, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\DocumentFormat.OpenXml.Framework.3.0.2\lib\net46\DocumentFormat.OpenXml.Framework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.4.1.0\lib\net472\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
</Reference>
Expand All @@ -68,6 +74,7 @@
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
Expand Down Expand Up @@ -96,6 +103,7 @@
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Xml.Linq" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\CSS\DynamicForms-Admin.css" />
Expand Down Expand Up @@ -597,4 +605,4 @@
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>
132 changes: 111 additions & 21 deletions src/Orchard.Web/Modules/Orchard.DynamicForms/Services/FormService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using Orchard.Collections;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
Expand All @@ -21,6 +25,7 @@
using Orchard.Layouts.Services;
using Orchard.Localization.Services;
using Orchard.Services;
using Orchard.Utility.Extensions;

namespace Orchard.DynamicForms.Services {
public class FormService : IFormService {
Expand All @@ -38,16 +43,16 @@ public class FormService : IFormService {
private readonly ICultureAccessor _cultureAccessor;

public FormService(
ILayoutSerializer serializer,
IClock clock,
IRepository<Submission> submissionRepository,
IFormElementEventHandler elementHandlers,
IContentDefinitionManager contentDefinitionManager,
IBindingManager bindingManager,
IDynamicFormEventHandler formEventHandler,
ILayoutSerializer serializer,
IClock clock,
IRepository<Submission> submissionRepository,
IFormElementEventHandler elementHandlers,
IContentDefinitionManager contentDefinitionManager,
IBindingManager bindingManager,
IDynamicFormEventHandler formEventHandler,
Lazy<IEnumerable<IElementValidator>> validators,
IDateLocalizationServices dateLocalizationServices,
IOrchardServices services,
IDateLocalizationServices dateLocalizationServices,
IOrchardServices services,
ICultureAccessor cultureAccessor) {

_serializer = serializer;
Expand Down Expand Up @@ -152,6 +157,90 @@ public class FormService : IFormService {
};
}

public Stream ExportSubmissions(string formName = null) {
var stream = new MemoryStream();

string GetColumnId(int columnNumber) {
string result = "";
do {
result = ((char)((columnNumber - 1) % 26 + (int)'A')).ToString() + result;
columnNumber = (columnNumber - 1) / 26;
} while (columnNumber != 0);
return result;
}

// Create a spreadsheet document.
var spreadsheetDocument = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook);

// Add a WorkbookPart to the document.
var workbookpart = spreadsheetDocument.AddWorkbookPart();
workbookpart.Workbook = new Workbook();

// Add a WorksheetPart to the WorkbookPart.
var worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
var sheetData = new SheetData();
worksheetPart.Worksheet = new Worksheet(sheetData);

// Add Sheets to the Workbook.
var sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild(new Sheets());

// Fetch submissions
var query = _submissionRepository.Table;

if (!String.IsNullOrWhiteSpace(formName)) {
query = query.Where(x => x.FormName == formName);
}

var submissions = new Orderable<Submission>(query).Desc(x => x.CreatedUtc).Queryable.ToArray();

foreach (var formGroup in submissions.GroupBy(s => s.FormName)) {
// Append a new worksheet and associate it with the workbook.
var sheet = new Sheet() {
Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = formGroup.Key
};
sheets.Append(sheet);

var data = GenerateDataTable(formGroup);
uint rowIndex = 1;

var headerRow = new Row { RowIndex = rowIndex };
sheetData.Append(headerRow);

for (int i = 0; i < data.Columns.Count; i++) {
var title = data.Columns[i].ToString().CamelFriendly();
headerRow.Append(new Cell {
CellReference = GetColumnId(i + 1) + rowIndex,
InlineString = new InlineString { Text = new Text(title) },
DataType = new EnumValue<CellValues>(CellValues.InlineString),
});
}

foreach (DataRow dataRow in data.Rows) {
rowIndex++;
var row = new Row { RowIndex = rowIndex };
sheetData.Append(row);
for (int i = 0; i < data.Columns.Count; i++) {
var value = dataRow[data.Columns[i]];
row.Append(new Cell {
CellReference = GetColumnId(i + 1) + rowIndex,
InlineString = new InlineString { Text = new Text(value.ToString()) },
DataType = new EnumValue<CellValues>(CellValues.InlineString),
});
}
}
}

workbookpart.Workbook.Save();

// Close the document.
spreadsheetDocument.Dispose();
stream.Seek(0, SeekOrigin.Begin);

return stream;
}

public void DeleteSubmission(Submission submission) {
_submissionRepository.Delete(submission);
}
Expand Down Expand Up @@ -191,10 +280,10 @@ public class FormService : IFormService {

// Collect any remaining form values not handled by any specific element.
var requestForm = _services.WorkContext.HttpContext.Request.Form;
var blackList = new[] {"__RequestVerificationToken", "formName", "contentId"};
foreach (var key in
from string key in requestForm
where !String.IsNullOrWhiteSpace(key) && !blackList.Contains(key) && values[key] == null
var blackList = new[] { "__RequestVerificationToken", "formName", "contentId" };
foreach (var key in
from string key in requestForm
where !String.IsNullOrWhiteSpace(key) && !blackList.Contains(key) && values[key] == null
select key) {

values.Add(key, requestForm[key]);
Expand All @@ -204,13 +293,14 @@ public class FormService : IFormService {
}

public DataTable GenerateDataTable(IEnumerable<Submission> submissions) {
var records = submissions.Select(x => Tuple.Create(x, x.ToNameValues())).ToArray();
var records = submissions.Select(x => System.Tuple.Create(x, x.ToNameValues())).ToArray();
var columnNames = new HashSet<string>();
var dataTable = new DataTable();

foreach (var key in
from record in records
from string key in record.Item2 where !columnNames.Contains(key)
foreach (var key in
from record in records
from string key in record.Item2
where !columnNames.Contains(key)
where !String.IsNullOrWhiteSpace(key)
select key) {
columnNames.Add(key);
Expand Down Expand Up @@ -282,7 +372,7 @@ public class FormService : IFormService {
if (form.Publication == "Publish" || !contentTypeSettings.Draftable) {
_contentManager.Publish(contentItem);
}

return contentItem;
}

Expand All @@ -307,8 +397,8 @@ public class FormService : IFormService {
}

private static void InvokePartBindings(
ContentItem contentItem,
IEnumerable<ContentPartBindingDescriptor> lookup,
ContentItem contentItem,
IEnumerable<ContentPartBindingDescriptor> lookup,
PartBindingSettings partBindingSettings,
string value) {

Expand Down Expand Up @@ -348,7 +438,7 @@ public class FormService : IFormService {
if (field == null)
return;

var fieldBindingDescriptorsQuery =
var fieldBindingDescriptorsQuery =
from partBindingDescriptor in lookup
where partBindingDescriptor.Part.PartDefinition.Name == partBindingSettings.Name
from fieldBindingDescriptor in partBindingDescriptor.FieldBindings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Web.Mvc;
using Orchard.Collections;
using Orchard.ContentManagement;
Expand All @@ -20,6 +21,7 @@ public interface IFormService : IDependency {
Submission CreateSubmission(Submission submission);
Submission GetSubmission(int id);
IPageOfItems<Submission> GetSubmissions(string formName = null, int? skip = null, int? take = null);
Stream ExportSubmissions(string formName = null);
void DeleteSubmission(Submission submission);
int DeleteSubmissions(IEnumerable<int> submissionIds);
void ReadElementValues(FormElement element, ReadElementValuesContext context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
dataColumns.Add(Model.Submissions.Columns[i]);
}
}
<div class="manage">
@Html.ActionLink(T("Export").Text, "Export", "SubmissionAdmin", new { id = Model.FormName, area = "Orchard.DynamicForms" }, new { @class = "button primaryAction" })
</div>
@using (Html.BeginFormAntiForgeryPost()) {
<fieldset class="bulk-actions">
<label for="publishActions">@T("Actions:")</label>
Expand Down
4 changes: 3 additions & 1 deletion src/Orchard.Web/Modules/Orchard.DynamicForms/packages.config
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DocumentFormat.OpenXml" version="3.0.2" targetFramework="net48" />
<package id="DocumentFormat.OpenXml.Framework" version="3.0.2" targetFramework="net48" />
<package id="Microsoft.AspNet.Mvc" version="5.2.7" targetFramework="net48" />
<package id="Microsoft.AspNet.Razor" version="3.2.7" targetFramework="net48" />
<package id="Microsoft.AspNet.WebPages" version="3.2.7" targetFramework="net48" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="4.1.0" targetFramework="net48" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net48" />
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net48" />
</packages>
</packages>

0 comments on commit 04e9c73

Please sign in to comment.