refactor: make exporting consistent
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user