From 1c3f4071ed3ea4d9d5382a27e06d46523d8168a4 Mon Sep 17 00:00:00 2001 From: Oleg Zhuk Date: Fri, 19 Jun 2026 22:52:26 +0200 Subject: [PATCH 1/2] =?UTF-8?q?VCST-5163:=20Stable=2015=20=E2=80=94=20Expo?= =?UTF-8?q?rt=20(platform=203.1039.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump VirtoCommerce.Platform.* to 3.1039.0 and Wave-1 deps to released versions; module version -> 3.1002.0. - Built green via vc-build Compress against nuget.org. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Services/IDataExporter.cs | 31 ++- .../VirtoCommerce.ExportModule.Core.csproj | 2 +- .../Services/DataExporter.cs | 263 +++++++++--------- .../VirtoCommerce.ExportModule.Data.csproj | 2 +- .../BackgroundJobs/ExportJob.cs | 2 +- .../Controllers/Api/ExportController.cs | 4 + .../VirtoCommerce.ExportModule.Web.csproj | 4 +- .../module.manifest | 4 +- 8 files changed, 159 insertions(+), 153 deletions(-) diff --git a/src/VirtoCommerce.ExportModule.Core/Services/IDataExporter.cs b/src/VirtoCommerce.ExportModule.Core/Services/IDataExporter.cs index b1718fa..da953ef 100644 --- a/src/VirtoCommerce.ExportModule.Core/Services/IDataExporter.cs +++ b/src/VirtoCommerce.ExportModule.Core/Services/IDataExporter.cs @@ -1,15 +1,16 @@ -using System; -using System.IO; -using VirtoCommerce.ExportModule.Core.Model; -using VirtoCommerce.Platform.Core.Common; - -namespace VirtoCommerce.ExportModule.Core.Services -{ - /// - /// Interface for data exporter implementation. - /// - public interface IDataExporter - { - void Export(Stream stream, ExportDataRequest request, Action progressCallback, ICancellationToken token); - } -} +using System; +using System.Threading; +using System.IO; +using VirtoCommerce.ExportModule.Core.Model; +using VirtoCommerce.Platform.Core.Common; + +namespace VirtoCommerce.ExportModule.Core.Services +{ + /// + /// Interface for data exporter implementation. + /// + public interface IDataExporter + { + void Export(Stream stream, ExportDataRequest request, Action progressCallback, CancellationToken token); + } +} diff --git a/src/VirtoCommerce.ExportModule.Core/VirtoCommerce.ExportModule.Core.csproj b/src/VirtoCommerce.ExportModule.Core/VirtoCommerce.ExportModule.Core.csproj index 7b263d5..0bfdc97 100644 --- a/src/VirtoCommerce.ExportModule.Core/VirtoCommerce.ExportModule.Core.csproj +++ b/src/VirtoCommerce.ExportModule.Core/VirtoCommerce.ExportModule.Core.csproj @@ -14,6 +14,6 @@ - + \ No newline at end of file diff --git a/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs b/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs index f7f5f58..cd30aaf 100644 --- a/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs +++ b/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs @@ -1,131 +1,132 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using VirtoCommerce.ExportModule.Core.Model; -using VirtoCommerce.ExportModule.Core.Services; -using VirtoCommerce.ExportModule.Data.Extensions; -using VirtoCommerce.Platform.Core.Common; - -namespace VirtoCommerce.ExportModule.Data.Services -{ - public class DataExporter : IDataExporter - { - private readonly IKnownExportTypesResolver _exportTypesResolver; - private readonly IExportProviderFactory _exportProviderFactory; - - public DataExporter(IKnownExportTypesResolver exportTypesResolver, IExportProviderFactory exportProviderFactory) - { - _exportTypesResolver = exportTypesResolver; - _exportProviderFactory = exportProviderFactory; - } - - public void Export(Stream stream, ExportDataRequest request, Action progressCallback, ICancellationToken token) - { - if (request == null) - { - throw new ArgumentNullException(nameof(request)); - } - - token.ThrowIfCancellationRequested(); - - var exportedTypeDefinition = _exportTypesResolver.ResolveExportedTypeDefinition(request.ExportTypeName); - var pagedDataSource = (exportedTypeDefinition.DataSourceFactory ?? throw new ArgumentNullException(nameof(ExportedTypeDefinition.DataSourceFactory))).Create(request.DataQuery); - - var completedMessage = "Export completed"; - var totalCount = pagedDataSource.GetTotalCount(); - var exportedCount = 0; - var exportProgress = new ExportProgressInfo - { - ProcessedCount = 0, - TotalCount = totalCount, - Description = "Export has started", - }; - progressCallback(exportProgress); - - try - { - exportProgress.Description = "Creating provider…"; - progressCallback(exportProgress); - - using var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true) { AutoFlush = true }; - using var exportProvider = _exportProviderFactory.CreateProvider(request); - - var needTabularData = exportProvider.IsTabular; - if (needTabularData && !exportedTypeDefinition.IsTabularExportSupported) - { - throw new NotSupportedException($"Provider \"{exportProvider.TypeName}\" does not support tabular export."); - } - - exportProgress.Description = "Fetching…"; - progressCallback(exportProgress); - - while (pagedDataSource.Fetch()) - { - token.ThrowIfCancellationRequested(); - foreach (var obj in pagedDataSource.Items) - { - try - { - var preparedObject = obj.CloneTyped(); - - if (preparedObject is IEnumerable enumerable) - { - foreach (var exportable in enumerable) - { - WriteRecord(exportProvider, writer, request, exportable, needTabularData); - } - } - else - { - WriteRecord(exportProvider, writer, request, preparedObject, needTabularData); - } - } - catch (Exception e) - { - exportProgress.Errors.Add(e.Message); - progressCallback(exportProgress); - } - exportedCount++; - } - - exportProgress.ProcessedCount = exportedCount; - - if (exportedCount != totalCount) - { - exportProgress.Description = $"{exportedCount} out of {totalCount} have been exported."; - progressCallback(exportProgress); - } - } - } - catch (Exception e) - { - exportProgress.Errors.Add(e.Message); - } - finally - { - if (exportProgress.Errors.Count > 0) - { - completedMessage = "Export completed with errors"; - } - - exportProgress.Description = $"{completedMessage}: {exportedCount} out of {totalCount} have been exported."; - progressCallback(exportProgress); - } - } - - - private static void WriteRecord(IExportProvider exportProvider, TextWriter writer, ExportDataRequest request, IExportable exportable, bool needTabularData) - { - if (needTabularData) - { - var tabular = exportable as ITabularConvertible ?? - throw new NotSupportedException($"Object should be {nameof(ITabularConvertible)} to be exported using tabular provider."); - exportable = tabular.ToTabular(); - } - - request.DataQuery.FilterProperties(exportable); - exportProvider.WriteRecord(writer, exportable); - } - } -} +using System; +using System.Threading; +using System.Collections.Generic; +using System.IO; +using System.Text; +using VirtoCommerce.ExportModule.Core.Model; +using VirtoCommerce.ExportModule.Core.Services; +using VirtoCommerce.ExportModule.Data.Extensions; +using VirtoCommerce.Platform.Core.Common; + +namespace VirtoCommerce.ExportModule.Data.Services +{ + public class DataExporter : IDataExporter + { + private readonly IKnownExportTypesResolver _exportTypesResolver; + private readonly IExportProviderFactory _exportProviderFactory; + + public DataExporter(IKnownExportTypesResolver exportTypesResolver, IExportProviderFactory exportProviderFactory) + { + _exportTypesResolver = exportTypesResolver; + _exportProviderFactory = exportProviderFactory; + } + + public void Export(Stream stream, ExportDataRequest request, Action progressCallback, CancellationToken token) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + token.ThrowIfCancellationRequested(); + + var exportedTypeDefinition = _exportTypesResolver.ResolveExportedTypeDefinition(request.ExportTypeName); + var pagedDataSource = (exportedTypeDefinition.DataSourceFactory ?? throw new ArgumentNullException(nameof(ExportedTypeDefinition.DataSourceFactory))).Create(request.DataQuery); + + var completedMessage = "Export completed"; + var totalCount = pagedDataSource.GetTotalCount(); + var exportedCount = 0; + var exportProgress = new ExportProgressInfo + { + ProcessedCount = 0, + TotalCount = totalCount, + Description = "Export has started", + }; + progressCallback(exportProgress); + + try + { + exportProgress.Description = "Creating provider…"; + progressCallback(exportProgress); + + using var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true) { AutoFlush = true }; + using var exportProvider = _exportProviderFactory.CreateProvider(request); + + var needTabularData = exportProvider.IsTabular; + if (needTabularData && !exportedTypeDefinition.IsTabularExportSupported) + { + throw new NotSupportedException($"Provider \"{exportProvider.TypeName}\" does not support tabular export."); + } + + exportProgress.Description = "Fetching…"; + progressCallback(exportProgress); + + while (pagedDataSource.Fetch()) + { + token.ThrowIfCancellationRequested(); + foreach (var obj in pagedDataSource.Items) + { + try + { + var preparedObject = obj.CloneTyped(); + + if (preparedObject is IEnumerable enumerable) + { + foreach (var exportable in enumerable) + { + WriteRecord(exportProvider, writer, request, exportable, needTabularData); + } + } + else + { + WriteRecord(exportProvider, writer, request, preparedObject, needTabularData); + } + } + catch (Exception e) + { + exportProgress.Errors.Add(e.Message); + progressCallback(exportProgress); + } + exportedCount++; + } + + exportProgress.ProcessedCount = exportedCount; + + if (exportedCount != totalCount) + { + exportProgress.Description = $"{exportedCount} out of {totalCount} have been exported."; + progressCallback(exportProgress); + } + } + } + catch (Exception e) + { + exportProgress.Errors.Add(e.Message); + } + finally + { + if (exportProgress.Errors.Count > 0) + { + completedMessage = "Export completed with errors"; + } + + exportProgress.Description = $"{completedMessage}: {exportedCount} out of {totalCount} have been exported."; + progressCallback(exportProgress); + } + } + + + private static void WriteRecord(IExportProvider exportProvider, TextWriter writer, ExportDataRequest request, IExportable exportable, bool needTabularData) + { + if (needTabularData) + { + var tabular = exportable as ITabularConvertible ?? + throw new NotSupportedException($"Object should be {nameof(ITabularConvertible)} to be exported using tabular provider."); + exportable = tabular.ToTabular(); + } + + request.DataQuery.FilterProperties(exportable); + exportProvider.WriteRecord(writer, exportable); + } + } +} diff --git a/src/VirtoCommerce.ExportModule.Data/VirtoCommerce.ExportModule.Data.csproj b/src/VirtoCommerce.ExportModule.Data/VirtoCommerce.ExportModule.Data.csproj index 346ea8b..4782d6d 100644 --- a/src/VirtoCommerce.ExportModule.Data/VirtoCommerce.ExportModule.Data.csproj +++ b/src/VirtoCommerce.ExportModule.Data/VirtoCommerce.ExportModule.Data.csproj @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/src/VirtoCommerce.ExportModule.Web/BackgroundJobs/ExportJob.cs b/src/VirtoCommerce.ExportModule.Web/BackgroundJobs/ExportJob.cs index 4878f3a..990e61a 100644 --- a/src/VirtoCommerce.ExportModule.Web/BackgroundJobs/ExportJob.cs +++ b/src/VirtoCommerce.ExportModule.Web/BackgroundJobs/ExportJob.cs @@ -48,7 +48,7 @@ void ProgressCallback(ExportProgressInfo x) await using (var stream = await _exportFileStorage.OpenWriteAsync(fileName)) { - _dataExporter.Export(stream, request, ProgressCallback, new JobCancellationTokenWrapper(cancellationToken)); + _dataExporter.Export(stream, request, ProgressCallback, cancellationToken.ShutdownToken); } notification.DownloadUrl = $"/api/export/download/{fileName}"; diff --git a/src/VirtoCommerce.ExportModule.Web/Controllers/Api/ExportController.cs b/src/VirtoCommerce.ExportModule.Web/Controllers/Api/ExportController.cs index b1e5fda..2435209 100644 --- a/src/VirtoCommerce.ExportModule.Web/Controllers/Api/ExportController.cs +++ b/src/VirtoCommerce.ExportModule.Web/Controllers/Api/ExportController.cs @@ -158,7 +158,11 @@ public ActionResult CancelExport([FromBody] ExportCancellationRequest cancellati /// [HttpGet] [Route("download/{fileName}")] + // VC0015: PlatformExport permission was moved to the BackupRestore module but the platform keeps + // the constant for backward compatibility; keep using it here to avoid an Export->BackupRestore dependency. +#pragma warning disable VC0015 [AuthorizeAny(PlatformConstants.Security.Permissions.PlatformExport, ModuleConstants.Security.Permissions.Download)] +#pragma warning restore VC0015 public async Task DownloadExportFile([FromRoute] string fileName) { var contentType = MimeTypeResolver.ResolveContentType(fileName); diff --git a/src/VirtoCommerce.ExportModule.Web/VirtoCommerce.ExportModule.Web.csproj b/src/VirtoCommerce.ExportModule.Web/VirtoCommerce.ExportModule.Web.csproj index 010252e..b6e0b26 100644 --- a/src/VirtoCommerce.ExportModule.Web/VirtoCommerce.ExportModule.Web.csproj +++ b/src/VirtoCommerce.ExportModule.Web/VirtoCommerce.ExportModule.Web.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/src/VirtoCommerce.ExportModule.Web/module.manifest b/src/VirtoCommerce.ExportModule.Web/module.manifest index 2643a1f..a84def8 100644 --- a/src/VirtoCommerce.ExportModule.Web/module.manifest +++ b/src/VirtoCommerce.ExportModule.Web/module.manifest @@ -4,9 +4,9 @@ 3.1002.0 - 3.1002.0 + 3.1039.0 - + Generic Export From 53e60cb10248f074878f0ae94785050812dda164 Mon Sep 17 00:00:00 2001 From: Oleg Zhuk Date: Fri, 19 Jun 2026 23:10:34 +0200 Subject: [PATCH 2/2] VCST-5163: reduce DataExporter.Export complexity (SonarCloud S3776/S1541) Extract the per-object export block (clone + tabular/enumerable handling + error capture) into a private ExportObject method, lowering Export's cognitive complexity (21->under 15) and cyclomatic complexity (11->under 10). Behavior unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Services/DataExporter.cs | 267 +++++++++--------- 1 file changed, 136 insertions(+), 131 deletions(-) diff --git a/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs b/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs index cd30aaf..19df0bd 100644 --- a/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs +++ b/src/VirtoCommerce.ExportModule.Data/Services/DataExporter.cs @@ -1,132 +1,137 @@ -using System; +using System; using System.Threading; -using System.Collections.Generic; -using System.IO; -using System.Text; -using VirtoCommerce.ExportModule.Core.Model; -using VirtoCommerce.ExportModule.Core.Services; -using VirtoCommerce.ExportModule.Data.Extensions; -using VirtoCommerce.Platform.Core.Common; - -namespace VirtoCommerce.ExportModule.Data.Services -{ - public class DataExporter : IDataExporter - { - private readonly IKnownExportTypesResolver _exportTypesResolver; - private readonly IExportProviderFactory _exportProviderFactory; - - public DataExporter(IKnownExportTypesResolver exportTypesResolver, IExportProviderFactory exportProviderFactory) - { - _exportTypesResolver = exportTypesResolver; - _exportProviderFactory = exportProviderFactory; - } - - public void Export(Stream stream, ExportDataRequest request, Action progressCallback, CancellationToken token) - { - if (request == null) - { - throw new ArgumentNullException(nameof(request)); - } - - token.ThrowIfCancellationRequested(); - - var exportedTypeDefinition = _exportTypesResolver.ResolveExportedTypeDefinition(request.ExportTypeName); - var pagedDataSource = (exportedTypeDefinition.DataSourceFactory ?? throw new ArgumentNullException(nameof(ExportedTypeDefinition.DataSourceFactory))).Create(request.DataQuery); - - var completedMessage = "Export completed"; - var totalCount = pagedDataSource.GetTotalCount(); - var exportedCount = 0; - var exportProgress = new ExportProgressInfo - { - ProcessedCount = 0, - TotalCount = totalCount, - Description = "Export has started", - }; - progressCallback(exportProgress); - - try - { - exportProgress.Description = "Creating provider…"; - progressCallback(exportProgress); - - using var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true) { AutoFlush = true }; - using var exportProvider = _exportProviderFactory.CreateProvider(request); - - var needTabularData = exportProvider.IsTabular; - if (needTabularData && !exportedTypeDefinition.IsTabularExportSupported) - { - throw new NotSupportedException($"Provider \"{exportProvider.TypeName}\" does not support tabular export."); - } - - exportProgress.Description = "Fetching…"; - progressCallback(exportProgress); - - while (pagedDataSource.Fetch()) - { - token.ThrowIfCancellationRequested(); - foreach (var obj in pagedDataSource.Items) - { - try - { - var preparedObject = obj.CloneTyped(); - - if (preparedObject is IEnumerable enumerable) - { - foreach (var exportable in enumerable) - { - WriteRecord(exportProvider, writer, request, exportable, needTabularData); - } - } - else - { - WriteRecord(exportProvider, writer, request, preparedObject, needTabularData); - } - } - catch (Exception e) - { - exportProgress.Errors.Add(e.Message); - progressCallback(exportProgress); - } - exportedCount++; - } - - exportProgress.ProcessedCount = exportedCount; - - if (exportedCount != totalCount) - { - exportProgress.Description = $"{exportedCount} out of {totalCount} have been exported."; - progressCallback(exportProgress); - } - } - } - catch (Exception e) - { - exportProgress.Errors.Add(e.Message); - } - finally - { - if (exportProgress.Errors.Count > 0) - { - completedMessage = "Export completed with errors"; - } - - exportProgress.Description = $"{completedMessage}: {exportedCount} out of {totalCount} have been exported."; - progressCallback(exportProgress); - } - } - - - private static void WriteRecord(IExportProvider exportProvider, TextWriter writer, ExportDataRequest request, IExportable exportable, bool needTabularData) - { - if (needTabularData) - { - var tabular = exportable as ITabularConvertible ?? - throw new NotSupportedException($"Object should be {nameof(ITabularConvertible)} to be exported using tabular provider."); - exportable = tabular.ToTabular(); - } - - request.DataQuery.FilterProperties(exportable); - exportProvider.WriteRecord(writer, exportable); - } - } -} +using System.Collections.Generic; +using System.IO; +using System.Text; +using VirtoCommerce.ExportModule.Core.Model; +using VirtoCommerce.ExportModule.Core.Services; +using VirtoCommerce.ExportModule.Data.Extensions; +using VirtoCommerce.Platform.Core.Common; + +namespace VirtoCommerce.ExportModule.Data.Services +{ + public class DataExporter : IDataExporter + { + private readonly IKnownExportTypesResolver _exportTypesResolver; + private readonly IExportProviderFactory _exportProviderFactory; + + public DataExporter(IKnownExportTypesResolver exportTypesResolver, IExportProviderFactory exportProviderFactory) + { + _exportTypesResolver = exportTypesResolver; + _exportProviderFactory = exportProviderFactory; + } + + public void Export(Stream stream, ExportDataRequest request, Action progressCallback, CancellationToken token) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + token.ThrowIfCancellationRequested(); + + var exportedTypeDefinition = _exportTypesResolver.ResolveExportedTypeDefinition(request.ExportTypeName); + var pagedDataSource = (exportedTypeDefinition.DataSourceFactory ?? throw new ArgumentNullException(nameof(ExportedTypeDefinition.DataSourceFactory))).Create(request.DataQuery); + + var completedMessage = "Export completed"; + var totalCount = pagedDataSource.GetTotalCount(); + var exportedCount = 0; + var exportProgress = new ExportProgressInfo + { + ProcessedCount = 0, + TotalCount = totalCount, + Description = "Export has started", + }; + progressCallback(exportProgress); + + try + { + exportProgress.Description = "Creating provider…"; + progressCallback(exportProgress); + + using var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true) { AutoFlush = true }; + using var exportProvider = _exportProviderFactory.CreateProvider(request); + + var needTabularData = exportProvider.IsTabular; + if (needTabularData && !exportedTypeDefinition.IsTabularExportSupported) + { + throw new NotSupportedException($"Provider \"{exportProvider.TypeName}\" does not support tabular export."); + } + + exportProgress.Description = "Fetching…"; + progressCallback(exportProgress); + + while (pagedDataSource.Fetch()) + { + token.ThrowIfCancellationRequested(); + foreach (var obj in pagedDataSource.Items) + { + ExportObject(exportProvider, writer, request, obj, needTabularData, exportProgress, progressCallback); + exportedCount++; + } + + exportProgress.ProcessedCount = exportedCount; + + if (exportedCount != totalCount) + { + exportProgress.Description = $"{exportedCount} out of {totalCount} have been exported."; + progressCallback(exportProgress); + } + } + } + catch (Exception e) + { + exportProgress.Errors.Add(e.Message); + } + finally + { + if (exportProgress.Errors.Count > 0) + { + completedMessage = "Export completed with errors"; + } + + exportProgress.Description = $"{completedMessage}: {exportedCount} out of {totalCount} have been exported."; + progressCallback(exportProgress); + } + } + + + private static void ExportObject(IExportProvider exportProvider, TextWriter writer, ExportDataRequest request, IExportable obj, bool needTabularData, ExportProgressInfo exportProgress, Action progressCallback) + { + try + { + var preparedObject = obj.CloneTyped(); + + if (preparedObject is IEnumerable enumerable) + { + foreach (var exportable in enumerable) + { + WriteRecord(exportProvider, writer, request, exportable, needTabularData); + } + } + else + { + WriteRecord(exportProvider, writer, request, preparedObject, needTabularData); + } + } + catch (Exception e) + { + exportProgress.Errors.Add(e.Message); + progressCallback(exportProgress); + } + } + + private static void WriteRecord(IExportProvider exportProvider, TextWriter writer, ExportDataRequest request, IExportable exportable, bool needTabularData) + { + if (needTabularData) + { + var tabular = exportable as ITabularConvertible ?? + throw new NotSupportedException($"Object should be {nameof(ITabularConvertible)} to be exported using tabular provider."); + exportable = tabular.ToTabular(); + } + + request.DataQuery.FilterProperties(exportable); + exportProvider.WriteRecord(writer, exportable); + } + } +}