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());
}
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>
</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>
@ -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" />
@ -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" />

View File

@ -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;
@ -21,6 +25,7 @@ using Orchard.Layouts.Models;
using Orchard.Layouts.Services;
using Orchard.Localization.Services;
using Orchard.Services;
using Orchard.Utility.Extensions;
namespace Orchard.DynamicForms.Services {
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) {
_submissionRepository.Delete(submission);
}
@ -191,7 +280,7 @@ namespace Orchard.DynamicForms.Services {
// Collect any remaining form values not handled by any specific element.
var requestForm = _services.WorkContext.HttpContext.Request.Form;
var blackList = new[] {"__RequestVerificationToken", "formName", "contentId"};
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
@ -204,13 +293,14 @@ namespace Orchard.DynamicForms.Services {
}
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)
from string key in record.Item2
where !columnNames.Contains(key)
where !String.IsNullOrWhiteSpace(key)
select key) {
columnNames.Add(key);

View File

@ -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;
@ -20,6 +21,7 @@ namespace Orchard.DynamicForms.Services {
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);

View File

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

View File

@ -1,5 +1,7 @@
<?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" />