diff --git a/AsbCloudApp/Data/ParserResultDto.cs b/AsbCloudApp/Data/ParserResultDto.cs new file mode 100644 index 00000000..5b6eca8d --- /dev/null +++ b/AsbCloudApp/Data/ParserResultDto.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace AsbCloudApp.Data; + +/// +/// Результат парсинга файла +/// +/// +public class ParserResultDto : ValidationResultDto>> + where TDto : class, IId +{ +} \ No newline at end of file diff --git a/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs b/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs index 47563011..e47a66e8 100644 --- a/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs +++ b/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs @@ -5,7 +5,7 @@ namespace AsbCloudApp.Data.Trajectory /// /// Базовая географическая траектория /// - public abstract class TrajectoryGeoDto + public abstract class TrajectoryGeoDto : IId { /// /// ИД строки с координатами diff --git a/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs b/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs new file mode 100644 index 00000000..b3b12f51 --- /dev/null +++ b/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs @@ -0,0 +1,19 @@ +namespace AsbCloudApp.Requests.ParserOptions; + +/// +/// Параметры парсинга +/// +public interface IParserOptionsRequest +{ + private static DummyOptions empty => new(); + + private class DummyOptions : IParserOptionsRequest + { + } + + /// + /// Получение пустого объекта опций + /// + /// + public static IParserOptionsRequest Empty() => empty; +} \ No newline at end of file diff --git a/AsbCloudApp/Services/IParserService.cs b/AsbCloudApp/Services/IParserService.cs new file mode 100644 index 00000000..2cfd8eb7 --- /dev/null +++ b/AsbCloudApp/Services/IParserService.cs @@ -0,0 +1,38 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; + +namespace AsbCloudApp.Services; + +/// +/// Сервис парсинга +/// +/// +/// +public interface IParserService : IParserService + where TDto : class, IId + where TOptions : IParserOptionsRequest +{ + /// + /// Распарсить файл + /// + /// + /// + /// + ParserResultDto Parse(Stream file, TOptions options); + + /// + /// Получение шаблона для заполнения + /// + /// + Stream GetTemplateFile(); +} + +/// +/// Сервис парсинга(интерфейс маркер) +/// +public interface IParserService +{ +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/AssemblyExtensions.cs b/AsbCloudInfrastructure/AssemblyExtensions.cs index 9594261b..03cf7325 100644 --- a/AsbCloudInfrastructure/AssemblyExtensions.cs +++ b/AsbCloudInfrastructure/AssemblyExtensions.cs @@ -1,30 +1,50 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure { - public static class AssemblyExtensions - { - public static async Task GetTemplateCopyStreamAsync(this Assembly assembly, string templateName, CancellationToken cancellationToken) - { - var resourceName = assembly - .GetManifestResourceNames() - .FirstOrDefault(n => n.EndsWith(templateName))!; + public static class AssemblyExtensions + { + public static Stream GetTemplateCopyStream(this Assembly assembly, string templateName) + { + var resourceName = assembly + .GetManifestResourceNames() + .FirstOrDefault(n => n.EndsWith(templateName)); - using var stream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream(resourceName)!; + if (string.IsNullOrWhiteSpace(resourceName)) + throw new ArgumentNullException(nameof(resourceName)); - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, cancellationToken); - memoryStream.Position = 0; + using var stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream(resourceName); - return memoryStream; - } - } -} + var memoryStream = new MemoryStream(); + stream?.CopyTo(memoryStream); + memoryStream.Position = 0; + + return memoryStream; + } + + [Obsolete] + public static async Task GetTemplateCopyStreamAsync(this Assembly assembly, + string templateName, + CancellationToken cancellationToken) + { + var resourceName = assembly + .GetManifestResourceNames() + .FirstOrDefault(n => n.EndsWith(templateName))!; + + using var stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream(resourceName)!; + + var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, cancellationToken); + memoryStream.Position = 0; + + return memoryStream; + } + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 913fdfe4..28a051ab 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -30,7 +30,6 @@ using AsbCloudInfrastructure.Services.SAUB; using AsbCloudInfrastructure.Services.Subsystems; using AsbCloudInfrastructure.Services.Trajectory; using AsbCloudInfrastructure.Services.Trajectory.Export; -using AsbCloudInfrastructure.Services.Trajectory.Import; using AsbCloudInfrastructure.Services.WellOperationImport; using AsbCloudInfrastructure.Services.WellOperationImport.FileParser; using AsbCloudInfrastructure.Services.WellOperationService; @@ -46,6 +45,7 @@ using AsbCloudDb.Model.WellSections; using AsbCloudInfrastructure.Services.ProcessMaps; using AsbCloudApp.Data.ProcessMapPlan; using AsbCloudApp.Requests; +using AsbCloudInfrastructure.Services.Trajectory.Parser; namespace AsbCloudInfrastructure { @@ -328,6 +328,8 @@ namespace AsbCloudInfrastructure services.AddTransient, ProcessMapPlanService>(); services.AddTransient(); + + services.AddSingleton(); return services; } diff --git a/AsbCloudInfrastructure/ParserServiceBase.cs b/AsbCloudInfrastructure/ParserServiceBase.cs new file mode 100644 index 00000000..d98404e7 --- /dev/null +++ b/AsbCloudInfrastructure/ParserServiceBase.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudApp.Services; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure; + +public abstract class ParserServiceBase : IParserService + where TDto : class, IId + where TOptions : IParserOptionsRequest +{ + protected readonly IServiceProvider serviceProvider; + + protected ParserServiceBase(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public abstract ParserResultDto Parse(Stream file, TOptions options); + public abstract Stream GetTemplateFile(); + + protected virtual ParserResultDto ParseExcelSheet(IXLWorksheet sheet, + Func> parseRow, + int columnCount, + int headerRowsCount = 0) + { + if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnCount) + throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); + + var count = sheet.RowsUsed().Count() - headerRowsCount; + + if (count > 1024) + throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк."); + + if (count <= 0) + return new ParserResultDto(); + + var dtos = new List>(count); + var warnings = new List(); + + for (var i = 0; i < count; i++) + { + var row = sheet.Row(1 + i + headerRowsCount); + + try + { + var dto = parseRow.Invoke(row); + dtos.Add(dto); + } + catch (FileFormatException ex) + { + var warning = new ValidationResult(ex.Message); + warnings.Add(warning); + } + } + + var parserResult = new ParserResultDto + { + Item = dtos + }; + + if (warnings.Any()) + parserResult.Warnings = warnings; + + return parserResult; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs new file mode 100644 index 00000000..5b852a8e --- /dev/null +++ b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudApp.Services; +using AsbCloudInfrastructure.Services.Trajectory.Parser; + +namespace AsbCloudInfrastructure.Services; + +public class ParserServiceFactory +{ + public const int IdTrajectoryFactManualParserService = 1; + public const int IdTrajectoryPlanParserService = 2; + + private readonly IDictionary> parsers; + + public ParserServiceFactory(IServiceProvider serviceProvider) + { + parsers = new Dictionary> + { + { IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService(serviceProvider) }, + { IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService(serviceProvider) } + }; + } + + public IParserService Create(int idParserService) + where TDto : class, IId + where TOptions : IParserOptionsRequest + { + if (!parsers.TryGetValue(idParserService, out var parserService)) + throw new ArgumentNullException(nameof(idParserService), "Не правильный идентификатор парсера"); + + return parserService.Invoke() as IParserService + ?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа"); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index ec92c00f..74643429 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -299,7 +299,7 @@ namespace AsbCloudInfrastructure.Services.SAUB if (request.LeDate.HasValue) { var leDate = request.LeDate.Value.ToRemoteDateTime(cacheItem.TimezoneHours); - data = data.Where(d => d.DateTime >= request.LeDate); + data = data.Where(d => d.DateTime <= request.LeDate); } if (request.Divider > 1) diff --git a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryFactManualParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryFactManualParserService.cs deleted file mode 100644 index be08e12e..00000000 --- a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryFactManualParserService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AsbCloudApp.Data.Trajectory; -using ClosedXML.Excel; - -namespace AsbCloudInfrastructure.Services.Trajectory.Import -{ - - public class TrajectoryFactManualParserService : TrajectoryParserService - { - public override string templateFileName { get; } = "TrajectoryFactManualTemplate.xlsx"; - public override string usingTemplateFile { get; } = "AsbCloudInfrastructure.Services.Trajectory.Templates"; - public override string sheetName { get; } = "Фактическая траектория"; - public override int headerRowsCount { get; } = 2; - - protected override TrajectoryGeoFactDto ParseRow(IXLRow row) - { - var trajectoryRow = new TrajectoryGeoFactDto - { - WellboreDepth = row.Cell(1).GetCellValue(), - ZenithAngle = row.Cell(2).GetCellValue(), - AzimuthGeo = row.Cell(3).GetCellValue(), - AzimuthMagnetic = row.Cell(4).GetCellValue(), - VerticalDepth = row.Cell(5).GetCellValue(), - Comment = row.Cell(6).GetCellValue() - }; - //TODO: Добавить валидацию модели IValidatableObject - return trajectoryRow; - } - } -} - diff --git a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryParserService.cs deleted file mode 100644 index c67bc409..00000000 --- a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryParserService.cs +++ /dev/null @@ -1,78 +0,0 @@ -using AsbCloudApp.Data.Trajectory; -using ClosedXML.Excel; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace AsbCloudInfrastructure.Services.Trajectory.Import -{ - public abstract class TrajectoryParserService - where T : TrajectoryGeoDto - { - public abstract string templateFileName { get; } - public abstract string usingTemplateFile { get; } - public abstract string sheetName { get; } - public abstract int headerRowsCount { get; } - - protected abstract T ParseRow(IXLRow row); - - public IEnumerable Import(Stream stream) - { - using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); - var trajectoryRows = ParseFileStream(stream); - - return trajectoryRows; - } - - - private IEnumerable ParseFileStream(Stream stream) - { - using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); - return ParseWorkbook(workbook); - } - - private IEnumerable ParseWorkbook(IXLWorkbook workbook) - { - var sheetTrajectory = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName); - if (sheetTrajectory is null) - throw new FileFormatException($"Книга excel не содержит листа {sheetName}."); - var trajectoryRows = ParseSheet(sheetTrajectory); - return trajectoryRows; - } - - private IEnumerable ParseSheet(IXLWorksheet sheet) - { - if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 6) - throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); - - var count = sheet.RowsUsed().Count() - headerRowsCount; - - if (count > 1024) - throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк."); - - if (count <= 0) - throw new FileFormatException($"Лист {sheet.Name} некорректного формата либо пустой"); - - var trajectoryRows = new List(count); - var parseErrors = new List(); - for (int i = 0; i < count; i++) - { - var row = sheet.Row(1 + i + headerRowsCount); - try - { - var trajectoryRow = ParseRow(row); - trajectoryRows.Add(trajectoryRow); - } - catch (FileFormatException ex) - { - parseErrors.Add(ex.Message); - } - } - - if (parseErrors.Any()) - throw new FileFormatException(string.Join("\r\n", parseErrors)); - - return trajectoryRows; - } - } -} diff --git a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryPlanParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryPlanParserService.cs deleted file mode 100644 index 00f3f197..00000000 --- a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryPlanParserService.cs +++ /dev/null @@ -1,33 +0,0 @@ -using AsbCloudApp.Data.Trajectory; -using ClosedXML.Excel; - -namespace AsbCloudInfrastructure.Services.Trajectory.Import -{ - - public class TrajectoryPlanParserService : TrajectoryParserService - { - public override string templateFileName { get; } = "TrajectoryPlanTemplate.xlsx"; - public override string usingTemplateFile { get; } = "AsbCloudInfrastructure.Services.Trajectory.Templates"; - public override string sheetName { get; } = "Плановая траектория"; - public override int headerRowsCount { get; } = 2; - - protected override TrajectoryGeoPlanDto ParseRow(IXLRow row) - { - var trajectoryRow = new TrajectoryGeoPlanDto - { - WellboreDepth = row.Cell(1).GetCellValue(), - ZenithAngle = row.Cell(2).GetCellValue(), - AzimuthGeo = row.Cell(3).GetCellValue(), - AzimuthMagnetic = row.Cell(4).GetCellValue(), - VerticalDepth = row.Cell(5).GetCellValue(), - Radius = row.Cell(6).GetCellValue(), - Comment = row.Cell(7).GetCellValue() - }; - - //TODO: Добавить валидацию модели IValidatableObject - return trajectoryRow; - } - } - -} - diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs new file mode 100644 index 00000000..6e4034a5 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs @@ -0,0 +1,39 @@ +using System; +using AsbCloudApp.Data; +using AsbCloudApp.Data.Trajectory; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure.Services.Trajectory.Parser; + +public class TrajectoryFactManualParserService : TrajectoryParserService +{ + protected override string SheetName => "Фактическая траектория"; + protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx"; + + public TrajectoryFactManualParserService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + protected override ValidationResultDto ParseRow(IXLRow row) + { + var trajectoryRow = new TrajectoryGeoFactDto + { + WellboreDepth = row.Cell(1).GetCellValue(), + ZenithAngle = row.Cell(2).GetCellValue(), + AzimuthGeo = row.Cell(3).GetCellValue(), + AzimuthMagnetic = row.Cell(4).GetCellValue(), + VerticalDepth = row.Cell(5).GetCellValue(), + Comment = row.Cell(6).GetCellValue() + }; + + //TODO: Добавить валидацию модели + + var validationResult = new ValidationResultDto + { + Item = trajectoryRow + }; + + return validationResult; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs new file mode 100644 index 00000000..d0afe513 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs @@ -0,0 +1,44 @@ +using System; +using AsbCloudApp.Data.Trajectory; +using ClosedXML.Excel; +using System.IO; +using System.Linq; +using System.Reflection; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; + +namespace AsbCloudInfrastructure.Services.Trajectory.Parser; + +public abstract class TrajectoryParserService : ParserServiceBase + where T : TrajectoryGeoDto +{ + private const int HeaderRowsCount = 2; + private const int ColumnCount = 6; + + protected TrajectoryParserService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + protected abstract string SheetName { get; } + + protected abstract string TemplateFileName { get; } + + protected abstract ValidationResultDto ParseRow(IXLRow row); + + public override Stream GetTemplateFile() => + Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateFileName) + ?? throw new ArgumentNullException($"Файл '{TemplateFileName}' не найден"); + + public override ParserResultDto Parse(Stream file, IParserOptionsRequest options) + { + using var workbook = new XLWorkbook(file, XLEventTracking.Disabled); + + var sheet = workbook.Worksheets.FirstOrDefault(ws => + ws.Name.ToLower().Trim() == SheetName.ToLower().Trim()) + ?? throw new FileFormatException($"Книга excel не содержит листа {SheetName}."); + + var trajectoryRows = ParseExcelSheet(sheet, ParseRow, ColumnCount, HeaderRowsCount); + return trajectoryRows; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs new file mode 100644 index 00000000..fbf5a537 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs @@ -0,0 +1,40 @@ +using System; +using AsbCloudApp.Data; +using AsbCloudApp.Data.Trajectory; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure.Services.Trajectory.Parser; + +public class TrajectoryPlanParserService : TrajectoryParserService +{ + protected override string SheetName => "Плановая траектория"; + protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx"; + + public TrajectoryPlanParserService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + protected override ValidationResultDto ParseRow(IXLRow row) + { + var trajectoryRow = new TrajectoryGeoPlanDto + { + WellboreDepth = row.Cell(1).GetCellValue(), + ZenithAngle = row.Cell(2).GetCellValue(), + AzimuthGeo = row.Cell(3).GetCellValue(), + AzimuthMagnetic = row.Cell(4).GetCellValue(), + VerticalDepth = row.Cell(5).GetCellValue(), + Radius = row.Cell(6).GetCellValue(), + Comment = row.Cell(7).GetCellValue() + }; + + //TODO: Добавить валидацию модели + + var validationResult = new ValidationResultDto + { + Item = trajectoryRow + }; + + return validationResult; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/XLExtentions.cs b/AsbCloudInfrastructure/XLExtentions.cs index 3a583bf9..e633a875 100644 --- a/AsbCloudInfrastructure/XLExtentions.cs +++ b/AsbCloudInfrastructure/XLExtentions.cs @@ -2,96 +2,11 @@ using System; using System.Globalization; using System.IO; -using AsbCloudInfrastructure.Services.DailyReport; namespace AsbCloudInfrastructure; internal static class XLExtentions { - internal static IXLRange _SetValue(this IXLRange range, object value) - { - var mergedRange = range.Merge(); - mergedRange.FirstCell()._SetValue(value); - var colWidth = mergedRange.FirstCell().WorksheetColumn().Width; - var maxCharsToWrap = colWidth / (0.1d * mergedRange.FirstCell().Style.Font.FontSize); - if (value is string valueString && valueString.Length > maxCharsToWrap) - { - var row = mergedRange.FirstCell().WorksheetRow(); - var baseHeight = row.Height; - row.Height = 0.5d * baseHeight * Math.Ceiling(1d + valueString.Length / maxCharsToWrap); - } - - mergedRange.Style.SetAllBorders() - .Alignment.SetWrapText(true); - return mergedRange; - } - - internal static IXLCell _SetValue(this IXLCell cell, object value) - { - switch (value) - { - case DateTime dateTime: - cell._SetValue(dateTime); - break; - case IFormattable formattable: - cell._SetValue(formattable); - break; - case string valueString: - cell._SetValue(valueString); - break; - default: - cell.Value = value; - break; - } - - return cell; - } - - internal static IXLCell _SetValue(this IXLCell cell, string value, bool adaptRowHeight = false) - { - cell.Value = value; - cell.Style - .SetAllBorders() - .Alignment.WrapText = true; - - cell.Value = value; - if (adaptRowHeight) - { - var colWidth = cell.WorksheetColumn().Width; - var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize); - if (value.Length > maxCharsToWrap) - { - var row = cell.WorksheetRow(); - var baseHeight = row.Height; - row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap); - } - } - - return cell; - } - - internal static IXLCell _ValueNoBorder(this IXLCell cell, string value, bool adaptRowHeight = false) - { - cell.Value = value; - cell.Style.Alignment.WrapText = true; - - cell.Value = value; - if (adaptRowHeight) - { - var colWidth = cell.WorksheetColumn().Width; - var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize); - if (value.Length > maxCharsToWrap) - { - var row = cell.WorksheetRow(); - var baseHeight = row.Height; - row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap); - } - } - - return cell; - } - - internal static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS", bool setAllBorders = true) { cell.Value = value; @@ -111,21 +26,6 @@ internal static class XLExtentions return cell; } - internal static IXLCell _SetValue(this IXLCell cell, IFormattable value, string format = "0.00") - { - cell.Value = value; - cell.Style - .SetAllBorders() - .Alignment.WrapText = true; - - cell.Value = value; - - cell.DataType = XLDataType.Number; - cell.Style.NumberFormat.Format = "0.00"; - - return cell; - } - public static IXLCell SetVal(this IXLCell cell, double? value, string format = "0.00") { cell.Value = (value is not null && double.IsFinite(value.Value)) ? value : null; @@ -161,7 +61,7 @@ internal static class XLExtentions return cell; } - internal static IXLStyle SetAllBorders(this IXLStyle style, XLBorderStyleValues borderStyle = XLBorderStyleValues.Thin) + private static IXLStyle SetAllBorders(this IXLStyle style, XLBorderStyleValues borderStyle = XLBorderStyleValues.Thin) { style.Border.RightBorder = borderStyle; style.Border.LeftBorder = borderStyle; @@ -172,20 +72,6 @@ internal static class XLExtentions return style; } - internal static IXLStyle SetBaseFont(this IXLStyle style) - { - style.Font.FontName = "Calibri"; - style.Font.FontSize = 10; - return style; - } - - internal static IXLStyle SetH1(this IXLStyle style) - { - style.Font.FontName = "Calibri"; - style.Font.FontSize = 14; - return style; - } - internal static T? GetCellValue(this IXLCell cell) { try @@ -204,7 +90,7 @@ internal static class XLExtentions catch { throw new FileFormatException( - $"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение"); + $"Лист '{cell.Worksheet.Name}'. {cell.Address.RowNumber} строка содержит некорректное значение в {cell.Address.ColumnNumber} столбце"); } } } \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryImportTest.cs b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryImportTest.cs deleted file mode 100644 index e6763582..00000000 --- a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryImportTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AsbCloudInfrastructure.Services.Trajectory.Import; -using System.Linq; -using Xunit; - -namespace AsbCloudWebApi.Tests.Services.Trajectory -{ - public class TrajectoryImportTest - { - private readonly TrajectoryPlanParserService trajectoryPlanImportService; - private readonly TrajectoryFactManualParserService trajectoryFactManualImportService; - - private string usingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates"; - - public TrajectoryImportTest() - { - trajectoryPlanImportService = new TrajectoryPlanParserService(); - trajectoryFactManualImportService = new TrajectoryFactManualParserService(); - } - - [Fact] - public void Import_trajectory_plan() - { - var stream = System.Reflection.Assembly.GetExecutingAssembly() - .GetManifestResourceStream($"{usingTemplateFile}.TrajectoryPlanTemplate.xlsx"); - - if (stream is null) - Assert.Fail("Файла для импорта не существует"); - - var trajectoryRows = trajectoryPlanImportService.Import(stream); - - Assert.Equal(3, trajectoryRows.Count()); - } - - [Fact] - public void Import_trajectory_fact_manual() - { - var stream = System.Reflection.Assembly.GetExecutingAssembly() - .GetManifestResourceStream($"{usingTemplateFile}.TrajectoryFactManualTemplate.xlsx"); - - if (stream is null) - Assert.Fail("Файла для импорта не существует"); - - var trajectoryRows = trajectoryFactManualImportService.Import(stream); - - Assert.Equal(4, trajectoryRows.Count()); - } - } -} diff --git a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs new file mode 100644 index 00000000..1fddd56d --- /dev/null +++ b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using AsbCloudApp.Data.Trajectory; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudInfrastructure.Services; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Xunit; + +namespace AsbCloudWebApi.Tests.Services.Trajectory; + +public class TrajectoryParserTest +{ + private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates"; + + private readonly IServiceProvider serviceProviderMock = Substitute.For(); + private readonly IServiceScope serviceScopeMock = Substitute.For(); + private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For(); + + private readonly ParserServiceFactory parserServiceFactory; + + public TrajectoryParserTest() + { + serviceScopeFactoryMock.CreateScope().Returns(serviceScopeMock); + ((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock); + + parserServiceFactory = new ParserServiceFactory(serviceProviderMock); + } + + [Fact] + public void Parse_trajectory_plan() + { + var stream = System.Reflection.Assembly.GetExecutingAssembly() + .GetManifestResourceStream($"{UsingTemplateFile}.TrajectoryPlanTemplate.xlsx"); + + if (stream is null) + Assert.Fail("Файла для импорта не существует"); + + var parserService = parserServiceFactory.Create( + ParserServiceFactory.IdTrajectoryPlanParserService); + + var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty()); + + Assert.Equal(3, trajectoryRows.Item.Count()); + } + + [Fact] + public void Parse_trajectory_fact_manual() + { + var stream = System.Reflection.Assembly.GetExecutingAssembly() + .GetManifestResourceStream($"{UsingTemplateFile}.TrajectoryFactManualTemplate.xlsx"); + + if (stream is null) + Assert.Fail("Файла для импорта не существует"); + + var parserService = parserServiceFactory.Create( + ParserServiceFactory.IdTrajectoryFactManualParserService); + + var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty()); + + Assert.Equal(4, trajectoryRows.Item.Count()); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs b/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs new file mode 100644 index 00000000..d11ac201 --- /dev/null +++ b/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs @@ -0,0 +1,13 @@ +using System.IO; +using AsbCloudApp.Data; +using Microsoft.AspNetCore.Mvc; + +namespace AsbCloudWebApi.Controllers.Interfaces; + +public interface IControllerWithParser + where TDto : class, IId +{ + ActionResult> Parse(Stream file, TOptions options); + + IActionResult GetTemplate(); +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs index 10ed41f3..7a04e7fc 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs @@ -2,7 +2,6 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudInfrastructure.Services.Trajectory.Export; -using AsbCloudInfrastructure.Services.Trajectory.Import; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -10,6 +9,10 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudInfrastructure.Services; +using AsbCloudWebApi.Controllers.Interfaces; namespace AsbCloudWebApi.Controllers.Trajectory { @@ -19,28 +22,41 @@ namespace AsbCloudWebApi.Controllers.Trajectory /// [ApiController] [Authorize] - public abstract class TrajectoryEditableController : TrajectoryController + public abstract class TrajectoryEditableController : TrajectoryController, + IControllerWithParser where TDto : TrajectoryGeoDto - { - private readonly TrajectoryParserService trajectoryImportService; - private readonly TrajectoryExportService trajectoryExportService; - private readonly ITrajectoryEditableRepository trajectoryRepository; + { + private readonly IParserService parserService; + private readonly ITrajectoryEditableRepository trajectoryRepository; - public TrajectoryEditableController(IWellService wellService, - TrajectoryParserService trajectoryImportService, - TrajectoryExportService trajectoryExportService, - ITrajectoryEditableRepository trajectoryRepository) + protected TrajectoryEditableController(IWellService wellService, + ParserServiceFactory parserServiceFactory, + TrajectoryExportService trajectoryExportService, + ITrajectoryEditableRepository trajectoryRepository, + int idParserService) : base( wellService, trajectoryExportService, trajectoryRepository) { - this.trajectoryImportService = trajectoryImportService; - this.trajectoryExportService = trajectoryExportService; - this.trajectoryRepository = trajectoryRepository; - - } + parserService = parserServiceFactory.Create(idParserService); + this.trajectoryRepository = trajectoryRepository; + } + ActionResult> IControllerWithParser.Parse(Stream file, + IParserOptionsRequest options) + { + try + { + var parserResult = parserService.Parse(file, options); + return Ok(parserResult); + } + catch (FileFormatException ex) + { + return this.ValidationBadRequest("files", ex.Message); + } + } + /// /// Возвращает excel шаблон для заполнения строк траектории /// @@ -51,110 +67,96 @@ namespace AsbCloudWebApi.Controllers.Trajectory [ProducesResponseType(StatusCodes.Status204NoContent)] public IActionResult GetTemplate() { - var stream = trajectoryExportService.GetTemplateFile(); + var stream = parserService.GetTemplateFile(); return File(stream, "application/octet-stream", fileName); } - /// - /// Импортирует координаты из excel (xlsx) файла - /// - /// id скважины - /// Коллекция из одного файла xlsx - /// Удалить операции перед импортом, если фал валидный - /// Токен отмены задачи - /// количество успешно записанных строк в БД - [HttpPost("import/{deleteBeforeImport}")] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)] - public async Task ImportAsync(int idWell, - [FromForm] IFormFileCollection files, - bool deleteBeforeImport, - CancellationToken token) - { - int? idUser = User.GetUserId(); - if (!idUser.HasValue) - return Forbid(); - if (!await CanUserAccessToWellAsync(idWell, - token).ConfigureAwait(false)) - return Forbid(); - if (files.Count < 1) - return this.ValidationBadRequest(nameof(files), "нет файла"); - var file = files[0]; - if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") - return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл."); - using Stream stream = file.OpenReadStream(); + /// + /// Импортирует координаты из excel (xlsx) файла + /// + /// id скважины + /// Коллекция из одного файла xlsx + /// Токен отмены задачи + /// количество успешно записанных строк в БД + [HttpPost("parse")] + [ProducesResponseType((int)System.Net.HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)] + public async Task>> Parse(int idWell, + [FromForm] IFormFileCollection files, + CancellationToken token) + { + var idUser = User.GetUserId(); - try - { - var trajectoryRows = trajectoryImportService.Import(stream); - foreach (var row in trajectoryRows) - { - row.IdWell = idWell; - row.IdUser = idUser.Value; - } + if (!idUser.HasValue) + return Forbid(); - if (deleteBeforeImport) - await trajectoryRepository.DeleteByIdWellAsync(idWell, token); + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); - var rowsCount = await trajectoryRepository.AddRangeAsync(trajectoryRows, token); + return this.ParseExcelFile(files, IParserOptionsRequest.Empty()); + } + + /// + /// Добавление + /// + /// + /// + /// + /// + [HttpPost] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task InsertRangeAsync(int idWell, [FromBody] IEnumerable dtos, CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + var idUser = User.GetUserId(); + + if (!idUser.HasValue) + return Forbid(); + + foreach (var dto in dtos) + { + dto.IdUser = idUser.Value; + dto.IdWell = idWell; + } - return Ok(rowsCount); - } - catch (FileFormatException ex) - { - return this.ValidationBadRequest(nameof(files), ex.Message); - } - } + var result = await trajectoryRepository.AddRangeAsync(dtos, token); + return Ok(result); + } + /// + /// Удалить все по скважине и добавить новые + /// + /// + /// + /// + /// + [HttpPost("replace")] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task ClearAndInsertRangeAsync(int idWell, [FromBody] IEnumerable dtos, CancellationToken token) + { + //TODO: это вся радость требует рефакторинга. + //Удаление с добавлением новых записей должно происходить в рамках одной транзакции, да и вообще должно быть реализовано на уровне репозиторий. + //Рефакторинг будет когда доберёмся до журнала изменений для траекторий. + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + var idUser = User.GetUserId(); + + if (!idUser.HasValue) + return Forbid(); + + foreach (var dto in dtos) + { + dto.IdUser = idUser.Value; + dto.IdWell = idWell; + } - /// - /// Добавить одну новую строчку координат для плановой траектории - /// - /// - /// - /// - /// количество успешно записанных строк в БД - [HttpPost] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task AddAsync(int idWell, [FromBody] TDto row, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - var idUser = User.GetUserId(); - if (!idUser.HasValue) - return Forbid(); - row.IdUser = idUser.Value; - row.IdWell = idWell; - var result = await trajectoryRepository.AddAsync(row, token); - return Ok(result); - } - - /// - /// Добавить массив строчек координат для плановой траектории - /// - /// - /// - /// - /// количество успешно записанных строк в БД - [HttpPost("range")] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task AddRangeAsync(int idWell, [FromBody] IEnumerable rows, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - int? idUser = User.GetUserId(); - if (!idUser.HasValue) - return Forbid(); - foreach (var item in rows) - { - item.IdUser = idUser.Value; - item.IdWell = idWell; - } - var result = await trajectoryRepository.AddRangeAsync(rows, token); - return Ok(result); - } + await trajectoryRepository.DeleteByIdWellAsync(idWell, token); + var result = await trajectoryRepository.AddRangeAsync(dtos, token); + return Ok(result); + } /// /// Изменить выбранную строку с координатами @@ -201,4 +203,4 @@ namespace AsbCloudWebApi.Controllers.Trajectory return Ok(result); } } -} +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs index e3020342..97b84b07 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs @@ -1,8 +1,8 @@ using AsbCloudApp.Data.Trajectory; using AsbCloudApp.Repositories; using AsbCloudApp.Services; +using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.Trajectory.Export; -using AsbCloudInfrastructure.Services.Trajectory.Import; using Microsoft.AspNetCore.Mvc; namespace AsbCloudWebApi.Controllers.Trajectory; @@ -14,15 +14,17 @@ namespace AsbCloudWebApi.Controllers.Trajectory; [Route("api/well/{idWell}/[controller]")] public class TrajectoryFactManualController : TrajectoryEditableController { - protected override string fileName => "ЕЦП_шаблон_файла_фактическая_траектория.xlsx"; - public TrajectoryFactManualController(IWellService wellService, - TrajectoryFactManualParserService factTrajectoryImportService, - TrajectoryFactManualExportService factTrajectoryExportService, - ITrajectoryEditableRepository trajectoryFactRepository) - : base( - wellService, - factTrajectoryImportService, - factTrajectoryExportService, - trajectoryFactRepository) - { } + protected override string fileName => "ЕЦП_шаблон_файла_фактическая_траектория.xlsx"; + + public TrajectoryFactManualController(IWellService wellService, + TrajectoryFactManualExportService trajectoryExportService, + ParserServiceFactory parserServiceFactory, + ITrajectoryEditableRepository trajectoryRepository) + : base(wellService, + parserServiceFactory, + trajectoryExportService, + trajectoryRepository, + ParserServiceFactory.IdTrajectoryFactManualParserService) + { + } } \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs index 122352ad..cf3f648d 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs @@ -2,58 +2,58 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudInfrastructure.Services.Trajectory; -using AsbCloudInfrastructure.Services.Trajectory.Import; using AsbCloudInfrastructure.Services.Trajectory.Export; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using AsbCloudInfrastructure.Services; namespace AsbCloudWebApi.Controllers.Trajectory { + /// + /// Плановая траектория (загрузка и хранение) + /// + [Route("api/well/{idWell}/[controller]")] + [ApiController] + public class TrajectoryPlanController : TrajectoryEditableController + { + private readonly TrajectoryService trajectoryVisualizationService; - /// - /// Плановая траектория (загрузка и хранение) - /// - [Route("api/well/{idWell}/[controller]")] - [ApiController] - public class TrajectoryPlanController : TrajectoryEditableController - { - private readonly TrajectoryService trajectoryVisualizationService; + protected override string fileName => "ЕЦП_шаблон_файла_плановая_траектория.xlsx"; - protected override string fileName => "ЕЦП_шаблон_файла_плановая_траектория.xlsx"; + public TrajectoryPlanController(IWellService wellService, + TrajectoryPlanExportService trajectoryExportService, + ParserServiceFactory parserServiceFactory, + ITrajectoryEditableRepository trajectoryRepository, + TrajectoryService trajectoryVisualizationService) + : base(wellService, + parserServiceFactory, + trajectoryExportService, + trajectoryRepository, + ParserServiceFactory.IdTrajectoryPlanParserService) + { + this.trajectoryVisualizationService = trajectoryVisualizationService; + } - public TrajectoryPlanController(IWellService wellService, - TrajectoryPlanParserService trajectoryPlanImportService, - TrajectoryPlanExportService trajectoryPlanExportService, - ITrajectoryEditableRepository trajectoryPlanRepository, - TrajectoryService trajectoryVisualizationService) - : base( - wellService, - trajectoryPlanImportService, - trajectoryPlanExportService, - trajectoryPlanRepository) - { - this.trajectoryVisualizationService = trajectoryVisualizationService; - } + /// + /// Получение координат для визуализации траектории (плановой и фактической) + /// + /// + /// + /// + [HttpGet("trajectoryCartesianPlanFact")] + [ProducesResponseType( + typeof(TrajectoryPlanFactDto, IEnumerable>), + (int)System.Net.HttpStatusCode.OK)] + public async Task GetTrajectoryCartesianPlanFactAsync(int idWell, CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, + token).ConfigureAwait(false)) + return Forbid(); - /// - /// Получение координат для визуализации траектории (плановой и фактической) - /// - /// - /// - /// - [HttpGet("trajectoryCartesianPlanFact")] - [ProducesResponseType(typeof(TrajectoryPlanFactDto, IEnumerable>), (int)System.Net.HttpStatusCode.OK)] - public async Task GetTrajectoryCartesianPlanFactAsync(int idWell, CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, - token).ConfigureAwait(false)) - return Forbid(); - - var result = await trajectoryVisualizationService.GetTrajectoryCartesianAsync(idWell, token); - return Ok(result); - } - } - -} + var result = await trajectoryVisualizationService.GetTrajectoryCartesianAsync(idWell, token); + return Ok(result); + } + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Extensions.cs b/AsbCloudWebApi/Extensions.cs new file mode 100644 index 00000000..489d6149 --- /dev/null +++ b/AsbCloudWebApi/Extensions.cs @@ -0,0 +1,125 @@ +using AsbCloudApp.Data.User; +using AsbCloudWebApi.Converters; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Security.Claims; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudWebApi.Controllers.Interfaces; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc; + +public static class Extensions +{ + public static int? GetCompanyId(this ClaimsPrincipal user) + { + var claimIdCompany = user.FindFirst(nameof(UserDto.IdCompany)); + if (claimIdCompany is null) + return null; + + return int.TryParse(claimIdCompany.Value, out int uid) + ? uid + : null; + } + + public static int? GetUserId(this ClaimsPrincipal user) + { + var userId = user.FindFirst(nameof(UserDto.Id)); + if (userId is null) + return null; + + return int.TryParse(userId.Value, out int uid) + ? uid + : null; + } + + /// + /// + /// Returns BadRequest with ValidationProblemDetails as body + /// + /// + /// Используйте этот метод только если валидацию нельзя сделать через + /// атрибуты валидации или IValidatableObject модели. + /// + /// + /// + /// + /// + /// + public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, string paramName, string error) + { + return MakeBadRequestObjectResult(paramName, error); + } + + private static BadRequestObjectResult MakeBadRequestObjectResult(string paramName, string error) + { + var errors = new Dictionary { + { paramName, new[]{ error } } + }; + var problem = new ValidationProblemDetails(errors); + var badRequestObject = new BadRequestObjectResult(problem); + return badRequestObject; + } + + /// + /// + /// Returns BadRequest with ValidationProblemDetails as body + /// + /// + /// Используйте этот метод только если валидацию нельзя сделать через + /// атрибуты валидации или IValidatableObject модели. + /// + /// + /// + /// + /// + public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, IEnumerable validationResults) + { + var errors = validationResults + .SelectMany(e => e.MemberNames.Select(name => new { name, e.ErrorMessage })) + .GroupBy(e => e.name) + .ToDictionary(e => e.Key, e => e.Select(el => el.ErrorMessage ?? string.Empty).ToArray()); + + var problem = new ValidationProblemDetails(errors); + return controller.BadRequest(problem); + } + + public static MvcOptions UseDateOnlyTimeOnlyStringConverters(this MvcOptions options) + { + TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter))); + return options; + } + + /// + /// Вызов парсера со стандартной валидацией входного файла + /// + /// + /// + /// + /// + /// + /// + public static ActionResult> ParseExcelFile( + this IControllerWithParser controller, + IFormFileCollection files, + TOptions options) + where TDto : class, IId + where TOptions : class, IParserOptionsRequest + { + if (files.Count < 1) + return MakeBadRequestObjectResult(nameof(files), "Нет файла"); + + var file = files[0]; + if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") + return MakeBadRequestObjectResult(nameof(files), "Требуется .xlsx файл."); + + var stream = file.OpenReadStream(); + + return controller.Parse(stream, options); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs deleted file mode 100644 index 7299fae5..00000000 --- a/AsbCloudWebApi/Extentions.cs +++ /dev/null @@ -1,89 +0,0 @@ -using AsbCloudApp.Data.User; -using AsbCloudWebApi.Converters; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Security.Claims; - -namespace Microsoft.AspNetCore.Mvc -{ - public static class Extentions - { - public static int? GetCompanyId(this ClaimsPrincipal user) - { - var claimIdCompany = user.FindFirst(nameof(UserDto.IdCompany)); - if (claimIdCompany is null) - return null; - - return int.TryParse(claimIdCompany.Value, out int uid) - ? uid - : null; - } - - public static int? GetUserId(this ClaimsPrincipal user) - { - var userId = user.FindFirst(nameof(UserDto.Id)); - if (userId is null) - return null; - - return int.TryParse(userId.Value, out int uid) - ? uid - : null; - } - - /// - /// - /// Returns BadRequest with ValidationProblemDetails as body - /// - /// - /// Используйте этот метод только если валидацию нельзя сделать через - /// атрибуты валидации или IValidatableObject модели. - /// - /// - /// - /// - /// - /// - public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, string paramName, string error) - { - var errors = new Dictionary { - { paramName, new[]{ error } } - }; - var problem = new ValidationProblemDetails(errors); - return controller.BadRequest(problem); - } - - /// - /// - /// Returns BadRequest with ValidationProblemDetails as body - /// - /// - /// Используйте этот метод только если валидацию нельзя сделать через - /// атрибуты валидации или IValidatableObject модели. - /// - /// - /// - /// - /// - public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, IEnumerable validationResults) - { - var errors = validationResults - .SelectMany(e => e.MemberNames.Select(name => new { name, e.ErrorMessage })) - .GroupBy(e => e.name) - .ToDictionary(e => e.Key, e => e.Select(el => el.ErrorMessage ?? string.Empty).ToArray()); - - var problem = new ValidationProblemDetails(errors); - return controller.BadRequest(problem); - } - - public static MvcOptions UseDateOnlyTimeOnlyStringConverters(this MvcOptions options) - { - TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter))); - return options; - } - - } -} -