From d903cd933fc3b3c5ad28145325eb1bf665fde5df Mon Sep 17 00:00:00 2001 From: Vitalii Mikhailov Date: Fri, 16 Feb 2024 15:26:05 +0200 Subject: [PATCH] Updated backend Added csv export --- ...NexusMods.Server.ValueObjects.Vogen.csproj | 2 +- .../BUTR.Site.NexusMods.Server.csproj | 35 +++++----- .../Csv/Extensions/MvcOptionsExtensions.cs | 21 ++++++ .../Utils/Csv/ICsvFile.cs | 6 ++ .../Utils/Csv/Utils/ExportOutputFormatter.cs | 65 +++++++++++++++++++ .../BUTR.Site.NexusMods.Shared.csproj | 2 +- 6 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 src/BUTR.Site.NexusMods.Server/Utils/Csv/Extensions/MvcOptionsExtensions.cs create mode 100644 src/BUTR.Site.NexusMods.Server/Utils/Csv/ICsvFile.cs create mode 100644 src/BUTR.Site.NexusMods.Server/Utils/Csv/Utils/ExportOutputFormatter.cs diff --git a/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj b/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj index 5d62cdc1..a7d4faec 100644 --- a/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj +++ b/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj b/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj index 1b3c5bc2..a058f896 100644 --- a/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj +++ b/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj @@ -19,42 +19,43 @@ - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + - - - - - - - - - + + + + + + + + + - + - + diff --git a/src/BUTR.Site.NexusMods.Server/Utils/Csv/Extensions/MvcOptionsExtensions.cs b/src/BUTR.Site.NexusMods.Server/Utils/Csv/Extensions/MvcOptionsExtensions.cs new file mode 100644 index 00000000..5b6fd63c --- /dev/null +++ b/src/BUTR.Site.NexusMods.Server/Utils/Csv/Extensions/MvcOptionsExtensions.cs @@ -0,0 +1,21 @@ +using BUTR.Site.NexusMods.Server.Utils.Csv.Utils; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace BUTR.Site.NexusMods.Server.Utils.Csv.Extensions; + +[ExcludeFromCodeCoverage] +public static class MvcOptionsExtensions +{ + public static void AddCsvOutputFormatters(this MvcOptions options) + { + var types = typeof(Program).Assembly.GetTypes().Where(t => t.IsAssignableTo(typeof(ICsvFile)) && !t.IsAbstract); + foreach (var type in types) + options.OutputFormatters.Insert(0, (IOutputFormatter) Activator.CreateInstance(typeof(ExportOutputFormatter<>).MakeGenericType(type))!); + } +} \ No newline at end of file diff --git a/src/BUTR.Site.NexusMods.Server/Utils/Csv/ICsvFile.cs b/src/BUTR.Site.NexusMods.Server/Utils/Csv/ICsvFile.cs new file mode 100644 index 00000000..b212e2fd --- /dev/null +++ b/src/BUTR.Site.NexusMods.Server/Utils/Csv/ICsvFile.cs @@ -0,0 +1,6 @@ +namespace BUTR.Site.NexusMods.Server.Utils.Csv; + +public interface ICsvFile +{ + static abstract string GenerateFilename(); +} \ No newline at end of file diff --git a/src/BUTR.Site.NexusMods.Server/Utils/Csv/Utils/ExportOutputFormatter.cs b/src/BUTR.Site.NexusMods.Server/Utils/Csv/Utils/ExportOutputFormatter.cs new file mode 100644 index 00000000..8a1f7822 --- /dev/null +++ b/src/BUTR.Site.NexusMods.Server/Utils/Csv/Utils/ExportOutputFormatter.cs @@ -0,0 +1,65 @@ +using CsvHelper; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Net.Http.Headers; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace BUTR.Site.NexusMods.Server.Utils.Csv.Utils; + +public class ExportOutputFormatter : TextOutputFormatter where TCsvFile: ICsvFile +{ + private static string ContentType => "text/csv"; + + public ExportOutputFormatter() + { + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ContentType)); + + SupportedEncodings.Add(Encoding.UTF8); + SupportedEncodings.Add(Encoding.Unicode); + } + + protected override bool CanWriteType(Type? type) + { + if (typeof(TCsvFile).IsAssignableFrom(type) || typeof(IEnumerable).IsAssignableFrom(type)) + return base.CanWriteType(type); + return false; + } + + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + var (data, type) = context.Object switch + { + IEnumerable enumerable => (enumerable, enumerable.GetType().GetGenericArguments()[0]), + TCsvFile entry => (new List { entry }, entry.GetType()), + _ => default + }; + if (data is null || type is null) + throw new InvalidOperationException("Invalid csv data type!"); + + await WriteCsvCollection(context.HttpContext, data, selectedEncoding); + } + + private static async Task WriteCsvCollection(HttpContext context, IEnumerable entries, Encoding selectedEncoding) + { + var response = context.Response; + + response.Headers.ContentDisposition = new System.Net.Mime.ContentDisposition + { + FileName = TCsvFile.GenerateFilename(), + Inline = false, + }.ToString(); + + await using var csv = new CsvWriter(new StreamWriter(response.Body, selectedEncoding), CultureInfo.InvariantCulture); + + csv.WriteHeader(); + await csv.NextRecordAsync(); + await csv.WriteRecordsAsync(entries); + } +} \ No newline at end of file diff --git a/src/BUTR.Site.NexusMods.Shared/BUTR.Site.NexusMods.Shared.csproj b/src/BUTR.Site.NexusMods.Shared/BUTR.Site.NexusMods.Shared.csproj index b082cb6e..7700d99b 100644 --- a/src/BUTR.Site.NexusMods.Shared/BUTR.Site.NexusMods.Shared.csproj +++ b/src/BUTR.Site.NexusMods.Shared/BUTR.Site.NexusMods.Shared.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers