8012: Add Excel Export functionality to DynamicForms (#8016)

* 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>
This commit is contained in:
Thierry Fleury 2024-04-16 16:59:14 +02:00 committed by GitHub
parent 530d2a9221
commit 04e9c73391
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 134 additions and 23 deletions

View File

@ -74,5 +74,11 @@ namespace Orchard.DynamicForms.Controllers {
return Redirect(Request.UrlReferrer.ToString()); return Redirect(Request.UrlReferrer.ToString());
} }
public ActionResult Export(string id) =>
File(
_formService.ExportSubmissions(id),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Export.xlsx");
} }
} }

View File

@ -52,6 +52,12 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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"> <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> <HintPath>..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.4.1.0\lib\net472\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
</Reference> </Reference>
@ -68,6 +74,7 @@
<RequiredTargetFramework>3.5</RequiredTargetFramework> <RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference> </Reference>
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Web.ApplicationServices" /> <Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Web.DynamicData" /> <Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" /> <Reference Include="System.Web.Entity" />
@ -96,6 +103,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Assets\CSS\DynamicForms-Admin.css" /> <Content Include="Assets\CSS\DynamicForms-Admin.css" />

View File

@ -2,9 +2,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Data; using System.Data;
using System.IO;
using System.Linq; using System.Linq;
using System.Web; using System.Web;
using System.Web.Mvc; using System.Web.Mvc;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using Orchard.Collections; using Orchard.Collections;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData;
@ -21,6 +25,7 @@ using Orchard.Layouts.Models;
using Orchard.Layouts.Services; using Orchard.Layouts.Services;
using Orchard.Localization.Services; using Orchard.Localization.Services;
using Orchard.Services; using Orchard.Services;
using Orchard.Utility.Extensions;
namespace Orchard.DynamicForms.Services { namespace Orchard.DynamicForms.Services {
public class FormService : IFormService { public class FormService : IFormService {
@ -152,6 +157,90 @@ namespace Orchard.DynamicForms.Services {
}; };
} }
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) { public void DeleteSubmission(Submission submission) {
_submissionRepository.Delete(submission); _submissionRepository.Delete(submission);
} }
@ -204,13 +293,14 @@ namespace Orchard.DynamicForms.Services {
} }
public DataTable GenerateDataTable(IEnumerable<Submission> submissions) { 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 columnNames = new HashSet<string>();
var dataTable = new DataTable(); var dataTable = new DataTable();
foreach (var key in foreach (var key in
from record in records from record in records
from string key in record.Item2 where !columnNames.Contains(key) from string key in record.Item2
where !columnNames.Contains(key)
where !String.IsNullOrWhiteSpace(key) where !String.IsNullOrWhiteSpace(key)
select key) { select key) {
columnNames.Add(key); columnNames.Add(key);

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Data; using System.Data;
using System.IO;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.Collections; using Orchard.Collections;
using Orchard.ContentManagement; using Orchard.ContentManagement;
@ -20,6 +21,7 @@ namespace Orchard.DynamicForms.Services {
Submission CreateSubmission(Submission submission); Submission CreateSubmission(Submission submission);
Submission GetSubmission(int id); Submission GetSubmission(int id);
IPageOfItems<Submission> GetSubmissions(string formName = null, int? skip = null, int? take = null); IPageOfItems<Submission> GetSubmissions(string formName = null, int? skip = null, int? take = null);
Stream ExportSubmissions(string formName = null);
void DeleteSubmission(Submission submission); void DeleteSubmission(Submission submission);
int DeleteSubmissions(IEnumerable<int> submissionIds); int DeleteSubmissions(IEnumerable<int> submissionIds);
void ReadElementValues(FormElement element, ReadElementValuesContext context); void ReadElementValues(FormElement element, ReadElementValuesContext context);

View File

@ -13,6 +13,9 @@
dataColumns.Add(Model.Submissions.Columns[i]); 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()) { @using (Html.BeginFormAntiForgeryPost()) {
<fieldset class="bulk-actions"> <fieldset class="bulk-actions">
<label for="publishActions">@T("Actions:")</label> <label for="publishActions">@T("Actions:")</label>

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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.Mvc" version="5.2.7" targetFramework="net48" />
<package id="Microsoft.AspNet.Razor" version="3.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.AspNet.WebPages" version="3.2.7" targetFramework="net48" />