Files
Data-Coupler/Data_Coupler/Services/DataConnectionFactory.cs
T
Alessio 82e0d6bc77 [Feature] Aggiunto supporto completo OLE DB per connessione database
## Nuovi file
- DataConnection/DB/OleDbDatabaseManager.cs: Manager completo per connessioni OLE DB
  con supporto Task.Run() per operazioni sincrone, parametri posizionali '?',
  guard per piattaforma Windows, nessun supporto ChangeDatabaseAsync (no-op)
- DataConnection/DB/EF/SchemaProviders/OleDbSchemaProvider.cs: Schema provider per
  OLE DB, usa OleDbSchemaGuid per tabelle/colonne/chiavi primarie, mapping tipi dati
- CredentialManager/Services/OleDbProviderDiscoveryService.cs: Servizio di discovery
  provider OLE DB installati tramite registro Windows (HKEY_CLASSES_ROOT). Rileva
  9 provider noti: VFPOLEDB.1, Microsoft.ACE.OLEDB.12.0, Jet 4.0, SQLOLEDB, ecc.
  Mostra warning per provider solo 32-bit (VFPOLEDB, Jet)
- PUBLISH_32BIT_64BIT.md: Documentazione completa comandi pubblicazione per
  win-x64, win-x86 (richiesto per VFP), linux-x64, osx-x64, osx-arm64.
  Include prerequisiti VFPOLEDB, esempi connection string VFP, note Docker

## File modificati
- DataConnection/DB/Enums/DatabaseType.cs: Aggiunto valore OleDb dopo Odbc
- DataConnection/DataConnection.csproj: Aggiunto pacchetto System.Data.OleDb 9.0.3
- DataConnection/DB/OdbcDatabaseManager.cs: Fix bug ChangeDatabaseAsync con try-catch
- CredentialManager/Models/CredentialModels.cs: Aggiunto OleDb all'enum DatabaseType,
  BuildOleDbConnectionString() con supporto provider da AdditionalParameters,
  default VFPOLEDB.1, costruzione connection string con parametri VFP
- DataConnection/CredentialManagement/Models/CredentialExtensions.cs: Mappatura
  OleDb in ToDataConnectionDatabaseType() e ToCredentialDatabaseType()
- DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs:
  Aggiunto case OleDb in TestDatabaseConnectionAsync e metodo TestOleDbConnection()
  con apertura connessione via Task.Run() e gestione OleDbException dettagliata
- Data_Coupler/Services/DataConnectionFactory.cs: Aggiunto case OleDb per creazione
  OleDbDatabaseManager
- Data_Coupler/Program.cs: Registrazione IOleDbProviderDiscoveryService come Scoped
- Data_Coupler/Pages/CredentialManagement.razor: Aggiunta UI completa OLE DB con
  sezione dedicata Visual FoxPro (percorso .dbc/.dbf, Collating Sequence, DELETED),
  provider discovery con refresh, anteprima connection string, variabili di stato
  e metodi nel codice Blazor, sincronizzazione AdditionalParameters al salvataggio

## Compatibilità
- VFP 8.0/9.0: testato con VFPOLEDB.1, connessione file-based .dbc e .dbf
- Richiede pubblicazione win-x86 per driver OLE DB 32-bit
- AnyCPU non supportato per VFP (COM 32-bit)
2026-05-25 21:20:08 +02:00

264 lines
13 KiB
C#

