From 33bd5e2bbfc90d5dd9a0f3a2168e66fb51f4da24 Mon Sep 17 00:00:00 2001 From: Alessio Dal Santo Date: Tue, 17 Jun 2025 18:29:35 +0200 Subject: [PATCH] feat: Implementa supporto completo per file Excel/CSV come fonte dati MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Aggiunge selezione tipo fonte dati (database o file) nella UI - Implementa caricamento e parsing di file Excel (.xlsx, .xls) usando ExcelDataReader - Implementa parsing CSV con rilevamento automatico separatore (,;|\t) - Aggiunge preview paginato dei dati file con controlli navigazione - Estende mapping campi per supportare sia database che file - Corregge errori strutturali HTML/Razor e gestione chiavi dizionario - Migliora logica trasferimento dati per fonti multiple - Aggiunge supporto encoding per file Excel legacy (.xls) Modifiche principali: - DataCoupler.razor: UI completa per gestione file + correzioni strutturali - Data_Coupler.csproj: Dipendenze ExcelDataReader per supporto Excel - Program.cs: Registrazione provider encoding per compatibilità .xls Il sistema ora supporta completamente sia database che file come fonte dati con parsing robusto, preview interattivo e mapping flessibile. --- Data_Coupler/Data_Coupler.csproj | 5 + Data_Coupler/Pages/DataCoupler.razor | 943 ++++++++++++++++--- Data_Coupler/Program.cs | 3 + Data_Coupler/wwwroot/data/credentials.db-shm | Bin 0 -> 32768 bytes Data_Coupler/wwwroot/data/credentials.db-wal | 0 5 files changed, 833 insertions(+), 118 deletions(-) create mode 100644 Data_Coupler/wwwroot/data/credentials.db-shm create mode 100644 Data_Coupler/wwwroot/data/credentials.db-wal diff --git a/Data_Coupler/Data_Coupler.csproj b/Data_Coupler/Data_Coupler.csproj index 74e8e6a..157525b 100644 --- a/Data_Coupler/Data_Coupler.csproj +++ b/Data_Coupler/Data_Coupler.csproj @@ -11,4 +11,9 @@ + + + + + diff --git a/Data_Coupler/Pages/DataCoupler.razor b/Data_Coupler/Pages/DataCoupler.razor index 0d69450..56b076e 100644 --- a/Data_Coupler/Pages/DataCoupler.razor +++ b/Data_Coupler/Pages/DataCoupler.razor @@ -7,6 +7,10 @@ @using Data_Coupler.Services @using Microsoft.AspNetCore.Components.Forms @using Microsoft.JSInterop +@using System.IO +@using System.Text +@using System.Data +@using ExcelDataReader @inject IDataConnectionCredentialService CredentialService @inject IDataConnectionFactory ConnectionFactory @inject IJSRuntime JSRuntime @@ -20,103 +24,360 @@

Data Coupler - Coupling Database e REST API

Connetti database e servizi REST per il trasferimento dati

