mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-04-05 12:53:16 +08:00
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:
parent
530d2a9221
commit
04e9c73391
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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" />
|
||||||
@ -597,4 +605,4 @@
|
|||||||
</FlavorProperties>
|
</FlavorProperties>
|
||||||
</VisualStudio>
|
</VisualStudio>
|
||||||
</ProjectExtensions>
|
</ProjectExtensions>
|
||||||
</Project>
|
</Project>
|
@ -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 {
|
||||||
@ -38,16 +43,16 @@ namespace Orchard.DynamicForms.Services {
|
|||||||
private readonly ICultureAccessor _cultureAccessor;
|
private readonly ICultureAccessor _cultureAccessor;
|
||||||
|
|
||||||
public FormService(
|
public FormService(
|
||||||
ILayoutSerializer serializer,
|
ILayoutSerializer serializer,
|
||||||
IClock clock,
|
IClock clock,
|
||||||
IRepository<Submission> submissionRepository,
|
IRepository<Submission> submissionRepository,
|
||||||
IFormElementEventHandler elementHandlers,
|
IFormElementEventHandler elementHandlers,
|
||||||
IContentDefinitionManager contentDefinitionManager,
|
IContentDefinitionManager contentDefinitionManager,
|
||||||
IBindingManager bindingManager,
|
IBindingManager bindingManager,
|
||||||
IDynamicFormEventHandler formEventHandler,
|
IDynamicFormEventHandler formEventHandler,
|
||||||
Lazy<IEnumerable<IElementValidator>> validators,
|
Lazy<IEnumerable<IElementValidator>> validators,
|
||||||
IDateLocalizationServices dateLocalizationServices,
|
IDateLocalizationServices dateLocalizationServices,
|
||||||
IOrchardServices services,
|
IOrchardServices services,
|
||||||
ICultureAccessor cultureAccessor) {
|
ICultureAccessor cultureAccessor) {
|
||||||
|
|
||||||
_serializer = serializer;
|
_serializer = serializer;
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
@ -191,10 +280,10 @@ namespace Orchard.DynamicForms.Services {
|
|||||||
|
|
||||||
// Collect any remaining form values not handled by any specific element.
|
// Collect any remaining form values not handled by any specific element.
|
||||||
var requestForm = _services.WorkContext.HttpContext.Request.Form;
|
var requestForm = _services.WorkContext.HttpContext.Request.Form;
|
||||||
var blackList = new[] {"__RequestVerificationToken", "formName", "contentId"};
|
var blackList = new[] { "__RequestVerificationToken", "formName", "contentId" };
|
||||||
foreach (var key in
|
foreach (var key in
|
||||||
from string key in requestForm
|
from string key in requestForm
|
||||||
where !String.IsNullOrWhiteSpace(key) && !blackList.Contains(key) && values[key] == null
|
where !String.IsNullOrWhiteSpace(key) && !blackList.Contains(key) && values[key] == null
|
||||||
select key) {
|
select key) {
|
||||||
|
|
||||||
values.Add(key, requestForm[key]);
|
values.Add(key, requestForm[key]);
|
||||||
@ -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);
|
||||||
@ -282,7 +372,7 @@ namespace Orchard.DynamicForms.Services {
|
|||||||
if (form.Publication == "Publish" || !contentTypeSettings.Draftable) {
|
if (form.Publication == "Publish" || !contentTypeSettings.Draftable) {
|
||||||
_contentManager.Publish(contentItem);
|
_contentManager.Publish(contentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentItem;
|
return contentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,8 +397,8 @@ namespace Orchard.DynamicForms.Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void InvokePartBindings(
|
private static void InvokePartBindings(
|
||||||
ContentItem contentItem,
|
ContentItem contentItem,
|
||||||
IEnumerable<ContentPartBindingDescriptor> lookup,
|
IEnumerable<ContentPartBindingDescriptor> lookup,
|
||||||
PartBindingSettings partBindingSettings,
|
PartBindingSettings partBindingSettings,
|
||||||
string value) {
|
string value) {
|
||||||
|
|
||||||
@ -348,7 +438,7 @@ namespace Orchard.DynamicForms.Services {
|
|||||||
if (field == null)
|
if (field == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var fieldBindingDescriptorsQuery =
|
var fieldBindingDescriptorsQuery =
|
||||||
from partBindingDescriptor in lookup
|
from partBindingDescriptor in lookup
|
||||||
where partBindingDescriptor.Part.PartDefinition.Name == partBindingSettings.Name
|
where partBindingDescriptor.Part.PartDefinition.Name == partBindingSettings.Name
|
||||||
from fieldBindingDescriptor in partBindingDescriptor.FieldBindings
|
from fieldBindingDescriptor in partBindingDescriptor.FieldBindings
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?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" />
|
||||||
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="4.1.0" 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="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net48" />
|
||||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net48" />
|
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net48" />
|
||||||
</packages>
|
</packages>
|
Loading…
Reference in New Issue
Block a user