@page "/data-coupler" @using CredentialManager.Models @using DataConnection.Interfaces @using DataConnection.CredentialManagement.Interfaces @using DataConnection.REST.Interfaces @using DataConnection.REST.Models @using Data_Coupler.Services @using Microsoft.AspNetCore.Components.Forms @using Microsoft.JSInterop @inject IDataConnectionCredentialService CredentialService @inject IDataConnectionFactory ConnectionFactory @inject IJSRuntime JSRuntime @inject ILogger Logger Data Coupler

Data Coupler - Coupling Database e REST API

Connetti database e servizi REST per il trasferimento dati

Database Source
@if (!string.IsNullOrEmpty(selectedDatabaseCredential)) {
@if (isDatabaseConnected) { Connesso }
} @if (!string.IsNullOrEmpty(databaseErrorMessage)) { } @if (databaseTables.Any()) {
Tabelle Database (@databaseTables.Count disponibili):
@if (!string.IsNullOrEmpty(databaseSearchTerm)) { }
@if (!GetFilteredDatabaseTables().Any()) {
Nessuna tabella trovata con il termine di ricerca "@databaseSearchTerm"
}
}
REST API Destination
@if (!string.IsNullOrEmpty(selectedRestCredential)) {
@if (isRestConnected) { Connesso }
} @if (!string.IsNullOrEmpty(restErrorMessage)) { } @if (restEntities.Any()) {
Entità REST (@restEntities.Count disponibili):
@if (!string.IsNullOrEmpty(restSearchTerm)) { }
@if (!GetFilteredRestEntities().Any()) {
Nessuna entità trovata con il termine di ricerca "@restSearchTerm"
}
}
@if (isDatabaseConnected && isRestConnected && !string.IsNullOrEmpty(selectedTable) && selectedRestEntity != null) {
Mapping Campi
Mapping tra @selectedTable e @selectedRestEntity.Name

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

Campi Database (@selectedTable)
@if (databaseTables.ContainsKey(selectedTable)) { @foreach (var column in databaseTables[selectedTable]) {
@column.Name @column.DataType
@if (column.IsPrimaryKey) { PK } @if (fieldMappings.ContainsKey(column.Name)) { Mapped }
} }
Proprietà REST (@selectedRestEntity.Name)
@if (restEntityDetails != null) { @foreach (var property in restEntityDetails.Properties) {
@property.Name @property.Type
@if (property.IsRequired) { Required } @if (fieldMappings.ContainsValue(property.Name)) { Mapped }
} }
@if (fieldMappings.Any()) {
Mappature Correnti (@fieldMappings.Count)
@foreach (var mapping in fieldMappings) { var dbColumn = databaseTables[selectedTable].FirstOrDefault(c => c.Name == mapping.Key); var restProperty = restEntityDetails?.Properties.FirstOrDefault(p => p.Name == mapping.Value); }
Campo Database Tipo DB Proprietà REST Tipo REST Azioni
@mapping.Key @(dbColumn?.DataType ?? "Unknown") @mapping.Value @(restProperty?.Type ?? "Unknown")
}
@if (fieldMappings.Any()) { }
@if (fieldMappings.Any()) { @fieldMappings.Count mapping(s) configurati } else { Configura almeno una mappatura per iniziare }
@if (!string.IsNullOrEmpty(transferMessage)) { }
}
@code { // Stato delle credenziali private List databaseCredentials = new(); private List restApiCredentials = new(); // Credenziali selezionate private string selectedDatabaseCredential = ""; private string selectedRestCredential = ""; // Stato connessioni private bool isConnectingDatabase = false; private bool isConnectingRest = false; private bool isDatabaseConnected = false; private bool isRestConnected = false; // Messaggi di errore private string databaseErrorMessage = ""; private string restErrorMessage = ""; // Database discovery private Dictionary> databaseTables = new(); private string selectedTable = ""; private string databaseSearchTerm = ""; // REST discovery private List restEntities = new(); private RestEntitySummary? selectedRestEntity = null; private RestEntityInfo? restEntityDetails = null; private string restSearchTerm = ""; // Mapping campi private Dictionary fieldMappings = new(); // DbColumn -> RestProperty private string selectedDbColumn = ""; private string selectedRestProperty = ""; // Trasferimento dati private bool isTransferringData = false; private string transferMessage = ""; private string transferMessageType = ""; // Servizi private IDatabaseManager? currentDatabaseManager = null; private IRestMetadataDiscovery? currentRestDiscovery = null; private IRestServiceClient? currentRestClient = null; protected override async Task OnInitializedAsync() { await LoadCredentials(); } private async Task LoadCredentials() { try { databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync(); restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync(); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento delle credenziali"); await JSRuntime.InvokeVoidAsync("alert", $"Errore nel caricamento delle credenziali: {ex.Message}"); } } private void OnDatabaseCredentialChanged(ChangeEventArgs e) { selectedDatabaseCredential = e.Value?.ToString() ?? ""; ResetDatabaseState(); } private void OnRestCredentialChanged(ChangeEventArgs e) { var newCredential = e.Value?.ToString() ?? ""; // Clear the cache if we're switching to a different credential if (!string.IsNullOrEmpty(selectedRestCredential) && selectedRestCredential != newCredential) { ConnectionFactory.ClearRestClientCache(selectedRestCredential); Logger.LogInformation("Cleared REST client cache for credential: {CredentialName}", selectedRestCredential); } selectedRestCredential = newCredential; ResetRestState(); } private void ResetDatabaseState() { isDatabaseConnected = false; databaseTables.Clear(); selectedTable = ""; databaseSearchTerm = ""; databaseErrorMessage = ""; currentDatabaseManager?.Dispose(); currentDatabaseManager = null; // Clear mappings when resetting database state ClearAllMappings(); } private void ResetRestState() { isRestConnected = false; restEntities.Clear(); selectedRestEntity = null; restEntityDetails = null; restSearchTerm = ""; restErrorMessage = ""; currentRestDiscovery = null; currentRestClient = null; // Clear mappings when resetting REST state ClearAllMappings(); }private async Task ConnectToDatabase() { if (string.IsNullOrEmpty(selectedDatabaseCredential)) return; isConnectingDatabase = true; databaseErrorMessage = ""; try { // Trova la credenziale var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) { databaseErrorMessage = "Credenziale database non trovata"; return; } // Test della connessione var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); if (!success) { databaseErrorMessage = $"Connessione fallita: {message}"; return; } // Crea il database manager usando il factory con le credenziali complete currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); Logger.LogInformation("Iniziando discovery dello schema per database {DatabaseType} con credenziale: {CredentialName}", credential.DatabaseType, selectedDatabaseCredential); // Discovery dello schema var schema = await currentDatabaseManager.GetDatabaseSchemaAsync(); Logger.LogInformation("Schema discovery completato. Tipo restituito: {SchemaType}, Numero elementi: {Count}", schema?.GetType().Name ?? "null", schema?.Count() ?? 0); if (schema != null) { foreach (var item in schema.Take(5)) // Log primi 5 elementi per debug { Logger.LogInformation("Schema item - Key: {Key}, Value type: {ValueType}, Column count: {ColumnCount}", item.Key, item.Value?.GetType().Name ?? "null", item.Value?.Count() ?? 0); } } databaseTables = schema as Dictionary> ?? (schema != null ? new Dictionary>(schema) : new Dictionary>()); Logger.LogInformation("Database tables dopo conversione: {Count} tabelle", databaseTables.Count); isDatabaseConnected = true; } catch (Exception ex) { Logger.LogError(ex, "Errore nella connessione al database"); databaseErrorMessage = $"Errore: {ex.Message}"; } finally { isConnectingDatabase = false; } } private async Task ConnectToRestApi() { if (string.IsNullOrEmpty(selectedRestCredential)) return; isConnectingRest = true; restErrorMessage = ""; try { // Trova la credenziale var credential = restApiCredentials.FirstOrDefault(c => c.Name == selectedRestCredential); if (credential == null) { restErrorMessage = "Credenziale REST API non trovata"; return; } // Test della connessione var (success, message) = await CredentialService.TestRestApiConnectionAsync(credential.Name); if (!success) { restErrorMessage = $"Connessione fallita: {message}"; return; } // Crea i client REST usando il factory con le credenziali complete currentRestClient = await ConnectionFactory.CreateRestServiceClientAsync(selectedRestCredential); currentRestDiscovery = await ConnectionFactory.CreateRestMetadataDiscoveryAsync(selectedRestCredential); Logger.LogInformation("Iniziando autenticazione per il servizio REST {ServiceType} con credenziale: {CredentialName}", credential.ServiceType, selectedRestCredential); // Autenticazione prima del discovery var authResult = await currentRestClient.AuthenticateAsync(); if (!authResult) { Logger.LogWarning("Autenticazione fallita per il servizio REST {ServiceType}", credential.ServiceType); restErrorMessage = "Autenticazione fallita per il servizio REST"; return; } Logger.LogInformation("Autenticazione completata. Iniziando discovery delle entità REST per {ServiceType}", credential.ServiceType); // Discovery delle entità disponibili restEntities = await currentRestDiscovery.DiscoverEntitySummariesAsync(); Logger.LogInformation("Discovery completato. Trovate {Count} entità", restEntities?.Count ?? 0); if (restEntities == null || !restEntities.Any()) { Logger.LogWarning("Nessuna entità trovata dal servizio REST"); restErrorMessage = "Nessuna entità disponibile dal servizio REST"; return; } isRestConnected = true; } catch (Exception ex) { Logger.LogError(ex, "Errore nella connessione al servizio REST"); restErrorMessage = $"Errore: {ex.Message}"; } finally { isConnectingRest = false; } } private void SelectTable(string tableName) { selectedTable = tableName; // Clear mappings when changing table ClearAllMappings(); } private async Task SelectRestEntity(RestEntitySummary entity) { selectedRestEntity = entity; // Clear mappings when changing entity ClearAllMappings(); try { if (currentRestDiscovery != null) { // Discovery dei dettagli dell'entità restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name); } else { restErrorMessage = "Servizio di discovery REST non disponibile"; return; } } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name); restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}"; } } // Metodi per la ricerca e il filtraggio private IEnumerable GetFilteredDatabaseTables() { if (string.IsNullOrEmpty(databaseSearchTerm)) return databaseTables.Keys; return databaseTables.Keys.Where(table => table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase)); } private IEnumerable GetFilteredRestEntities() { if (string.IsNullOrEmpty(restSearchTerm)) return restEntities; return restEntities.Where(entity => entity.Name.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrEmpty(entity.Label) && entity.Label.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase))); } private async Task FilterDatabaseTables(ChangeEventArgs e) { databaseSearchTerm = e.Value?.ToString() ?? ""; await InvokeAsync(StateHasChanged); } private async Task FilterRestEntities(ChangeEventArgs e) { restSearchTerm = e.Value?.ToString() ?? ""; await InvokeAsync(StateHasChanged); } private async Task ClearDatabaseSearch() { databaseSearchTerm = ""; await InvokeAsync(StateHasChanged); } private async Task ClearRestSearch() { restSearchTerm = ""; await InvokeAsync(StateHasChanged); } // Metodi per il mapping dei campi private void SelectDbColumn(string columnName) { selectedDbColumn = columnName; } private void SelectRestProperty(string propertyName) { selectedRestProperty = propertyName; } private void CreateMapping() { if (string.IsNullOrEmpty(selectedDbColumn) || string.IsNullOrEmpty(selectedRestProperty)) return; // Rimuovi eventuali mapping esistenti per questo campo database if (fieldMappings.ContainsKey(selectedDbColumn)) { fieldMappings.Remove(selectedDbColumn); } // Crea il nuovo mapping fieldMappings[selectedDbColumn] = selectedRestProperty; Logger.LogInformation("Creato mapping: {DbColumn} -> {RestProperty}", selectedDbColumn, selectedRestProperty); // Deseleziona i campi selectedDbColumn = ""; selectedRestProperty = ""; } private void RemoveMapping() { if (string.IsNullOrEmpty(selectedDbColumn) || !fieldMappings.ContainsKey(selectedDbColumn)) return; fieldMappings.Remove(selectedDbColumn); Logger.LogInformation("Rimosso mapping per campo: {DbColumn}", selectedDbColumn); } private void RemoveSpecificMapping(string dbColumn) { if (fieldMappings.ContainsKey(dbColumn)) { fieldMappings.Remove(dbColumn); Logger.LogInformation("Rimosso mapping specifico per campo: {DbColumn}", dbColumn); } } private void ClearAllMappings() { fieldMappings.Clear(); selectedDbColumn = ""; selectedRestProperty = ""; transferMessage = ""; transferMessageType = ""; Logger.LogInformation("Tutti i mapping sono stati cancellati"); } private void AutoMapFields() { if (!databaseTables.ContainsKey(selectedTable) || restEntityDetails == null) return; var dbColumns = databaseTables[selectedTable]; var restProperties = restEntityDetails.Properties; int mappingsCreated = 0; foreach (var dbColumn in dbColumns) { // Trova una proprietà REST con nome simile var matchingProperty = restProperties.FirstOrDefault(p => string.Equals(p.Name, dbColumn.Name, StringComparison.OrdinalIgnoreCase) || string.Equals(p.Name.Replace("_", ""), dbColumn.Name.Replace("_", ""), StringComparison.OrdinalIgnoreCase) || string.Equals(p.Name.Replace("Id", ""), dbColumn.Name.Replace("Id", ""), StringComparison.OrdinalIgnoreCase) ); if (matchingProperty != null && !fieldMappings.ContainsKey(dbColumn.Name)) { fieldMappings[dbColumn.Name] = matchingProperty.Name; mappingsCreated++; } } Logger.LogInformation("Auto-mapping completato. Creati {Count} mapping automatici", mappingsCreated); } private async Task ShowMappingSummary() { var summary = "Riepilogo Mapping:\n\n"; foreach (var mapping in fieldMappings) { summary += $"• {mapping.Key} → {mapping.Value}\n"; } await JSRuntime.InvokeVoidAsync("alert", summary); } private async Task StartDataTransfer() { if (!fieldMappings.Any() || currentDatabaseManager == null || currentRestClient == null || selectedRestEntity == null) { transferMessage = "Configurazione incompleta. Assicurati di aver selezionato tabella, entità e configurato almeno una mappatura."; transferMessageType = "error"; return; } isTransferringData = true; transferMessage = ""; transferMessageType = ""; try { Logger.LogInformation("Iniziando trasferimento dati da {Table} a {Entity} con {MappingCount} mappature", selectedTable, 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); if (!records.Any()) { transferMessage = "Nessun record trovato nella tabella selezionata."; transferMessageType = "error"; return; } // 2. Trasforma e trasferisci ogni record int successCount = 0; int errorCount = 0; var errors = new List(); foreach (var record in records) { try { // Trasforma il record in base ai mapping var restData = TransformRecordToRestEntity(record); // Esegui upsert (crea o aggiorna) var result = await currentRestClient.UpsertEntityAsync(selectedRestEntity.Name, restData); if (result != null) { successCount++; Logger.LogDebug("Record trasferito con successo: {Data}", string.Join(", ", restData.Select(kvp => $"{kvp.Key}={kvp.Value}"))); } else { errorCount++; errors.Add($"Errore nel trasferimento del record (result null)"); } } catch (Exception ex) { errorCount++; errors.Add($"Errore nel trasferimento: {ex.Message}"); Logger.LogError(ex, "Errore nel trasferimento di un record"); } } // 3. Mostra risultati if (errorCount == 0) { transferMessage = $"Trasferimento completato con successo! {successCount} record trasferiti."; transferMessageType = "success"; } else { transferMessage = $"Trasferimento completato con errori. Successi: {successCount}, Errori: {errorCount}. Primi errori: {string.Join("; ", errors.Take(3))}"; transferMessageType = "error"; } Logger.LogInformation("Trasferimento completato. Successi: {SuccessCount}, Errori: {ErrorCount}", successCount, errorCount); } catch (Exception ex) { Logger.LogError(ex, "Errore generale nel trasferimento dati"); transferMessage = $"Errore nel trasferimento dati: {ex.Message}"; transferMessageType = "error"; } finally { isTransferringData = false; } } private async Task>> GetAllRecordsFromTable() { if (currentDatabaseManager == null || string.IsNullOrEmpty(selectedTable)) return new List>(); try { // Usa il database manager per eseguire una query che ottiene tutti i record // Questo è un esempio semplificato - potresti voler implementare paginazione per tabelle grandi return await currentDatabaseManager.GetAllRecordsAsync(selectedTable); } catch (Exception ex) { Logger.LogError(ex, "Errore nell'ottenere i record dalla tabella {Table}", selectedTable); throw; } } private Dictionary TransformRecordToRestEntity(Dictionary dbRecord) { var restData = new Dictionary(); foreach (var mapping in fieldMappings) { string dbColumn = mapping.Key; string restProperty = mapping.Value; if (dbRecord.ContainsKey(dbColumn)) { var value = dbRecord[dbColumn]; // Trasforma il valore se necessario (es. date format, null handling, etc.) var transformedValue = TransformValue(value, dbColumn, restProperty); if (transformedValue != null) { restData[restProperty] = transformedValue; } } } Logger.LogDebug("Record trasformato: {DbColumns} → {RestProperties}", string.Join(", ", dbRecord.Keys), string.Join(", ", restData.Keys)); return restData; } private object? TransformValue(object? value, string dbColumn, string restProperty) { if (value == null || value == DBNull.Value) return null; // Ottieni informazioni sui tipi per fare trasformazioni intelligenti var dbColumnInfo = databaseTables.ContainsKey(selectedTable) ? databaseTables[selectedTable].FirstOrDefault(c => c.Name == dbColumn) : null; var restPropertyInfo = restEntityDetails?.Properties.FirstOrDefault(p => p.Name == restProperty); // Trasformazioni specifiche per tipo if (restPropertyInfo != null) { switch (restPropertyInfo.Type.ToLower()) { case "edm.string": return value.ToString(); case "edm.int32": case "edm.int64": if (int.TryParse(value.ToString(), out int intVal)) return intVal; break; case "edm.decimal": case "edm.double": if (decimal.TryParse(value.ToString(), out decimal decVal)) return decVal; break; case "edm.boolean": if (bool.TryParse(value.ToString(), out bool boolVal)) return boolVal; // Gestisci anche valori numerici (0/1) come boolean if (value.ToString() == "1") return true; if (value.ToString() == "0") return false; break; case "edm.datetime": case "edm.datetimeoffset": if (DateTime.TryParse(value.ToString(), out DateTime dateVal)) return dateVal.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); break; } } // Fallback: restituisci il valore convertito a stringa return value.ToString(); } private string GetPropertyPlaceholder(RestPropertyInfo property) { return property.Type switch { "Edm.String" => $"Inserisci {property.Name}" + (property.MaxLength.HasValue ? $" (max {property.MaxLength})" : ""), "Edm.Int32" => "Numero intero", "Edm.Decimal" => "Numero decimale", "Edm.DateTime" => "Data/Ora (YYYY-MM-DD)", "Edm.Boolean" => "true/false", _ => $"Valore per {property.Name}" }; } public void Dispose() { currentDatabaseManager?.Dispose(); } }