using CredentialManager.Models;
using DataConnection.EF;
using DataConnection.Interfaces;
using DataConnection.REST.Interfaces;
using DataConnection.REST.Implementations;
using DataConnection.REST.Configuration;
using DataConnection.CredentialManagement.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Security.Cryptography;
using System.Text;
namespace Data_Coupler.Services
{
/// <summary>
/// Classe per tracciare i client REST cached con le loro credenziali
/// </summary>
internal class CachedRestClient
{
public IRestServiceClient Client { get; set; } = null!;
public string CredentialsHash { get; set; } = string.Empty;
public DateTime CachedAt { get; set; }
}
/// <summary>
/// Factory service per creare istanze di DatabaseManager e RestClient
/// </summary>
public interface IDataConnectionFactory
{
/// <summary>
/// Crea un DatabaseManager per la credenziale specificata
/// </summary>
Task<IDatabaseManager> CreateDatabaseManagerAsync(string credentialName);
/// <summary>
/// Crea un RestServiceClient per la credenziale specificata
/// </summary>
Task<IRestServiceClient> CreateRestServiceClientAsync(string credentialName);
/// <summary>
/// Crea un RestMetadataDiscovery per la credenziale specificata
/// </summary>
Task<IRestMetadataDiscovery> CreateRestMetadataDiscoveryAsync(string credentialName);
/// <summary>
/// Pulisce la cache del client REST per una credenziale specifica
/// </summary>
void ClearRestClientCache(string credentialName);
/// <summary>
/// Pulisce tutta la cache dei client REST
/// </summary>
void ClearAllRestClientCache();
}
public class DataConnectionFactory : IDataConnectionFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<DataConnectionFactory> _logger;
private readonly IDataConnectionCredentialService _credentialService;
// Cache intelligente per client REST che mantiene lo stato di autenticazione ma verifica le credenziali
private readonly Dictionary<string, CachedRestClient> _restClientCache = new();
public DataConnectionFactory(IServiceProvider serviceProvider, ILogger<DataConnectionFactory> logger, IDataConnectionCredentialService credentialService)
{
_serviceProvider = serviceProvider;
_logger = logger;
_credentialService = credentialService;
}public async Task<IDatabaseManager> CreateDatabaseManagerAsync(string credentialName)
{
try
{
var credential = await _credentialService.GetDatabaseCredentialAsync(credentialName);
if (credential == null)
{
throw new ArgumentException($"Credenziale database '{credentialName}' non trovata");
}
// Per ODBC, usa OdbcDatabaseManager direttamente (EF Core non supporta ODBC)
if (credential.DatabaseType == DatabaseType.Odbc)
{
var connectionString = CredentialManager.Models.ConnectionStringBuilder.BuildConnectionString(credential);
_logger.LogInformation("Creando OdbcDatabaseManager con connection string per {CredentialName}", credentialName);
return new DataConnection.DB.OdbcDatabaseManager(connectionString);
}
// Per OLE DB, usa OleDbDatabaseManager direttamente (EF Core non supporta OLE DB)
if (credential.DatabaseType == DatabaseType.OleDb)
{
var connectionString = CredentialManager.Models.ConnectionStringBuilder.BuildConnectionString(credential);
_logger.LogInformation("Creando OleDbDatabaseManager con connection string per {CredentialName}", credentialName);
return new DataConnection.DB.OleDbDatabaseManager(connectionString);
}
// Per altri database, usa EFCoreDatabaseManager
var dbManagerOptions = await _credentialService.GetDbManagerOptionsAsync(credential.Name);
return new EFCoreDatabaseManager(dbManagerOptions);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione del DatabaseManager per {CredentialName}", credentialName);
throw;
}
} public async Task<IRestServiceClient> CreateRestServiceClientAsync(string credentialName)
{
try
{
// SEMPRE recupera le credenziali fresh dal database per assicurare che siano aggiornate
var credential = await _credentialService.GetRestApiCredentialAsync(credentialName);
if (credential == null)
{
throw new ArgumentException($"Credenziale REST API '{credentialName}' non trovata");
}
// Calcola l'hash delle credenziali correnti
var currentCredentialsHash = CalculateCredentialsHash(credential);
// Controlla se abbiamo un client cached con le stesse credenziali
if (_restClientCache.TryGetValue(credentialName, out var cachedClient))
{
if (cachedClient.CredentialsHash == currentCredentialsHash)
{
_logger.LogInformation("Utilizzando client REST cached con credenziali verificate per {CredentialName}", credentialName);
return cachedClient.Client;
}
else
{
_logger.LogInformation("Credenziali cambiate, rimuovendo client cached per {CredentialName}", credentialName);
_restClientCache.Remove(credentialName);
}
} var options = new RestServiceOptions
{
BaseUrl = credential.BaseUrl,
Username = credential.Username,
Password = credential.Password,
TimeoutSeconds = credential.TimeoutSeconds,
IgnoreSslErrors = credential.IgnoreSslErrors
}; // Mapping specifico per tipo di servizio
switch (credential.ServiceType)
{
case RestServiceType.Salesforce:
// Per Salesforce usiamo i campi specifici ClientId e ClientSecret
options.ApiKey = credential.ClientId; // ClientId -> ApiKey
options.AuthToken = credential.ClientSecret; // ClientSecret -> AuthToken
options.SalesforceGrantType = credential.GrantType;
_logger.LogInformation("Salesforce mapping - GrantType: {GrantType}, ClientId: '{ClientId}', ClientSecret: {HasSecret}, Username: '{Username}', Password: {HasPassword}",
credential.GrantType,
credential.ClientId,
!string.IsNullOrEmpty(credential.ClientSecret),
credential.Username,
!string.IsNullOrEmpty(credential.Password));
break;
case RestServiceType.SapB1ServiceLayer:
// Per SAP B1 usiamo il CompanyDatabase come ApiKey
options.ApiKey = credential.CompanyDatabase;
options.AuthToken = credential.AuthToken;
_logger.LogInformation("SAP B1 mapping - CompanyDB: {CompanyDB}, Username: {Username}",
credential.CompanyDatabase, credential.Username);
break;
default:
// Per servizi generici usiamo ApiKey e AuthToken direttamente
options.ApiKey = credential.ApiKey;
options.AuthToken = credential.AuthToken;
_logger.LogInformation("Generic mapping - ApiKey: {ApiKey}, AuthToken: {HasToken}, Username: {Username}",
credential.ApiKey, !string.IsNullOrEmpty(credential.AuthToken), credential.Username);
break;
}
var client = credential.ServiceType switch
{
RestServiceType.SapB1ServiceLayer => new SapB1ServiceClient(options),
RestServiceType.Salesforce => CreateSalesforceClient(options),
RestServiceType.Generic => CreateGenericRestClient(options),
_ => throw new NotSupportedException($"Tipo di servizio REST non supportato: {credential.ServiceType}")
};
// Salva il client nella cache con l'hash delle credenziali
_restClientCache[credentialName] = new CachedRestClient
{
Client = client,
CredentialsHash = currentCredentialsHash,
CachedAt = DateTime.UtcNow
};
_logger.LogInformation("Client REST creato e cached con credenziali aggiornate per {CredentialName}", credentialName);
return client;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione del RestServiceClient per {CredentialName}", credentialName);
throw;
}
} public async Task<IRestMetadataDiscovery> CreateRestMetadataDiscoveryAsync(string credentialName)
{
try
{
// Crea un nuovo client REST con credenziali aggiornate
var restClient = await CreateRestServiceClientAsync(credentialName);
// I service client già implementano IRestMetadataDiscovery
if (restClient is IRestMetadataDiscovery metadataDiscovery)
{
_logger.LogInformation("Utilizzando client REST per metadata discovery con credenziali aggiornate per {CredentialName}", credentialName);
return metadataDiscovery;
}
var credential = await _credentialService.GetRestApiCredentialAsync(credentialName);
throw new NotSupportedException($"Il servizio REST {credential?.ServiceType} non supporta il metadata discovery");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione del RestMetadataDiscovery per {CredentialName}", credentialName);
throw;
}
}
private IRestServiceClient CreateGenericRestClient(RestServiceOptions options)
{
var httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient();
return new BaseRestServiceClient(httpClient, options);
}
private IRestServiceClient CreateSalesforceClient(RestServiceOptions options)
{
var httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient();
var loggerFactory = _serviceProvider.GetService<Microsoft.Extensions.Logging.ILoggerFactory>();
var sfLogger = loggerFactory?.CreateLogger<DataConnection.REST.Implementations.SalesforceServiceClient>();
return new DataConnection.REST.Implementations.SalesforceServiceClient(httpClient, options, sfLogger);
}
/// <summary>
/// Calcola un hash delle credenziali per rilevare cambiamenti
/// </summary>
private string CalculateCredentialsHash(RestApiCredential credential)
{
var credentialString = $"{credential.BaseUrl}|{credential.Username}|{credential.Password}|{credential.ApiKey}|{credential.AuthToken}|{credential.ClientId}|{credential.ClientSecret}|{credential.CompanyDatabase}";
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(credentialString));
return Convert.ToBase64String(bytes);
}
/// <summary>
/// Pulisce la cache del client REST per una credenziale specifica.
/// </summary>
public void ClearRestClientCache(string credentialName)
{
if (_restClientCache.Remove(credentialName))
{
_logger.LogInformation("Cache del client REST rimossa per {CredentialName}", credentialName);
}
}
/// <summary>
/// Pulisce tutta la cache dei client REST.
/// </summary>
public void ClearAllRestClientCache()
{
_restClientCache.Clear();
_logger.LogInformation("Cache di tutti i client REST pulita");
}
}
}