refactor: make exporting consistent

This commit is contained in:
Gary Sharp
2025-02-06 19:14:36 +11:00
parent f946f3250c
commit 67f1c2a5d1
69 changed files with 908 additions and 921 deletions
+76
View File
@@ -0,0 +1,76 @@
using Disco.Data.Repository;
using Disco.Services.Tasks;
using Quartz;
using System;
using System.Web;
using System.Web.Caching;
namespace Disco.Services.Exporting
{
public class ExportTask : ScheduledTask
{
private IExportContext context;
public override string TaskName { get => context?.Name ?? "Exporting"; }
public override bool SingleInstanceTask { get { return false; } }
public override bool CancelInitiallySupported { get { return false; } }
public static ExportTaskContext ScheduleNow(IExportContext exportContext)
{
// Build Context
var taskContext = new ExportTaskContext(exportContext);
// Build Data Map
var task = new ExportTask();
JobDataMap taskData = new JobDataMap() { { nameof(ExportTask), taskContext } };
// Schedule Task
taskContext.TaskStatus = task.ScheduleTask(taskData);
return taskContext;
}
private static string GetCacheKey(Guid exportId) => $"ExportTask_{exportId}";
public static ExportTaskContext ScheduleNowCacheResult(IExportContext exportContext, Func<Guid, string> returnUrlBuilder)
{
var taskContext = ScheduleNow(exportContext);
var key = GetCacheKey(taskContext.Id);
HttpRuntime.Cache.Insert(key, taskContext, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null);
taskContext.TaskStatus.SetFinishedUrl(returnUrlBuilder(taskContext.Id));
return taskContext;
}
public static bool TryFromCache(Guid? exportId, out ExportTaskContext exportContext)
{
if (exportId != null)
{
var key = GetCacheKey(exportId.Value);
if (HttpRuntime.Cache.Get(key) is ExportTaskContext context)
{
exportContext = context;
return true;
}
}
exportContext = null;
return false;
}
protected override void ExecuteTask()
{
var context = (ExportTaskContext)ExecutionContext.JobDetail.JobDataMap[nameof(ExportTask)];
this.context = context.ExportContext;
Status.UpdateStatus(0, "Exporting", "Starting...");
using (var database = new DiscoDataContext())
{
context.Result = context.ExportContext.Export(database, Status);
}
}
}
}
@@ -1,19 +1,20 @@
using Disco.Models.Services.Exporting;
using Disco.Services.Tasks;
using System;
namespace Disco.Services.Exporting
{
public class ExportTaskContext<T> where T : IExportOptions
public class ExportTaskContext
{
public T Options { get; private set; }
public IExportContext ExportContext { get; }
public ScheduledTaskStatus TaskStatus { get; internal set; }
public ExportResult Result { get; internal set; }
public ScheduledTaskStatus TaskStatus { get; set; }
public Guid Id => ExportContext.Id;
public ExportResult Result { get; set; }
public ExportTaskContext(T Options)
public ExportTaskContext(IExportContext context)
{
this.Options = Options;
ExportContext = context;
}
}
}
@@ -1,4 +1,5 @@
using ClosedXML.Excel;
using Disco.Data.Repository;
using Disco.Models.Exporting;
using Disco.Models.Services.Exporting;
using Disco.Services.Tasks;
@@ -9,43 +10,66 @@ using System.IO;
using System.Linq;
using System.Text;
namespace Disco.Services
namespace Disco.Services.Exporting
{
internal class ExportHelpers
public static class Exporter
{
public static ExportResult WriteExport<T>(IExportOptions options, IScheduledTaskStatus status, List<ExportFieldMetadata<T>> metadata, List<T> records) where T : IExportRecord
public static ExportResult Export<T, R>(DiscoDataContext database, IExportContext<T, R> context, IScheduledTaskStatus status)
where T : IExportOptions, new()
where R : IExportRecord
{
var filenameWithoutExtension = $"{options.FilenamePrefix}-{status.StartedTimestamp.Value:yyyyMMdd-HHmmss}";
MemoryStream stream;
string filename;
string mimeType;
switch (options.Format)
status.UpdateStatus(1, $"Exporting {context.Name}", "Gathering data");
var records = context.BuildRecords(database, status);
status.UpdateStatus(70, "Building metadata");
var metadata = context.BuildMetadata(database, records, status);
if (metadata.Count == 0)
throw new ArgumentException("At least one export field must be specified", nameof(context.Options));
var filenameBuilder = new StringBuilder();
filenameBuilder.Append(context.SuggestedFilenamePrefix);
if (context.TimestampSuffix)
{
filenameBuilder.Append('-');
filenameBuilder.Append(status.StartedTimestamp.Value.ToString("yyyyMMdd-HHmmss"));
}
status.UpdateStatus(80, $"Rendering {records.Count} records for export");
switch (context.Options.Format)
{
case ExportFormat.Csv:
stream = WriteCSV(filenameWithoutExtension, metadata, records, out filename, out mimeType);
filenameBuilder.Append(".csv");
mimeType = "text/csv";
stream = WriteCSV(metadata, records);
break;
case ExportFormat.Xlsx:
stream = WriteXlsx(filenameWithoutExtension, options.ExcelWorksheetName, options.ExcelTableName, metadata, records, out filename, out mimeType);
filenameBuilder.Append(".xlsx");
mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
stream = WriteXlsx(context.ExcelWorksheetName, context.ExcelTableName, metadata, records);
break;
default:
throw new NotSupportedException($"Unsupported export format: {options.Format}");
throw new NotSupportedException($"Unsupported export format: {context.Options.Format}");
}
return new ExportResult()
{
Result = stream,
RecordCount = records.Count,
Filename = filename,
Filename = filenameBuilder.ToString(),
MimeType = mimeType,
};
}
private static MemoryStream WriteCSV<T>(string filenameWithoutExtension, List<ExportFieldMetadata<T>> metadata, List<T> records, out string filename, out string mimeType) where T : IExportRecord
private static MemoryStream WriteCSV<T>(List<ExportFieldMetadata<T>> metadata, List<T> records) where T : IExportRecord
{
var stream = new MemoryStream();
mimeType = "text/csv";
filename = $"{filenameWithoutExtension}.csv";
using (StreamWriter writer = new StreamWriter(stream, Encoding.Default, 0x400, true))
{
@@ -74,11 +98,9 @@ namespace Disco.Services
return stream;
}
private static MemoryStream WriteXlsx<T>(string filenameWithoutExtension, string worksheetName, string tableName, List<ExportFieldMetadata<T>> metadata, List<T> records, out string filename, out string mimeType) where T : IExportRecord
private static MemoryStream WriteXlsx<T>(string worksheetName, string tableName, List<ExportFieldMetadata<T>> metadata, List<T> records) where T : IExportRecord
{
var stream = new MemoryStream();
mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
filename = $"{filenameWithoutExtension}.xlsx";
// Create DataTable
var dataTable = new DataTable();
@@ -0,0 +1,39 @@
using Disco.Data.Repository;
using Disco.Models.Exporting;
using Disco.Models.Services.Exporting;
using Disco.Services.Tasks;
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Disco.Services.Exporting
{
public interface IExportContext
{
Guid Id { get; set; }
string Name { get; set; }
string Description { get; set; }
ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status);
}
public interface IExportContext<T, R>
: IExportContext
where T : IExportOptions, new()
where R : IExportRecord
{
bool TimestampSuffix { get; set; }
[JsonIgnore]
string SuggestedFilenamePrefix { get; }
[JsonIgnore]
string ExcelWorksheetName { get; }
[JsonIgnore]
string ExcelTableName { get; }
T Options { get; set; }
List<R> BuildRecords(DiscoDataContext database, IScheduledTaskStatus status);
List<ExportFieldMetadata<R>> BuildMetadata(DiscoDataContext database, List<R> records, IScheduledTaskStatus status);
}
}