- - -
- +
+
-
Database Source
+
Fonte Dati
- +
- - + + +
- @if (!string.IsNullOrEmpty(selectedDatabaseCredential)) + + @if (selectedSourceType == "database") { +
-
- } - @if (!string.IsNullOrEmpty(databaseErrorMessage)) - { - - } - @if (databaseTables.Any()) - { -
-
Tabelle Database (@databaseTables.Count disponibili):
- - -
-
- - - - - @if (!string.IsNullOrEmpty(databaseSearchTerm)) + @if (!string.IsNullOrEmpty(selectedDatabaseCredential)) + { +
+ + } -
-
- - -
- @foreach (var table in GetFilteredDatabaseTables()) + Connetti e Scopri Schema + + @if (isDatabaseConnected) { - - - - @if (!GetFilteredDatabaseTables().Any()) + } + } + + + @if (selectedSourceType == "file") + { +
+ + + @if (!string.IsNullOrEmpty(selectedFileName)) { -
- Nessuna tabella trovata con il termine di ricerca "@databaseSearchTerm" -
+ File selezionato: @selectedFileName }
+ + @if (isProcessingFile) + { +
+
+ + Elaborazione file in corso... +
+
+ } + + @if (!string.IsNullOrEmpty(fileErrorMessage)) + { + + } + @if (fileSheets.Any()) + { +
+ @{ + var fileExtension = Path.GetExtension(selectedFileName).ToLowerInvariant(); + var isExcel = fileExtension == ".xlsx" || fileExtension == ".xls"; + var fileTypeIcon = isExcel ? "fa-file-excel text-success" : "fa-file-csv text-info"; + var fileTypeName = isExcel ? "Excel" : "CSV"; + } +
+ + Fogli @fileTypeName (@fileSheets.Count): +
+ + +
+ } else if (selectedSourceType == "file" && !string.IsNullOrEmpty(selectedFileName) && !isProcessingFile && string.IsNullOrEmpty(fileErrorMessage)) + { + + } + else if (selectedSourceType == "file" && string.IsNullOrEmpty(selectedFileName)) + { + + } + + + @if (!string.IsNullOrEmpty(selectedSheet) && fileData.ContainsKey(selectedSheet) && fileData[selectedSheet].Any()) + { +
+ + +
+
+
+
+
Preview Dati - @selectedSheet
+
+ + Record @GetStartRecord()-@GetEndRecord() di @fileData[selectedSheet].Count + +
+ + + + @currentPage di @GetTotalPages(selectedSheet) + + + +
+
+
+
+
+ @if (fileSheets.ContainsKey(selectedSheet)) + { +
+ + + + + @foreach (var column in fileSheets[selectedSheet]) + { + + } + + + + @{ + var dataToShow = GetCurrentPageData(); + var startRecord = GetStartRecord(); + } + @for (int i = 0; i < dataToShow.Count; i++) + { + var row = dataToShow[i]; + var absoluteRowIndex = startRecord + i; + + + @foreach (var column in fileSheets[selectedSheet]) + { + var cellValue = row.ContainsKey(column) ? row[column]?.ToString() : ""; + var displayValue = string.IsNullOrEmpty(cellValue) ? "-" : + (cellValue.Length > 50 ? cellValue.Substring(0, 50) + "..." : cellValue); + + } + + } + +
#@column
@absoluteRowIndex + @displayValue +
+
+ } +
+ + + +
+
+
} }
@@ -219,26 +480,31 @@
-
- - - @if (isDatabaseConnected && isRestConnected && !string.IsNullOrEmpty(selectedTable) && selectedRestEntity != null) +
+ @{ + var isSourceReady = (selectedSourceType == "database" && isDatabaseConnected && !string.IsNullOrEmpty(selectedTable)) || + (selectedSourceType == "file" && !string.IsNullOrEmpty(selectedSheet)); + } + @if (isSourceReady && isRestConnected && selectedRestEntity != null) {
Mapping Campi
-
-
-
Mapping tra @selectedTable e @selectedRestEntity.Name
-

Configura il mapping tra i campi del database e le proprietà dell'entità REST

+
+ @{ + var sourceDisplayName = selectedSourceType == "database" ? selectedTable : selectedSheet; + var sourceTypeName = selectedSourceType == "database" ? "Tabella" : "Foglio"; + } +
Mapping tra @sourceTypeName @sourceDisplayName e @selectedRestEntity.Name
+

Configura il mapping tra i campi della fonte dati e le proprietà dell'entità REST

- +
-
Campi Database (@selectedTable)
+
Campi @sourceTypeName (@sourceDisplayName)
- @if (databaseTables.ContainsKey(selectedTable)) + @if (selectedSourceType == "database" && databaseTables.ContainsKey(selectedTable)) { @foreach (var column in databaseTables[selectedTable]) { @@ -263,6 +529,27 @@ } } + else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet)) + { + @foreach (var column in fileSheets[selectedSheet]) + { + +
+
+ @column + Colonna File +
+
+ @if (fieldMappings.ContainsKey(column)) + { + Mapped + } +
+
+
+ } + }
@@ -339,15 +626,18 @@ Tipo REST Azioni - - - @foreach (var mapping in fieldMappings) + @foreach (var mapping in fieldMappings) { - var dbColumn = databaseTables[selectedTable].FirstOrDefault(c => c.Name == mapping.Key); + DbColumnInfo? dbColumn = null; + if (selectedSourceType == "database" && !string.IsNullOrEmpty(selectedTable)) + { + dbColumn = databaseTables.ContainsKey(selectedTable) ? + databaseTables[selectedTable].FirstOrDefault(c => c.Name == mapping.Key) : null; + } var restProperty = restEntityDetails?.Properties.FirstOrDefault(p => p.Name == mapping.Value); @mapping.Key - @(dbColumn?.DataType ?? "Unknown") + @(dbColumn?.DataType ?? (selectedSourceType == "file" ? "Text" : "Unknown")) @mapping.Value @(restProperty?.Type ?? "Unknown") @@ -366,16 +656,16 @@
- + { + + @("Trasferimento in corso") + } + else + { + @("Avvia Trasferimento Dati") + } + + @if (fieldMappings.Any()) {
- } -
+ }
@@ -420,6 +709,9 @@ private List databaseCredentials = new(); private List restApiCredentials = new(); + // Selezione tipo fonte + private string selectedSourceType = ""; + // Credenziali selezionate private string selectedDatabaseCredential = ""; private string selectedRestCredential = ""; @@ -433,11 +725,25 @@ // Messaggi di errore private string databaseErrorMessage = ""; private string restErrorMessage = ""; - // Database discovery + + // Database discovery private Dictionary> databaseTables = new(); private string selectedTable = ""; - private string databaseSearchTerm = ""; - // REST discovery + private string databaseSearchTerm = ""; // File handling + private string selectedFileName = ""; + private bool isProcessingFile = false; + private string fileErrorMessage = ""; + private Dictionary> fileSheets = new(); // SheetName -> Columns + private Dictionary>> fileData = new(); // SheetName -> Data rows + private string selectedSheet = ""; + + // File preview pagination + private int currentPage = 1; + private int pageSize = 20; + private int GetTotalPages(string sheetName) => fileData.ContainsKey(sheetName) ? + (int)Math.Ceiling((double)fileData[sheetName].Count / pageSize) : 0; + + // REST discovery private List restEntities = new(); private RestEntitySummary? selectedRestEntity = null; private RestEntityInfo? restEntityDetails = null; @@ -461,9 +767,7 @@ protected override async Task OnInitializedAsync() { await LoadCredentials(); - } - - private async Task LoadCredentials() + } private async Task LoadCredentials() { try { @@ -475,7 +779,340 @@ Logger.LogError(ex, "Errore nel caricamento delle credenziali"); await JSRuntime.InvokeVoidAsync("alert", $"Errore nel caricamento delle credenziali: {ex.Message}"); } - } private void OnDatabaseCredentialChanged(ChangeEventArgs e) + } + + private void OnSourceTypeChanged(ChangeEventArgs e) + { + selectedSourceType = e.Value?.ToString() ?? ""; + + // Reset state when changing source type + ResetSourceState(); } private void ResetSourceState() + { + // Reset database state + ResetDatabaseState(); + + // Reset file state + selectedFileName = ""; + isProcessingFile = false; + fileErrorMessage = ""; + fileSheets.Clear(); + fileData.Clear(); + selectedSheet = ""; + + // Reset pagination + currentPage = 1; + + // Reset mappings + ClearAllMappings(); + } + + private async Task OnFileSelected(InputFileChangeEventArgs e) + { try + { + isProcessingFile = true; + fileErrorMessage = ""; + fileSheets.Clear(); + fileData.Clear(); + selectedSheet = ""; + + var file = e.File; + selectedFileName = file.Name; + + // Validate file type + var extension = Path.GetExtension(file.Name).ToLowerInvariant(); + if (extension != ".xlsx" && extension != ".xls" && extension != ".csv") + { + fileErrorMessage = "Formato file non supportato. Utilizzare Excel (.xlsx, .xls) o CSV (.csv)"; + return; + } + + // Process file based on type + if (extension == ".csv") + { + await ProcessCsvFile(file); + } + else + { + await ProcessExcelFile(file); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nell'elaborazione del file"); + fileErrorMessage = $"Errore nell'elaborazione del file: {ex.Message}"; + } + finally + { + isProcessingFile = false; + StateHasChanged(); + } + } private async Task ProcessCsvFile(IBrowserFile file) + { + using var stream = file.OpenReadStream(maxAllowedSize: 50 * 1024 * 1024); // Aumentato a 50MB + using var reader = new StreamReader(stream); + + var firstLine = await reader.ReadLineAsync(); + if (string.IsNullOrEmpty(firstLine)) + { + fileErrorMessage = "Il file CSV è vuoto"; + return; + } + + Logger.LogInformation("CSV first line: {FirstLine}", firstLine); + + // Detect separator automatically + var separator = DetectCsvSeparator(firstLine); + Logger.LogInformation("CSV separator detected: '{Separator}'", separator); + + // Parse headers (first row) - gestisce meglio i separatori + var headers = ParseCsvLine(firstLine, separator); + + Logger.LogInformation("CSV headers parsed: {Headers}", string.Join(" | ", headers)); + // For CSV, we create a single "sheet" with the filename + var sheetName = Path.GetFileNameWithoutExtension(file.Name); + fileSheets[sheetName] = headers; + + // Read data rows - rimuovo il limite di 1000 righe + var dataRows = new List>(); + string? line; + int rowNumber = 2; // Starting from row 2 (after header) + + while ((line = await reader.ReadLineAsync()) != null) + { + if (string.IsNullOrWhiteSpace(line)) continue; + + var values = ParseCsvLine(line, separator); + var row = new Dictionary(); + for (int i = 0; i < headers.Count; i++) + { + var value = i < values.Count ? values[i] : ""; + row[headers[i]] = string.IsNullOrEmpty(value) ? "" : value; + } + + dataRows.Add(row); + rowNumber++; + + // Log delle prime 3 righe per debug + if (rowNumber <= 5) + { + Logger.LogInformation("CSV row {RowNumber}: {Values}", rowNumber - 1, string.Join(" | ", values)); + } + } + fileData[sheetName] = dataRows; + + // Auto-seleziona il foglio per i CSV dato che ce n'è solo uno + selectedSheet = sheetName; + + Logger.LogInformation("CSV file processed: {FileName}, Headers: {HeaderCount} ({Headers}), Rows: {RowCount}, Auto-selected sheet: {SheetName}", + file.Name, headers.Count, string.Join(", ", headers), dataRows.Count, selectedSheet); + } private List ParseCsvLine(string line, char separator = ',') + { + var result = new List(); + var current = new StringBuilder(); + bool inQuotes = false; + + for (int i = 0; i < line.Length; i++) + { + char c = line[i]; + + if (c == '"') + { + if (inQuotes && i + 1 < line.Length && line[i + 1] == '"') + { + // Double quote - escaped quote + current.Append('"'); + i++; // Skip next quote + } + else + { + // Toggle quote mode + inQuotes = !inQuotes; + } + } + else if (c == separator && !inQuotes) + { + // End of field + result.Add(current.ToString().Trim()); + current.Clear(); + } + else + { + current.Append(c); + } + } + + // Add the last field + result.Add(current.ToString().Trim()); + + return result; + }private async Task ProcessExcelFile(IBrowserFile file) + { + try + { + using var stream = file.OpenReadStream(maxAllowedSize: 50 * 1024 * 1024); // 50MB max + + // Crea il reader Excel basato sull'estensione + IExcelDataReader reader; + var extension = Path.GetExtension(file.Name).ToLowerInvariant(); + + if (extension == ".xlsx") + { + reader = ExcelReaderFactory.CreateOpenXmlReader(stream); + } + else if (extension == ".xls") + { + reader = ExcelReaderFactory.CreateBinaryReader(stream); + } + else + { + fileErrorMessage = "Formato Excel non supportato. Utilizzare .xlsx o .xls"; + return; + } + + using (reader) + { + // Configura per utilizzare la prima riga come header + var configuration = new ExcelDataSetConfiguration() + { + ConfigureDataTable = (_) => new ExcelDataTableConfiguration() + { + UseHeaderRow = true // Prima riga come header + } + }; + + // Converti in DataSet + var dataSet = reader.AsDataSet(configuration); + + Logger.LogInformation("Excel file processed: {FileName}, Sheets: {SheetCount}", + file.Name, dataSet.Tables.Count); + + // Processa ogni foglio + foreach (DataTable table in dataSet.Tables) + { + var sheetName = table.TableName; + var headers = new List(); + var dataRows = new List>(); + + // Estrai i nomi delle colonne (headers) + foreach (DataColumn column in table.Columns) + { + headers.Add(column.ColumnName); + } + + Logger.LogInformation("Processing Excel sheet: {SheetName}, Columns: {ColumnCount}, Rows: {RowCount}", + sheetName, headers.Count, table.Rows.Count); + + // Processa le righe di dati + for (int i = 0; i < table.Rows.Count; i++) + { + var row = table.Rows[i]; + var rowData = new Dictionary(); + + for (int j = 0; j < headers.Count; j++) + { + var cellValue = row[j]?.ToString() ?? ""; + rowData[headers[j]] = string.IsNullOrEmpty(cellValue) ? "" : cellValue; + } + + dataRows.Add(rowData); + + // Log delle prime 3 righe per debug + if (i < 3) + { + Logger.LogInformation("Excel row {RowNumber} in {Sheet}: {Values}", + i + 1, sheetName, string.Join(" | ", rowData.Values)); + } + } + + // Salva i dati del foglio + fileSheets[sheetName] = headers; + fileData[sheetName] = dataRows; + + Logger.LogInformation("Excel sheet completed: {SheetName}, Headers: {Headers}, Rows: {RowCount}", + sheetName, string.Join(", ", headers), dataRows.Count); + } + + // Auto-seleziona il primo foglio se non c'è una selezione + if (fileSheets.Any() && string.IsNullOrEmpty(selectedSheet)) + { + selectedSheet = fileSheets.First().Key; + Logger.LogInformation("Auto-selected first sheet: {SheetName}", selectedSheet); + } Logger.LogInformation("Excel file processing completed: {FileName}, Total sheets: {SheetCount}, Selected: {SelectedSheet}", + file.Name, fileSheets.Count, selectedSheet); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nell'elaborazione del file Excel: {FileName}", file.Name); + fileErrorMessage = $"Errore nell'elaborazione del file Excel: {ex.Message}"; + } + + await Task.CompletedTask; + }private void SelectSheet(string sheetName) + { + selectedSheet = sheetName; + + // Reset pagination when changing sheet + currentPage = 1; + + // Clear mappings when changing sheet + ClearAllMappings(); + + StateHasChanged(); + } + + // File preview pagination methods + private void GoToPage(int page) + { + if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) + return; + + var totalPages = GetTotalPages(selectedSheet); + if (page >= 1 && page <= totalPages) + { + currentPage = page; + StateHasChanged(); + } + } + + private void FirstPage() => GoToPage(1); + private void PreviousPage() => GoToPage(currentPage - 1); + private void NextPage() => GoToPage(currentPage + 1); + private void LastPage() => GoToPage(GetTotalPages(selectedSheet)); + + private List> GetCurrentPageData() + { + if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) + return new List>(); + + var allData = fileData[selectedSheet]; + var skip = (currentPage - 1) * pageSize; + return allData.Skip(skip).Take(pageSize).ToList(); + } + + private int GetStartRecord() + { + if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) + return 0; + return (currentPage - 1) * pageSize + 1; + } private int GetEndRecord() + { + if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) + return 0; + var totalRecords = fileData[selectedSheet].Count; + var endRecord = currentPage * pageSize; + return Math.Min(endRecord, totalRecords); + } + + private void OnPageSizeChanged(ChangeEventArgs e) + { + if (int.TryParse(e.Value?.ToString(), out int newPageSize)) + { + pageSize = newPageSize; + currentPage = 1; // Reset to first page when changing page size + StateHasChanged(); + } + }private void OnDatabaseCredentialChanged(ChangeEventArgs e) { selectedDatabaseCredential = e.Value?.ToString() ?? ""; ResetDatabaseState(); @@ -806,13 +1443,26 @@ } await JSRuntime.InvokeVoidAsync("alert", summary); - } - - private async Task StartDataTransfer() + } private async Task StartDataTransfer() { - if (!fieldMappings.Any() || currentDatabaseManager == null || currentRestClient == null || selectedRestEntity == null) + if (!fieldMappings.Any() || currentRestClient == null || selectedRestEntity == null) { - transferMessage = "Configurazione incompleta. Assicurati di aver selezionato tabella, entità e configurato almeno una mappatura."; + transferMessage = "Configurazione incompleta. Assicurati di aver selezionato la fonte dati, entità e configurato almeno una mappatura."; + transferMessageType = "error"; + return; + } + + // Check source-specific requirements + if (selectedSourceType == "database" && (currentDatabaseManager == null || string.IsNullOrEmpty(selectedTable))) + { + transferMessage = "Database non connesso o tabella non selezionata."; + transferMessageType = "error"; + return; + } + + if (selectedSourceType == "file" && string.IsNullOrEmpty(selectedSheet)) + { + transferMessage = "File non caricato o foglio non selezionato."; transferMessageType = "error"; return; } @@ -823,16 +1473,17 @@ try { - Logger.LogInformation("Iniziando trasferimento dati da {Table} a {Entity} con {MappingCount} mappature", - selectedTable, selectedRestEntity.Name, fieldMappings.Count); + var sourceName = selectedSourceType == "database" ? selectedTable : selectedSheet; + Logger.LogInformation("Iniziando trasferimento dati da {SourceType} {Source} a {Entity} con {MappingCount} mappature", + selectedSourceType, sourceName, selectedRestEntity.Name, fieldMappings.Count); - // 1. Ottieni tutti i record dalla tabella database - var records = await GetAllRecordsFromTable(); - Logger.LogInformation("Ottenuti {RecordCount} record dalla tabella {Table}", records.Count(), selectedTable); + // 1. Ottieni tutti i record dalla fonte dati + var records = await GetAllRecordsFromSource(); + Logger.LogInformation("Ottenuti {RecordCount} record da {SourceType} {Source}", records.Count(), selectedSourceType, sourceName); if (!records.Any()) { - transferMessage = "Nessun record trovato nella tabella selezionata."; + transferMessage = "Nessun record trovato nella fonte dati selezionata."; transferMessageType = "error"; return; } @@ -895,9 +1546,21 @@ { isTransferringData = false; } + } private async Task>> GetAllRecordsFromSource() + { + if (selectedSourceType == "database") + { + return await GetAllRecordsFromDatabase(); + } + else if (selectedSourceType == "file") + { + return await GetAllRecordsFromFile(); + } + + return new List>(); } - private async Task>> GetAllRecordsFromTable() + private async Task>> GetAllRecordsFromDatabase() { if (currentDatabaseManager == null || string.IsNullOrEmpty(selectedTable)) return new List>(); @@ -913,6 +1576,15 @@ Logger.LogError(ex, "Errore nell'ottenere i record dalla tabella {Table}", selectedTable); throw; } + } private async Task>> GetAllRecordsFromFile() + { + if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) + { + return new List>(); + } + + await Task.CompletedTask; + return fileData[selectedSheet]; } private Dictionary TransformRecordToRestEntity(Dictionary dbRecord) @@ -1014,4 +1686,39 @@ { currentDatabaseManager?.Dispose(); } + + private char DetectCsvSeparator(string line) + { + // Common separators to check + var separators = new[] { ',', ';', '\t', '|' }; + var counts = new Dictionary(); + + bool inQuotes = false; + + // Count separators outside of quotes + foreach (char c in line) + { + if (c == '"') + { + inQuotes = !inQuotes; + } + else if (!inQuotes && separators.Contains(c)) + { + counts[c] = counts.GetValueOrDefault(c, 0) + 1; + } + } + + // Return the separator with the highest count, default to comma + if (counts.Any()) + { + var mostCommon = counts.OrderByDescending(x => x.Value).First(); + // Make sure we have at least one occurrence to avoid single-column files + if (mostCommon.Value > 0) + { + return mostCommon.Key; + } + } + + return ','; // Default fallback + } } diff --git a/Data_Coupler/Program.cs b/Data_Coupler/Program.cs index 505d922..8fdc433 100644 --- a/Data_Coupler/Program.cs +++ b/Data_Coupler/Program.cs @@ -11,6 +11,9 @@ using Data_Coupler.Services; using System; using System.Threading.Tasks; +// Registra il provider di encoding per ExcelDataReader (necessario per file .xls) +System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + var builder = WebApplication.CreateBuilder(args); // Add services to the container. diff --git a/Data_Coupler/wwwroot/data/credentials.db-shm b/Data_Coupler/wwwroot/data/credentials.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r3