e70abcdcb1
Aggiunge un connettore completamente managed per database Visual FoxPro e dBase, utilizzabile come sorgente dati in tutti i flussi dell'applicazione (discovery manuale, esecuzione schedulata, hash/change-detection, backup/restore). ## Motivazione Il provider OLE DB ufficiale (VFPOLEDB.1) è solo a 32-bit, deprecato e richiede installazione separata. Il connettore managed usa DbfDataReader 1.0.0 (MIT), legge direttamente i file .dbf/.fpt/.dbc a 64-bit senza dipendenze esterne e con streaming efficiente (tabelle da centinaia di MB con uso di memoria contenuto). ## Nuovi file - DataConnection/DB/FoxPro/FoxProConnectionInfo.cs Classe sealed con le informazioni di connessione risolte (cartella, percorso .dbc, encoding, flag IncludeDeleted). Proprietà di convenienza IsContainer e DisplayName. - DataConnection/DB/FoxPro/FoxProReader.cs Helper statico che registra CodePagesEncodingProvider, risolve la connection string (path diretto, stile OLE DB con Provider=vfpoledb, chiave=valore), verifica l'accesso al filesystem, legge il catalogo .dbc (il file .dbc è esso stesso un DBF; filtro su OBJECTTYPE='Table' + cross-check esistenza fisica .dbf), enumera le tabelle free, mappa i tipi VFP (Character, Memo, Numeric, Float, Double, Integer, Currency, Date, DateTime, Logical, General) in descrizioni leggibili, esegue streaming dei record via DbfDataReader, auto-rileva l'encoding dall'header DBF (language driver byte) con fallback a code page 1252, e analizza query SELECT [TOP n] * FROM tabella. - DataConnection/DB/FoxProDatabaseManager.cs Implementa IDatabaseManager. Lettura: TestConnectionAsync, GetTableNamesAsync, GetTableSchemaAsync, GetDatabaseSchemaAsync, GetAllRecordsAsync, ExecuteRawQueryAsync, GetPrimaryKeyFieldAsync (ritorna null, selezione manuale chiave), GetAvailableDatabasesAsync, ChangeDatabaseAsync (no-op per sorgenti file-based). Scrittura: tutti i metodi di modifica (Insert/Update/Delete/Upsert/BulkInsert/ ExecuteCommand/ExecuteNonQuery) lanciano NotSupportedException con messaggio esplicito. - DataConnection/DB/EF/SchemaProviders/FoxProSchemaProvider.cs Implementa IDatabaseSchemaProvider delegando a FoxProReader. Gestione errori per tabella in GetDatabaseSchemaAsync (una tabella non apribile non blocca la discovery). - FOXPRO_CONNECTION.md Guida utente: passo-passo per creare la credenziale, formato connection string, tabella tipi VFP supportati, note su sola lettura e limitazioni query, tabella risoluzione problemi (caratteri accentati, tabelle mancanti, ecc.). ## File modificati - DataConnection/DataConnection.csproj Aggiunto DbfDataReader 1.0.0 (porta transitivamente System.Text.Encoding.CodePages >= 10.0.3; rimossa dipendenza esplicita alla versione 9.0.3 che causava NU1605). - DataConnection/DB/Enums/DatabaseType.cs Aggiunto valore Foxpro in fondo all'enum (preserva valori già persistiti). - CredentialManager/Models/CredentialModels.cs Aggiunto DatabaseType.Foxpro all'enum e metodo BuildFoxproConnectionString che genera "Data Source=percorso[;CodePage=n][;IncludeDeleted=true]". - DataConnection/CredentialManagement/Models/CredentialExtensions.cs Mappatura bidirezionale Foxpro tra enum CredentialManager e DataConnection. - DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs Aggiunto TestFoxproConnection: verifica percorso accessibile, legge nomi tabelle, restituisce messaggio con conteggio tabelle, encoding rilevato e nota sola lettura. - DataConnection/DB/EF/DatabaseSchemaProviderFactory.cs Caso Foxpro => new FoxProSchemaProvider(). - Data_Coupler/Services/DataConnectionFactory.cs Branch Foxpro => new FoxProDatabaseManager(connectionString). - Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs Aggiunto IsFoxproConnection() e caso Foxpro in CreateLimitedQuery (SELECT TOP n * FROM (query) AS subquery). - CredentialManager/Integration/DataConnectionHelper.cs ValidateDatabaseCredential: introdotto flag isFileBased (Sqlite || Foxpro) per esonerare host/utente/password/porta; messaggio di errore specifico per il campo percorso FoxPro. - Data_Coupler/Pages/CredentialManagement.razor Aggiunta opzione "Visual FoxPro (.dbc / .dbf)" nel selettore tipo database. Sezione di configurazione dedicata: banner sola lettura, campo percorso con esempi (.dbc e cartella), dropdown code page (Auto/1252/1250/1251/850/437/65001), checkbox record cancellati, pannello tipi supportati, anteprima connection string. Fix validazione form test-connessione: branch Foxpro che verifica solo DatabaseName (non Host/Username/Password), eliminando il falso errore "Il campo Host è obbligatorio". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1203 lines
50 KiB
C#
1203 lines
50 KiB
C#
using CredentialManager.Models;
|
|
using CredentialManager.Services;
|
|
using CredentialManager.Integration;
|
|
using DataConnection.CredentialManagement.Interfaces;
|
|
using DataConnection.CredentialManagement.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Data.SqlClient;
|
|
using Microsoft.Data.Sqlite;
|
|
|
|
namespace DataConnection.CredentialManagement.Services;
|
|
|
|
/// <summary>
|
|
/// Servizio per l'integrazione delle credenziali con DataConnection
|
|
/// </summary>
|
|
public class DataConnectionCredentialService : IDataConnectionCredentialService
|
|
{
|
|
private readonly ICredentialService _credentialService;
|
|
private readonly IKeyAssociationService _keyAssociationService;
|
|
private readonly ILogger<DataConnectionCredentialService> _logger;
|
|
|
|
public DataConnectionCredentialService(
|
|
ICredentialService credentialService,
|
|
IKeyAssociationService keyAssociationService,
|
|
ILogger<DataConnectionCredentialService> logger)
|
|
{
|
|
_credentialService = credentialService;
|
|
_keyAssociationService = keyAssociationService;
|
|
_logger = logger;
|
|
}
|
|
|
|
#region Database Credentials
|
|
|
|
public async Task<DatabaseCredential?> GetDatabaseCredentialAsync(string name)
|
|
{
|
|
return await _credentialService.GetDatabaseCredentialAsync(name);
|
|
}
|
|
|
|
public async Task<DatabaseCredential?> GetDatabaseCredentialAsync(int id)
|
|
{
|
|
return await _credentialService.GetDatabaseCredentialAsync(id);
|
|
}
|
|
|
|
public async Task<List<DatabaseCredential>> GetAllDatabaseCredentialsAsync()
|
|
{
|
|
return await _credentialService.GetAllDatabaseCredentialsAsync();
|
|
}
|
|
|
|
public async Task<int> SaveDatabaseCredentialAsync(DatabaseCredential credential)
|
|
{
|
|
_logger.LogInformation("Saving database credential: {Name}", credential.Name);
|
|
return await _credentialService.SaveDatabaseCredentialAsync(credential);
|
|
}
|
|
|
|
public async Task<bool> DeleteDatabaseCredentialAsync(int id)
|
|
{
|
|
_logger.LogInformation("Deleting database credential with ID: {Id}", id);
|
|
return await _credentialService.DeleteCredentialAsync(id);
|
|
}
|
|
|
|
public async Task<bool> DeleteDatabaseCredentialAsync(string name)
|
|
{
|
|
_logger.LogInformation("Deleting database credential: {Name}", name);
|
|
return await _credentialService.DeleteCredentialAsync(name);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region REST API Credentials
|
|
|
|
public async Task<RestApiCredential?> GetRestApiCredentialAsync(string name)
|
|
{
|
|
return await _credentialService.GetRestApiCredentialAsync(name);
|
|
}
|
|
|
|
public async Task<RestApiCredential?> GetRestApiCredentialAsync(int id)
|
|
{
|
|
return await _credentialService.GetRestApiCredentialAsync(id);
|
|
}
|
|
|
|
public async Task<List<RestApiCredential>> GetAllRestApiCredentialsAsync()
|
|
{
|
|
return await _credentialService.GetAllRestApiCredentialsAsync();
|
|
}
|
|
|
|
public async Task<int> SaveRestApiCredentialAsync(RestApiCredential credential)
|
|
{
|
|
_logger.LogInformation("Saving REST API credential: {Name}", credential.Name);
|
|
return await _credentialService.SaveRestApiCredentialAsync(credential);
|
|
}
|
|
|
|
public async Task<bool> DeleteRestApiCredentialAsync(int id)
|
|
{
|
|
_logger.LogInformation("Deleting REST API credential with ID: {Id}", id);
|
|
return await _credentialService.DeleteCredentialAsync(id);
|
|
}
|
|
|
|
public async Task<bool> DeleteRestApiCredentialAsync(string name)
|
|
{
|
|
_logger.LogInformation("Deleting REST API credential: {Name}", name);
|
|
return await _credentialService.DeleteCredentialAsync(name);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DataConnection Specific Operations
|
|
|
|
public async Task<string> GetConnectionStringAsync(string credentialName)
|
|
{
|
|
var credential = await GetDatabaseCredentialAsync(credentialName);
|
|
if (credential == null)
|
|
throw new InvalidOperationException($"Database credential '{credentialName}' not found");
|
|
|
|
return credential.ToConnectionString();
|
|
}
|
|
|
|
public async Task<string> GetConnectionStringAsync(int credentialId)
|
|
{
|
|
var credential = await GetDatabaseCredentialAsync(credentialId);
|
|
if (credential == null)
|
|
throw new InvalidOperationException($"Database credential with ID '{credentialId}' not found"); return credential.ToConnectionString();
|
|
}
|
|
public async Task<DataConnection.EF.DbManagerOptions> GetDbManagerOptionsAsync(string credentialName)
|
|
{
|
|
var credential = await GetDatabaseCredentialAsync(credentialName);
|
|
if (credential == null)
|
|
throw new InvalidOperationException($"Database credential '{credentialName}' not found");
|
|
|
|
var options = new DataConnection.EF.DbManagerOptions
|
|
{
|
|
ServerConnectionString = credential.ToConnectionString(),
|
|
DatabaseName = credential.DatabaseName!,
|
|
DatabaseType = credential.DatabaseType.ToDataConnectionDatabaseType(),
|
|
CommandTimeout = credential.CommandTimeout
|
|
};
|
|
|
|
// Configura automaticamente il servizio di scoperta database
|
|
options.ConfigureDatabaseDiscovery(options.DatabaseType);
|
|
|
|
_logger.LogDebug("Created DbManagerOptions for credential: {Name} ({DatabaseType})",
|
|
credentialName, credential.DatabaseType);
|
|
|
|
return options;
|
|
}
|
|
|
|
public async Task<DataConnection.EF.DbManagerOptions> GetDbManagerOptionsAsync(int credentialId)
|
|
{
|
|
var credential = await GetDatabaseCredentialAsync(credentialId);
|
|
if (credential == null)
|
|
throw new InvalidOperationException($"Database credential with ID '{credentialId}' not found"); var options = new DataConnection.EF.DbManagerOptions
|
|
{
|
|
ServerConnectionString = credential.ToConnectionString(),
|
|
DatabaseName = credential.DatabaseName!,
|
|
DatabaseType = credential.DatabaseType.ToDataConnectionDatabaseType(),
|
|
CommandTimeout = credential.CommandTimeout
|
|
};
|
|
|
|
// Configura automaticamente il servizio di scoperta database
|
|
options.ConfigureDatabaseDiscovery(options.DatabaseType);
|
|
|
|
_logger.LogDebug("Created DbManagerOptions for credential ID: {Id} ({DatabaseType})",
|
|
credentialId, credential.DatabaseType);
|
|
return options;
|
|
}
|
|
|
|
public async Task<DataConnection.REST.Configuration.RestServiceOptions> GetRestServiceOptionsAsync(string credentialName)
|
|
{
|
|
var credential = await GetRestApiCredentialAsync(credentialName);
|
|
if (credential == null)
|
|
throw new InvalidOperationException($"REST API credential '{credentialName}' not found");
|
|
|
|
var options = BuildRestServiceOptions(credential);
|
|
|
|
_logger.LogDebug("Created RestServiceOptions for credential: {Name} ({BaseUrl})",
|
|
credentialName, credential.BaseUrl);
|
|
|
|
return options;
|
|
}
|
|
|
|
public async Task<DataConnection.REST.Configuration.RestServiceOptions> GetRestServiceOptionsAsync(int credentialId)
|
|
{
|
|
var credential = await GetRestApiCredentialAsync(credentialId);
|
|
if (credential == null)
|
|
throw new InvalidOperationException($"REST API credential with ID '{credentialId}' not found");
|
|
|
|
var options = BuildRestServiceOptions(credential);
|
|
|
|
_logger.LogDebug("Created RestServiceOptions for credential ID: {Id} ({BaseUrl})",
|
|
credentialId, credential.BaseUrl);
|
|
return options;
|
|
}
|
|
|
|
private static DataConnection.REST.Configuration.RestServiceOptions BuildRestServiceOptions(RestApiCredential credential)
|
|
{
|
|
var options = new DataConnection.REST.Configuration.RestServiceOptions
|
|
{
|
|
BaseUrl = credential.BaseUrl,
|
|
Username = credential.Username,
|
|
Password = credential.Password,
|
|
TimeoutSeconds = credential.TimeoutSeconds,
|
|
IgnoreSslErrors = credential.IgnoreSslErrors
|
|
};
|
|
|
|
// Mapping coerente con DataConnectionFactory.CreateRestServiceClientAsync
|
|
switch (credential.ServiceType)
|
|
{
|
|
case RestServiceType.Salesforce:
|
|
options.ApiKey = credential.ClientId;
|
|
options.AuthToken = credential.ClientSecret;
|
|
options.SalesforceGrantType = credential.GrantType;
|
|
break;
|
|
case RestServiceType.SapB1ServiceLayer:
|
|
options.ApiKey = credential.CompanyDatabase;
|
|
options.AuthToken = credential.AuthToken;
|
|
break;
|
|
default:
|
|
options.ApiKey = credential.ApiKey;
|
|
options.AuthToken = credential.AuthToken;
|
|
break;
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Connection Testing
|
|
|
|
public async Task<(bool Success, string Message)> TestDatabaseConnectionAsync(string credentialName)
|
|
{
|
|
try
|
|
{
|
|
var credential = await GetDatabaseCredentialAsync(credentialName);
|
|
if (credential == null)
|
|
return (false, $"Credenziale '{credentialName}' non trovata");
|
|
|
|
return await TestDatabaseConnectionAsync(credential);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione per credenziale: {Name}", credentialName);
|
|
return (false, $"Errore nel test: {ex.Message}");
|
|
}
|
|
}
|
|
public async Task<(bool Success, string Message)> TestDatabaseConnectionAsync(DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
var connectionString = ConnectionStringBuilder.BuildConnectionString(credential);
|
|
|
|
// Test base della sintassi
|
|
if (string.IsNullOrEmpty(connectionString))
|
|
return (false, "Connection string vuota o non valida");
|
|
|
|
_logger.LogInformation("Testing database connection for {Name} ({DatabaseType})",
|
|
credential.Name, credential.DatabaseType);
|
|
|
|
// Test di connessione diretto basato sul tipo di database
|
|
try
|
|
{
|
|
return credential.DatabaseType switch
|
|
{
|
|
CredentialManager.Models.DatabaseType.SqlServer => await TestSqlServerConnection(connectionString, credential),
|
|
CredentialManager.Models.DatabaseType.MySql => await TestMySqlConnection(connectionString, credential),
|
|
CredentialManager.Models.DatabaseType.PostgreSql => await TestPostgreSqlConnection(connectionString, credential),
|
|
CredentialManager.Models.DatabaseType.Oracle => await TestOracleConnection(connectionString, credential),
|
|
CredentialManager.Models.DatabaseType.Sqlite => await TestSqliteConnection(connectionString, credential),
|
|
CredentialManager.Models.DatabaseType.Odbc => await TestOdbcConnection(connectionString, credential),
|
|
CredentialManager.Models.DatabaseType.OleDb => await TestOleDbConnection(connectionString, credential),
|
|
CredentialManager.Models.DatabaseType.Foxpro => await TestFoxproConnection(connectionString, credential),
|
|
_ => (false, $"Test di connessione non implementato per {credential.DatabaseType}")
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Database connection test failed for {Name}", credential.Name);
|
|
return (false, $"Errore di connessione: {ex.Message}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione database");
|
|
return (false, $"Errore: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestSqlServerConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
using var connection = new Microsoft.Data.SqlClient.SqlConnection(connectionString);
|
|
connection.Open();
|
|
|
|
var command = connection.CreateCommand();
|
|
command.CommandText = "SELECT @@VERSION";
|
|
command.CommandTimeout = credential.CommandTimeout;
|
|
var version = await command.ExecuteScalarAsync();
|
|
|
|
return (true, $"Connessione SQL Server riuscita!\n\nDettagli:\n- Host: {credential.Host}:{credential.Port}\n- Database: {credential.DatabaseName ?? "Default"}\n- Versione: {version?.ToString()?.Split('\n')[0]}\n- Timeout: {credential.CommandTimeout}s");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"Errore SQL Server: {ex.Message}");
|
|
}
|
|
}
|
|
private Task<(bool Success, string Message)> TestMySqlConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
// Per MySQL, dovremmo usare MySql.Data.MySqlClient o Pomelo.EntityFrameworkCore.MySql
|
|
// Per ora returnamo un messaggio che indica che il test non è implementato
|
|
return Task.FromResult<(bool Success, string Message)>((false, "Test MySQL non implementato - installare MySql.Data.MySqlClient"));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Task.FromResult<(bool Success, string Message)>((false, $"Errore MySQL: {ex.Message}"));
|
|
}
|
|
}
|
|
|
|
private Task<(bool Success, string Message)> TestPostgreSqlConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
// Per PostgreSQL, dovremmo usare Npgsql
|
|
return Task.FromResult<(bool Success, string Message)>((false, "Test PostgreSQL non implementato - installare Npgsql"));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Task.FromResult<(bool Success, string Message)>((false, $"Errore PostgreSQL: {ex.Message}"));
|
|
}
|
|
}
|
|
|
|
private Task<(bool Success, string Message)> TestOracleConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
// Per Oracle, dovremmo usare Oracle.ManagedDataAccess
|
|
return Task.FromResult<(bool Success, string Message)>((false, "Test Oracle non implementato - installare Oracle.ManagedDataAccess"));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Task.FromResult<(bool Success, string Message)>((false, $"Errore Oracle: {ex.Message}"));
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestSqliteConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
using var connection = new Microsoft.Data.Sqlite.SqliteConnection(connectionString);
|
|
connection.Open();
|
|
|
|
var command = connection.CreateCommand();
|
|
command.CommandText = "SELECT sqlite_version()";
|
|
command.CommandTimeout = credential.CommandTimeout;
|
|
var version = await command.ExecuteScalarAsync();
|
|
|
|
return (true, $"Connessione SQLite riuscita!\n\nDettagli:\n- File: {credential.Host ?? connectionString}\n- Versione SQLite: {version}\n- Timeout: {credential.CommandTimeout}s");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"Errore SQLite: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestOdbcConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
using var connection = new System.Data.Odbc.OdbcConnection(connectionString);
|
|
await connection.OpenAsync();
|
|
|
|
// Non eseguiamo query di test perché alcuni database (come SAP HANA)
|
|
// hanno sintassi specifiche e potrebbero fallire anche con SELECT 1
|
|
// Ci limitiamo a testare l'apertura della connessione
|
|
|
|
var details = new System.Text.StringBuilder();
|
|
details.AppendLine("Connessione ODBC riuscita!");
|
|
details.AppendLine();
|
|
details.AppendLine("Dettagli:");
|
|
|
|
if (credential.OdbcMode == CredentialManager.Models.OdbcConnectionMode.Dsn && !string.IsNullOrEmpty(credential.OdbcDsnName))
|
|
{
|
|
details.AppendLine($"- DSN: {credential.OdbcDsnName}");
|
|
details.AppendLine($"- Tipo: {(credential.OdbcMode == CredentialManager.Models.OdbcConnectionMode.Dsn ? "DSN" : "Custom")}");
|
|
}
|
|
else
|
|
{
|
|
details.AppendLine($"- Modalità: Custom Connection String");
|
|
if (!string.IsNullOrEmpty(credential.Host))
|
|
details.AppendLine($"- Server: {credential.Host}" + (credential.Port > 0 ? $":{credential.Port}" : ""));
|
|
if (!string.IsNullOrEmpty(credential.DatabaseName))
|
|
details.AppendLine($"- Database: {credential.DatabaseName}");
|
|
}
|
|
|
|
details.AppendLine($"- Driver: {connection.Driver}");
|
|
details.AppendLine($"- Server Version: {connection.ServerVersion}");
|
|
details.AppendLine($"- Database: {connection.Database}");
|
|
details.AppendLine($"- Timeout: {credential.CommandTimeout}s");
|
|
|
|
return (true, details.ToString());
|
|
}
|
|
catch (System.Data.Odbc.OdbcException odbcEx)
|
|
{
|
|
var errorDetails = new System.Text.StringBuilder();
|
|
errorDetails.AppendLine($"Errore ODBC: {odbcEx.Message}");
|
|
errorDetails.AppendLine();
|
|
errorDetails.AppendLine("Dettagli errori:");
|
|
|
|
foreach (System.Data.Odbc.OdbcError error in odbcEx.Errors)
|
|
{
|
|
errorDetails.AppendLine($"- [{error.SQLState}] {error.Message}");
|
|
errorDetails.AppendLine($" Source: {error.Source}");
|
|
}
|
|
|
|
return (false, errorDetails.ToString());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"Errore ODBC: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestOleDbConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
using var connection = new System.Data.OleDb.OleDbConnection(connectionString);
|
|
await Task.Run(() => connection.Open());
|
|
|
|
var details = new System.Text.StringBuilder();
|
|
details.AppendLine("Connessione OLE DB stabilita con successo!");
|
|
details.AppendLine();
|
|
details.AppendLine("Dettagli:");
|
|
details.AppendLine($"- Provider: {connection.Provider}");
|
|
if (!string.IsNullOrEmpty(connection.Database))
|
|
details.AppendLine($"- Database: {connection.Database}");
|
|
if (!string.IsNullOrEmpty(credential.DatabaseName))
|
|
details.AppendLine($"- Data Source: {credential.DatabaseName}");
|
|
details.AppendLine($"- Timeout: {credential.CommandTimeout}s");
|
|
|
|
return (true, details.ToString());
|
|
}
|
|
catch (System.Data.OleDb.OleDbException oleDbEx)
|
|
{
|
|
var errorDetails = new System.Text.StringBuilder();
|
|
errorDetails.AppendLine($"Errore OLE DB: {oleDbEx.Message}");
|
|
errorDetails.AppendLine();
|
|
errorDetails.AppendLine("Dettagli errori:");
|
|
foreach (System.Data.OleDb.OleDbError error in oleDbEx.Errors)
|
|
{
|
|
errorDetails.AppendLine($"- [{error.SQLState}] {error.Message}");
|
|
errorDetails.AppendLine($" Source: {error.Source}");
|
|
}
|
|
|
|
return (false, errorDetails.ToString());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"Errore OLE DB: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestFoxproConnection(string connectionString, DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
return await Task.Run(() =>
|
|
{
|
|
var info = DataConnection.DB.FoxPro.FoxProReader.Resolve(connectionString);
|
|
DataConnection.DB.FoxPro.FoxProReader.EnsureReachable(info);
|
|
|
|
var tables = DataConnection.DB.FoxPro.FoxProReader.GetTableNames(info);
|
|
|
|
var details = new System.Text.StringBuilder();
|
|
details.AppendLine("Connessione Visual FoxPro riuscita! (lettura managed, 64-bit)");
|
|
details.AppendLine();
|
|
details.AppendLine("Dettagli:");
|
|
details.AppendLine(info.IsContainer
|
|
? $"- Database container: {info.DbcPath}"
|
|
: $"- Cartella tabelle libere: {info.Folder}");
|
|
details.AppendLine($"- Tabelle rilevate: {tables.Count}");
|
|
details.AppendLine($"- Encoding: code page {info.Encoding.CodePage}");
|
|
details.AppendLine($"- Record cancellati: {(info.IncludeDeleted ? "inclusi" : "esclusi")}");
|
|
details.AppendLine("- Modalità: sola lettura (sorgente dati)");
|
|
|
|
return tables.Count > 0
|
|
? (true, details.ToString())
|
|
: (false, $"Percorso raggiungibile ma nessuna tabella .dbf trovata in: {info.Folder}");
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"Errore Visual FoxPro: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Success, string Message)> TestRestApiConnectionAsync(string credentialName)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Attempting to test REST API connection for credential: {Name}", credentialName);
|
|
|
|
var credential = await GetRestApiCredentialAsync(credentialName);
|
|
if (credential == null)
|
|
{
|
|
_logger.LogWarning("REST API credential '{Name}' not found", credentialName);
|
|
return (false, $"Credenziale REST API '{credentialName}' non trovata");
|
|
}
|
|
|
|
_logger.LogInformation("Found credential: {Name}, ServiceType: {ServiceType}, BaseUrl: {BaseUrl}",
|
|
credential.Name, credential.ServiceType, credential.BaseUrl);
|
|
|
|
return await TestRestApiConnectionAsync(credential);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione REST API per credenziale: {Name}", credentialName);
|
|
return (false, $"Errore nel test: {ex.Message}");
|
|
}
|
|
}
|
|
public async Task<(bool Success, string Message)> TestRestApiConnectionAsync(RestApiCredential credential)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(credential.BaseUrl))
|
|
return (false, "Base URL non specificata");
|
|
|
|
// Test base dell'URL
|
|
if (!Uri.TryCreate(credential.BaseUrl, UriKind.Absolute, out var uri))
|
|
return (false, "Base URL non valida");
|
|
|
|
// Per i servizi specifici, eseguiamo test di autenticazione reali
|
|
switch (credential.ServiceType)
|
|
{
|
|
case RestServiceType.SapB1ServiceLayer:
|
|
return await TestSapB1ServiceLayerAuthentication(credential);
|
|
|
|
case RestServiceType.Salesforce:
|
|
return await TestSalesforceAuthentication(credential);
|
|
|
|
default:
|
|
// Per REST API generiche, facciamo un test di connettività base
|
|
return await TestGenericRestApiConnection(credential);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione REST API");
|
|
return (false, $"Errore: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestSapB1ServiceLayerAuthentication(RestApiCredential credential)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Testing SAP B1 Service Layer authentication for {Name} ({BaseUrl})",
|
|
credential.Name, credential.BaseUrl);
|
|
|
|
using var httpClient = new HttpClient();
|
|
httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds);
|
|
|
|
// Test di login al Service Layer
|
|
var loginUrl = credential.BaseUrl.TrimEnd('/') + "/Login";
|
|
|
|
var loginData = new
|
|
{
|
|
CompanyDB = credential.CompanyDatabase,
|
|
UserName = credential.Username,
|
|
Password = credential.Password,
|
|
Language = credential.Language ?? "en-US"
|
|
};
|
|
|
|
var loginJson = System.Text.Json.JsonSerializer.Serialize(loginData);
|
|
var loginContent = new StringContent(loginJson, System.Text.Encoding.UTF8, "application/json");
|
|
|
|
var response = await httpClient.PostAsync(loginUrl, loginContent);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
return (true, $"Autenticazione SAP B1 Service Layer riuscita!\n\nDettagli:\n- Server: {credential.BaseUrl}\n- Company DB: {credential.CompanyDatabase}\n- Versione: {credential.Version}\n- Lingua: {credential.Language}\n- Timeout: {credential.TimeoutSeconds}s");
|
|
}
|
|
else
|
|
{
|
|
var errorContent = await response.Content.ReadAsStringAsync();
|
|
return (false, $"Autenticazione SAP B1 fallita. Status: {response.StatusCode}\nDettagli: {errorContent}");
|
|
}
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
return (false, $"Errore di rete SAP B1: {ex.Message}");
|
|
}
|
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout"))
|
|
{
|
|
return (false, $"Timeout della connessione SAP B1 ({credential.TimeoutSeconds}s)");
|
|
}
|
|
}
|
|
private async Task<(bool Success, string Message)> TestSalesforceAuthentication(RestApiCredential credential)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Testing Salesforce authentication for {Name} ({BaseUrl}, GrantType={GrantType})",
|
|
credential.Name, credential.BaseUrl, credential.GrantType);
|
|
|
|
_logger.LogDebug("Salesforce credential details: Username={Username}, HasPassword={HasPassword}, HasSecurityToken={HasSecurityToken}, HasClientId={HasClientId}, HasClientSecret={HasClientSecret}",
|
|
credential.Username, !string.IsNullOrEmpty(credential.Password), !string.IsNullOrEmpty(credential.SecurityToken),
|
|
!string.IsNullOrEmpty(credential.ClientId), !string.IsNullOrEmpty(credential.ClientSecret));
|
|
|
|
using var httpClient = new HttpClient();
|
|
httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds);
|
|
|
|
var tokenUrl = credential.BaseUrl.TrimEnd('/') + "/services/oauth2/token";
|
|
List<KeyValuePair<string, string>> tokenData;
|
|
string flowLabel;
|
|
|
|
if (credential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials)
|
|
{
|
|
// Client Credentials flow — server-to-server, no user
|
|
if (string.IsNullOrEmpty(credential.ClientId) || string.IsNullOrEmpty(credential.ClientSecret))
|
|
{
|
|
return (false, "Flusso client_credentials richiede ClientId e ClientSecret configurati.");
|
|
}
|
|
|
|
tokenData = new List<KeyValuePair<string, string>>
|
|
{
|
|
new("grant_type", "client_credentials"),
|
|
new("client_id", credential.ClientId),
|
|
new("client_secret", credential.ClientSecret)
|
|
};
|
|
flowLabel = "client_credentials";
|
|
}
|
|
else
|
|
{
|
|
// Password flow (default)
|
|
var password = credential.Password ?? "";
|
|
if (!string.IsNullOrEmpty(credential.SecurityToken))
|
|
{
|
|
password += credential.SecurityToken;
|
|
}
|
|
|
|
tokenData = new List<KeyValuePair<string, string>>
|
|
{
|
|
new("grant_type", "password"),
|
|
new("username", credential.Username ?? ""),
|
|
new("password", password)
|
|
};
|
|
|
|
if (!string.IsNullOrEmpty(credential.ClientId))
|
|
{
|
|
tokenData.Add(new("client_id", credential.ClientId));
|
|
}
|
|
if (!string.IsNullOrEmpty(credential.ClientSecret))
|
|
{
|
|
tokenData.Add(new("client_secret", credential.ClientSecret));
|
|
}
|
|
flowLabel = "password";
|
|
}
|
|
|
|
_logger.LogDebug("Posting to Salesforce token URL: {TokenUrl} (flow={Flow})", tokenUrl, flowLabel);
|
|
|
|
var tokenContent = new FormUrlEncodedContent(tokenData);
|
|
var response = await httpClient.PostAsync(tokenUrl, tokenContent);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
_logger.LogInformation("Salesforce authentication ({Flow}) successful for {Name}", flowLabel, credential.Name);
|
|
return (true, $"Autenticazione Salesforce riuscita!\n\nDettagli:\n- Login URL: {credential.BaseUrl}\n- API Version: {credential.ApiVersion}\n- Sandbox: {credential.IsSandbox}\n- Tipo Auth: OAuth2 ({flowLabel})\n- Timeout: {credential.TimeoutSeconds}s");
|
|
}
|
|
else
|
|
{
|
|
var errorContent = await response.Content.ReadAsStringAsync();
|
|
_logger.LogWarning("Salesforce authentication ({Flow}) failed for {Name}. Status: {StatusCode}, Response: {Response}",
|
|
flowLabel, credential.Name, response.StatusCode, errorContent);
|
|
return (false, $"Autenticazione Salesforce ({flowLabel}) fallita. Status: {response.StatusCode}\nDettagli: {errorContent}");
|
|
}
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
_logger.LogError(ex, "Network error during Salesforce authentication for {Name}", credential.Name);
|
|
return (false, $"Errore di rete Salesforce: {ex.Message}");
|
|
}
|
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout"))
|
|
{
|
|
_logger.LogWarning("Timeout during Salesforce authentication for {Name} ({TimeoutSeconds}s)", credential.Name, credential.TimeoutSeconds);
|
|
return (false, $"Timeout della connessione Salesforce ({credential.TimeoutSeconds}s)");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected error during Salesforce authentication for {Name}", credential.Name);
|
|
return (false, $"Errore imprevisto: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestGenericRestApiConnection(RestApiCredential credential)
|
|
{
|
|
try
|
|
{
|
|
// Test di connessione reale per REST API generiche
|
|
using var httpClient = new HttpClient();
|
|
|
|
// Configurazione del timeout
|
|
httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds);
|
|
|
|
// Configurazione autenticazione
|
|
if (!string.IsNullOrEmpty(credential.ApiKey))
|
|
{
|
|
httpClient.DefaultRequestHeaders.Add("X-API-Key", credential.ApiKey);
|
|
}
|
|
else if (!string.IsNullOrEmpty(credential.AuthToken))
|
|
{
|
|
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {credential.AuthToken}");
|
|
}
|
|
else if (!string.IsNullOrEmpty(credential.Username) && !string.IsNullOrEmpty(credential.Password))
|
|
{
|
|
var authValue = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{credential.Username}:{credential.Password}"));
|
|
httpClient.DefaultRequestHeaders.Add("Authorization", $"Basic {authValue}");
|
|
}
|
|
|
|
_logger.LogInformation("Testing generic REST API connection for {Name} ({BaseUrl})",
|
|
credential.Name, credential.BaseUrl);
|
|
|
|
// Facciamo una richiesta HEAD o GET semplice per testare la connettività
|
|
var testEndpoint = credential.BaseUrl.TrimEnd('/') + "/";
|
|
|
|
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, testEndpoint));
|
|
|
|
// Anche un 404 o 401 indica che il server è raggiungibile
|
|
var isReachable = response.StatusCode != System.Net.HttpStatusCode.RequestTimeout &&
|
|
!IsNetworkError(response.StatusCode);
|
|
|
|
if (isReachable)
|
|
{
|
|
return (true, $"Connessione REST API riuscita!\n\nDettagli:\n- URL: {credential.BaseUrl}\n- Status: {response.StatusCode}\n- Timeout: {credential.TimeoutSeconds}s\n- Auth: {GetAuthType(credential)}");
|
|
}
|
|
else
|
|
{
|
|
return (false, $"Connessione fallita. Status: {response.StatusCode}\nVerifica URL e configurazione di rete.");
|
|
}
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
_logger.LogError(ex, "Generic REST API connection test failed for {Name}", credential.Name);
|
|
return (false, $"Errore di rete: {ex.Message}");
|
|
}
|
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout"))
|
|
{
|
|
return (false, $"Timeout della connessione ({credential.TimeoutSeconds}s). Il server potrebbe essere irraggiungibile.");
|
|
}
|
|
}
|
|
|
|
private static bool IsNetworkError(System.Net.HttpStatusCode statusCode)
|
|
{
|
|
return statusCode == System.Net.HttpStatusCode.RequestTimeout ||
|
|
statusCode == System.Net.HttpStatusCode.BadGateway ||
|
|
statusCode == System.Net.HttpStatusCode.ServiceUnavailable ||
|
|
statusCode == System.Net.HttpStatusCode.GatewayTimeout;
|
|
}
|
|
|
|
private static string GetAuthType(RestApiCredential credential)
|
|
{
|
|
if (!string.IsNullOrEmpty(credential.ApiKey))
|
|
return "API Key";
|
|
if (!string.IsNullOrEmpty(credential.AuthToken))
|
|
return "Auth Token";
|
|
if (!string.IsNullOrEmpty(credential.Username))
|
|
return "Basic Auth";
|
|
return "Nessuna";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SAP B1 Service Layer Credentials
|
|
|
|
public async Task<SapB1ServiceLayerCredential?> GetSapB1CredentialAsync(string name)
|
|
{
|
|
return await _credentialService.GetSapB1CredentialAsync(name);
|
|
}
|
|
|
|
public async Task<SapB1ServiceLayerCredential?> GetSapB1CredentialAsync(int id)
|
|
{
|
|
return await _credentialService.GetSapB1CredentialAsync(id);
|
|
}
|
|
|
|
public async Task<List<SapB1ServiceLayerCredential>> GetAllSapB1CredentialsAsync()
|
|
{
|
|
return await _credentialService.GetAllSapB1CredentialsAsync();
|
|
}
|
|
|
|
public async Task<int> SaveSapB1CredentialAsync(SapB1ServiceLayerCredential credential)
|
|
{
|
|
_logger.LogInformation("Saving SAP B1 Service Layer credential: {Name}", credential.Name);
|
|
return await _credentialService.SaveSapB1CredentialAsync(credential);
|
|
}
|
|
|
|
public async Task<bool> DeleteSapB1CredentialAsync(int id)
|
|
{
|
|
_logger.LogInformation("Deleting SAP B1 Service Layer credential with ID: {Id}", id);
|
|
return await _credentialService.DeleteCredentialAsync(id);
|
|
}
|
|
|
|
public async Task<bool> DeleteSapB1CredentialAsync(string name)
|
|
{
|
|
_logger.LogInformation("Deleting SAP B1 Service Layer credential: {Name}", name);
|
|
return await _credentialService.DeleteCredentialAsync(name);
|
|
}
|
|
|
|
public async Task<(bool Success, string Message)> TestSapB1ConnectionAsync(string credentialName)
|
|
{
|
|
try
|
|
{
|
|
var credential = await GetSapB1CredentialAsync(credentialName);
|
|
if (credential == null)
|
|
return (false, $"Credenziale SAP B1 '{credentialName}' non trovata");
|
|
|
|
return await TestSapB1ConnectionAsync(credential);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione SAP B1 per credenziale: {Name}", credentialName);
|
|
return (false, $"Errore nel test: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Success, string Message)> TestSapB1ConnectionAsync(SapB1ServiceLayerCredential credential)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(credential.ServerUrl))
|
|
return (false, "Server URL non specificato");
|
|
|
|
if (!Uri.TryCreate(credential.ServerUrl, UriKind.Absolute, out var uri))
|
|
return (false, "Server URL non valido");
|
|
|
|
_logger.LogInformation("Testing SAP B1 Service Layer connection for {Name} ({ServerUrl})",
|
|
credential.Name, credential.ServerUrl);
|
|
|
|
using var httpClient = new HttpClient();
|
|
httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds);
|
|
|
|
// Test di accesso al Service Layer
|
|
var loginUrl = credential.ServerUrl.TrimEnd('/') + "/Login";
|
|
|
|
var loginData = new
|
|
{
|
|
CompanyDB = credential.CompanyDatabase,
|
|
UserName = credential.Username,
|
|
Password = credential.Password,
|
|
Language = credential.Language ?? "en-US"
|
|
};
|
|
|
|
var loginJson = System.Text.Json.JsonSerializer.Serialize(loginData);
|
|
var loginContent = new StringContent(loginJson, System.Text.Encoding.UTF8, "application/json");
|
|
|
|
try
|
|
{
|
|
var response = await httpClient.PostAsync(loginUrl, loginContent);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
return (true, $"Connessione SAP B1 Service Layer riuscita!\n\nDettagli:\n- Server: {credential.ServerUrl}\n- Company DB: {credential.CompanyDatabase}\n- Versione: {credential.Version}\n- Lingua: {credential.Language}\n- Timeout: {credential.TimeoutSeconds}s");
|
|
}
|
|
else
|
|
{
|
|
var errorContent = await response.Content.ReadAsStringAsync();
|
|
return (false, $"Login fallito. Status: {response.StatusCode}\nDettagli: {errorContent}");
|
|
}
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
return (false, $"Errore di rete SAP B1: {ex.Message}");
|
|
}
|
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout"))
|
|
{
|
|
return (false, $"Timeout della connessione SAP B1 ({credential.TimeoutSeconds}s).");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione SAP B1");
|
|
return (false, $"Errore: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Salesforce Credentials
|
|
|
|
public async Task<SalesforceCredential?> GetSalesforceCredentialAsync(string name)
|
|
{
|
|
return await _credentialService.GetSalesforceCredentialAsync(name);
|
|
}
|
|
|
|
public async Task<SalesforceCredential?> GetSalesforceCredentialAsync(int id)
|
|
{
|
|
return await _credentialService.GetSalesforceCredentialAsync(id);
|
|
}
|
|
|
|
public async Task<List<SalesforceCredential>> GetAllSalesforceCredentialsAsync()
|
|
{
|
|
return await _credentialService.GetAllSalesforceCredentialsAsync();
|
|
}
|
|
|
|
public async Task<int> SaveSalesforceCredentialAsync(SalesforceCredential credential)
|
|
{
|
|
_logger.LogInformation("Saving Salesforce credential: {Name}", credential.Name);
|
|
return await _credentialService.SaveSalesforceCredentialAsync(credential);
|
|
}
|
|
|
|
public async Task<bool> DeleteSalesforceCredentialAsync(int id)
|
|
{
|
|
_logger.LogInformation("Deleting Salesforce credential with ID: {Id}", id);
|
|
return await _credentialService.DeleteCredentialAsync(id);
|
|
}
|
|
|
|
public async Task<bool> DeleteSalesforceCredentialAsync(string name)
|
|
{
|
|
_logger.LogInformation("Deleting Salesforce credential: {Name}", name);
|
|
return await _credentialService.DeleteCredentialAsync(name);
|
|
}
|
|
|
|
public async Task<(bool Success, string Message)> TestSalesforceConnectionAsync(string credentialName)
|
|
{
|
|
try
|
|
{
|
|
var credential = await GetSalesforceCredentialAsync(credentialName);
|
|
if (credential == null)
|
|
return (false, $"Credenziale Salesforce '{credentialName}' non trovata");
|
|
|
|
return await TestSalesforceConnectionAsync(credential);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione Salesforce per credenziale: {Name}", credentialName);
|
|
return (false, $"Errore nel test: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Success, string Message)> TestSalesforceConnectionAsync(SalesforceCredential credential)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(credential.LoginUrl))
|
|
return (false, "Login URL non specificato");
|
|
|
|
if (!Uri.TryCreate(credential.LoginUrl, UriKind.Absolute, out var uri))
|
|
return (false, "Login URL non valido");
|
|
|
|
_logger.LogInformation("Testing Salesforce connection for {Name} ({LoginUrl})",
|
|
credential.Name, credential.LoginUrl);
|
|
|
|
using var httpClient = new HttpClient();
|
|
httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds);
|
|
|
|
// Test di login SOAP/REST
|
|
if (credential.UseSoapApi)
|
|
{
|
|
return await TestSalesforceSoapLogin(httpClient, credential);
|
|
}
|
|
else
|
|
{
|
|
return await TestSalesforceOAuthLogin(httpClient, credential);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nel test della connessione Salesforce");
|
|
return (false, $"Errore: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestSalesforceOAuthLogin(HttpClient httpClient, SalesforceCredential credential)
|
|
{
|
|
try
|
|
{
|
|
var tokenUrl = credential.LoginUrl.TrimEnd('/') + "/services/oauth2/token";
|
|
List<KeyValuePair<string, string>> tokenData;
|
|
|
|
if (credential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials)
|
|
{
|
|
// Client Credentials flow — server-to-server, no user
|
|
tokenData = new List<KeyValuePair<string, string>>
|
|
{
|
|
new("grant_type", "client_credentials"),
|
|
new("client_id", credential.ClientId ?? ""),
|
|
new("client_secret", credential.ClientSecret ?? "")
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// Password flow (default)
|
|
tokenData = new List<KeyValuePair<string, string>>
|
|
{
|
|
new("grant_type", "password"),
|
|
new("username", credential.Username),
|
|
new("password", credential.Password + credential.SecurityToken),
|
|
new("client_id", credential.ClientId ?? ""),
|
|
new("client_secret", credential.ClientSecret ?? "")
|
|
};
|
|
}
|
|
|
|
var tokenContent = new FormUrlEncodedContent(tokenData);
|
|
var response = await httpClient.PostAsync(tokenUrl, tokenContent);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var flowLabel = credential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials
|
|
? "client_credentials" : "password";
|
|
return (true, $"Connessione Salesforce riuscita!\n\nDettagli:\n- Login URL: {credential.LoginUrl}\n- API Version: {credential.ApiVersion}\n- Sandbox: {credential.IsSandbox}\n- Tipo Auth: OAuth2 ({flowLabel})\n- Timeout: {credential.TimeoutSeconds}s");
|
|
}
|
|
else
|
|
{
|
|
var errorContent = await response.Content.ReadAsStringAsync();
|
|
return (false, $"Login OAuth Salesforce fallito. Status: {response.StatusCode}\nDettagli: {errorContent}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"Errore OAuth Salesforce: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<(bool Success, string Message)> TestSalesforceSoapLogin(HttpClient httpClient, SalesforceCredential credential)
|
|
{
|
|
try
|
|
{
|
|
// Test semplificato per SOAP - verifica solo la raggiungibilità del servizio
|
|
var soapUrl = credential.LoginUrl.TrimEnd('/') + "/services/Soap/u/" + credential.ApiVersion;
|
|
|
|
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, soapUrl));
|
|
|
|
if (response.IsSuccessStatusCode || response.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
|
|
{
|
|
return (true, $"Connessione Salesforce SOAP riuscita!\n\nDettagli:\n- SOAP URL: {soapUrl}\n- API Version: {credential.ApiVersion}\n- Sandbox: {credential.IsSandbox}\n- Tipo Auth: SOAP\n- Timeout: {credential.TimeoutSeconds}s");
|
|
}
|
|
else
|
|
{
|
|
return (false, $"Servizio SOAP Salesforce non raggiungibile. Status: {response.StatusCode}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"Errore SOAP Salesforce: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Key Associations
|
|
|
|
public async Task<int> SaveKeyAssociationAsync(KeyAssociation association)
|
|
{
|
|
return await _keyAssociationService.SaveAssociationAsync(association);
|
|
}
|
|
|
|
public async Task<int> SaveKeyAssociationParallelAsync(KeyAssociation association)
|
|
{
|
|
return await _keyAssociationService.SaveAssociationParallelAsync(association);
|
|
}
|
|
|
|
public async Task<KeyAssociation?> FindKeyAssociationByValueAsync(string keyValue, string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.FindAssociationByKeyValueAsync(keyValue, destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<KeyAssociation?> FindKeyAssociationByValueAsync(string keyValue)
|
|
{
|
|
return await _keyAssociationService.FindAssociationByKeyValueAsync(keyValue);
|
|
}
|
|
|
|
public async Task<List<KeyAssociation>> GetKeyAssociationsByDestinationAsync(string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.GetAssociationsByDestinationAsync(destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<List<KeyAssociation>> GetAllActiveKeyAssociationsAsync()
|
|
{
|
|
return await _keyAssociationService.GetAllActiveAssociationsAsync();
|
|
}
|
|
|
|
public async Task<List<KeyAssociation>> GetAllKeyAssociationsAsync()
|
|
{
|
|
return await _keyAssociationService.GetAllAssociationsAsync();
|
|
}
|
|
|
|
public async Task<bool> UpdateKeyAssociationAsync(KeyAssociation association)
|
|
{
|
|
return await _keyAssociationService.UpdateAssociationAsync(association);
|
|
}
|
|
|
|
public async Task<bool> DeactivateKeyAssociationAsync(int id)
|
|
{
|
|
return await _keyAssociationService.DeactivateAssociationAsync(id);
|
|
}
|
|
|
|
public async Task<bool> DeleteKeyAssociationAsync(int id)
|
|
{
|
|
return await _keyAssociationService.DeleteAssociationAsync(id);
|
|
}
|
|
|
|
public async Task<int> ClearKeyAssociationsAsync(string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.ClearAssociationsAsync(destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<int> ClearAllKeyAssociationsAsync()
|
|
{
|
|
return await _keyAssociationService.ClearAllAssociationsAsync();
|
|
}
|
|
|
|
public async Task<List<KeyAssociation>> GetInvalidKeyAssociationsAsync(string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.GetInvalidAssociationsAsync(destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<int> CleanupInvalidKeyAssociationsAsync(string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.CleanupInvalidAssociationsAsync(destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<bool> UpdateKeyAssociationLastVerifiedAsync(int id)
|
|
{
|
|
return await _keyAssociationService.UpdateLastVerifiedAsync(id);
|
|
}
|
|
|
|
public async Task<AssociationStatistics> GetKeyAssociationStatisticsAsync()
|
|
{
|
|
return await _keyAssociationService.GetStatisticsAsync();
|
|
}
|
|
|
|
// Parallel key association operations
|
|
public async Task<bool> SaveKeyAssociationParallelAsync(string keyValue, string destinationEntity, string destinationId, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.SaveAssociationParallelAsync(keyValue, destinationEntity, destinationId, restCredentialName);
|
|
}
|
|
|
|
public async Task<KeyAssociation?> FindKeyAssociationByValueParallelAsync(string keyValue, string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.FindAssociationByKeyValueParallelAsync(keyValue, destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<KeyAssociation?> FindKeyAssociationByValueParallelAsync(string keyValue)
|
|
{
|
|
return await _keyAssociationService.FindAssociationByKeyValueParallelAsync(keyValue);
|
|
}
|
|
|
|
public async Task<Dictionary<string, KeyAssociation>> FindKeyAssociationsByValuesBulkAsync(
|
|
IEnumerable<string> keyValues,
|
|
string destinationEntity,
|
|
string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.FindAssociationsByKeyValuesBulkAsync(keyValues, destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<bool> DeleteKeyAssociationParallelAsync(int id)
|
|
{
|
|
return await _keyAssociationService.DeleteAssociationParallelAsync(id);
|
|
}
|
|
|
|
// Deletion synchronization operations
|
|
public async Task<int> MarkDeletedAssociationsAsync(List<string> sourceKeyValues, string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.MarkDeletedAssociationsAsync(sourceKeyValues, destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<List<KeyAssociation>> GetPendingDeletionsAsync(string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.GetPendingDeletionsAsync(destinationEntity, restCredentialName);
|
|
}
|
|
|
|
public async Task<bool> MarkDeletionSyncedAsync(int associationId)
|
|
{
|
|
return await _keyAssociationService.MarkDeletionSyncedAsync(associationId);
|
|
}
|
|
|
|
public async Task<List<KeyAssociation>> GetDeletedAssociationsAsync(string destinationEntity, string restCredentialName)
|
|
{
|
|
return await _keyAssociationService.GetDeletedAssociationsAsync(destinationEntity, restCredentialName);
|
|
}
|
|
|
|
#region Helper Methods
|
|
|
|
public async Task<int?> GetCredentialIdByNameAsync(string name, CredentialManager.Models.CredentialType type)
|
|
{
|
|
return await _credentialService.GetCredentialIdByNameAsync(name, type);
|
|
}
|
|
|
|
public async Task<bool> DeleteCredentialCascadeAsync(string name)
|
|
{
|
|
_logger.LogInformation("Deleting credential cascade by name: {Name}", name);
|
|
return await _credentialService.DeleteCredentialCascadeAsync(name);
|
|
}
|
|
|
|
public async Task<bool> DeleteCredentialCascadeAsync(int id)
|
|
{
|
|
_logger.LogInformation("Deleting credential cascade by ID: {Id}", id);
|
|
return await _credentialService.DeleteCredentialCascadeAsync(id);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
}
|