Files
Alessio 483eb7b407 Fix: Risolto double-mapping negli External ID Relationships per Salesforce
- Implementata funzionalità completa External ID Relationships nell'interfaccia di mapping
- Corretto bug double-mapping: i campi sorgente usati per External ID non vengono più inclusi nei mapping normali
- Risolto errore MALFORMED_ID causato dall'invio duplicato di campi come proprietà dirette e nested objects
- Implementata logica corretta per relationship names: oggetti standard usano il nome diretto, custom objects usano suffisso __r
- Aggiunta UI a 3 colonne (Object, External ID Field, Source Field) per configurazione External ID Relationships
- Migrazione database per supporto External ID Relationships nei profili
- Aggiornato ProfileSaver.razor.cs per salvare/caricare External ID Relationships
- Aggiornato ScheduledProfileExecutionService.cs per gestire External ID nelle esecuzioni schedulate
- Formato JSON output corretto: { 'Account': { 'CardCode__c': 'V50000' } }

Documentazione: EXTERNAL_ID_RELATIONSHIPS_IMPLEMENTATION.md
2026-02-15 18:44:15 +01:00

417 lines
15 KiB
C#

namespace CredentialManager.Models;
/// <summary>
/// Tipi di credenziali supportate
/// </summary>
public enum CredentialType
{
Database,
RestApi,
OAuth,
ApiKey,
BasicAuth
}
/// <summary>
/// Tipi di servizi REST specifici
/// </summary>
public enum RestServiceType
{
Generic,
SapB1ServiceLayer,
Salesforce
}
/// <summary>
/// Tipi di database supportati (allineato con DataConnection.Enums.DatabaseType)
/// </summary>
public enum DatabaseType
{
SqlServer,
MySql,
PostgreSql,
Oracle,
Sqlite,
DB2,
SapHana,
Odbc
}
/// <summary>
/// Modalità di connessione ODBC
/// </summary>
public enum OdbcConnectionMode
{
/// <summary>
/// Utilizzo di un DSN (Data Source Name) configurato
/// </summary>
Dsn,
/// <summary>
/// Costruzione manuale della connection string
/// </summary>
Custom
}
/// <summary>
/// DTO per le credenziali database - completo per tutti i tipi di DB supportati
/// </summary>
public class DatabaseCredential
{
public string Name { get; set; } = string.Empty;
public DatabaseType DatabaseType { get; set; }
public string Host { get; set; } = string.Empty;
public int Port { get; set; }
public string? DatabaseName { get; set; } = string.Empty; // Ora opzionale
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string? ConnectionString { get; set; }
public int CommandTimeout { get; set; } = 30;
public bool IgnoreSslErrors { get; set; } = false;
public Dictionary<string, string>? AdditionalParameters { get; set; }
// ODBC specific properties
public string? OdbcDsnName { get; set; } // Nome del DSN ODBC (se utilizzato)
public OdbcConnectionMode OdbcMode { get; set; } = OdbcConnectionMode.Dsn; // Modalità ODBC (DSN o Custom)
}
/// <summary>
/// DTO per le credenziali REST API - allineato con RestServiceOptions e esteso per servizi specifici
/// </summary>
public class RestApiCredential
{
public string Name { get; set; } = string.Empty;
public RestServiceType ServiceType { get; set; } = RestServiceType.Generic;
public string BaseUrl { get; set; } = string.Empty;
public string? ApiKey { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? AuthToken { get; set; }
public string? BearerToken { get; set; }
public int TimeoutSeconds { get; set; } = 100;
public bool IgnoreSslErrors { get; set; } = false;
public Dictionary<string, string>? Headers { get; set; }
public Dictionary<string, string>? AdditionalParameters { get; set; }
// Campi specifici per SAP B1 Service Layer
public string? CompanyDatabase { get; set; }
public string? Language { get; set; } = "en-US";
public string? Version { get; set; } = "v1";
public bool UseTrustedConnection { get; set; } = false;
// Campi specifici per Salesforce
public string? SecurityToken { get; set; }
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
public string? ApiVersion { get; set; } = "59.0";
public bool IsSandbox { get; set; } = false;
public bool UseSoapApi { get; set; } = false;
public string? RefreshToken { get; set; }
public string? AccessToken { get; set; }
public DateTime? TokenExpiry { get; set; }
}
/// <summary>
/// Credenziali specifiche per SAP Business One Service Layer
/// </summary>
public class SapB1ServiceLayerCredential
{
public string Name { get; set; } = string.Empty;
public string ServerUrl { get; set; } = string.Empty; // es: https://server:50000/b1s/v1/
public string CompanyDatabase { get; set; } = string.Empty; // Database dell'azienda SAP
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string? Language { get; set; } = "en-US"; // Lingua per la sessione
public int TimeoutSeconds { get; set; } = 300; // Timeout per le chiamate API
public bool IgnoreSslErrors { get; set; } = false;
public string? Version { get; set; } = "v1"; // Versione del Service Layer (v1, v2, etc.)
public bool UseTrustedConnection { get; set; } = false; // Per autenticazione Windows
public Dictionary<string, string>? AdditionalHeaders { get; set; }
}
/// <summary>
/// Credenziali specifiche per Salesforce
/// </summary>
public class SalesforceCredential
{
public string Name { get; set; } = string.Empty;
public string LoginUrl { get; set; } = "https://login.salesforce.com"; // o https://test.salesforce.com per sandbox
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string SecurityToken { get; set; } = string.Empty; // Token di sicurezza Salesforce
public string? ClientId { get; set; } = string.Empty; // Consumer Key per Connected App
public string? ClientSecret { get; set; } = string.Empty; // Consumer Secret per Connected App
public string ApiVersion { get; set; } = "59.0"; // Versione API Salesforce
public bool IsSandbox { get; set; } = false; // Se è un ambiente sandbox
public int TimeoutSeconds { get; set; } = 120;
public bool UseSoapApi { get; set; } = false; // Se usare SOAP invece di REST
public string? RefreshToken { get; set; }
public string? AccessToken { get; set; }
public DateTime? TokenExpiry { get; set; }
}
/// <summary>
/// Factory per creare connection string per i diversi tipi di database
/// </summary>
public static class ConnectionStringBuilder
{
public static string BuildConnectionString(DatabaseCredential credential)
{
if (!string.IsNullOrEmpty(credential.ConnectionString))
return credential.ConnectionString;
return credential.DatabaseType switch
{
DatabaseType.SqlServer => BuildSqlServerConnectionString(credential),
DatabaseType.MySql => BuildMySqlConnectionString(credential),
DatabaseType.PostgreSql => BuildPostgreSqlConnectionString(credential),
DatabaseType.Oracle => BuildOracleConnectionString(credential),
DatabaseType.Sqlite => BuildSqliteConnectionString(credential),
DatabaseType.DB2 => BuildDb2ConnectionString(credential),
DatabaseType.SapHana => BuildSapHanaConnectionString(credential),
DatabaseType.Odbc => BuildOdbcConnectionString(credential),
_ => throw new NotSupportedException($"Database type {credential.DatabaseType} not supported")
};
} private static string BuildSqlServerConnectionString(DatabaseCredential credential)
{
var builder = new List<string>();
// Gestione speciale per SQL Server locale e named instances
// Se l'host contiene '\' (instance name) o '(localdb)', non aggiungere la porta
bool hasInstanceName = credential.Host.Contains('\\') ||
credential.Host.StartsWith("(localdb)", StringComparison.OrdinalIgnoreCase);
if (hasInstanceName)
{
// Per named instances e LocalDB, non includere la porta
builder.Add($"Server={credential.Host}");
}
else
{
// Per connessioni TCP/IP standard, include host e porta
// Ma solo se la porta non è la default (1433) per localhost
if ((credential.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) ||
credential.Host == "." ||
credential.Host == "127.0.0.1") && credential.Port == 1433)
{
// Per localhost con porta default, ometti la porta per usare Named Pipes
builder.Add($"Server={credential.Host}");
}
else
{
// Per altri casi, usa host,porta
builder.Add($"Server={credential.Host},{credential.Port}");
}
}
// Se username è vuoto o è "Integrated", usa Windows Authentication
if (string.IsNullOrWhiteSpace(credential.Username) ||
credential.Username.Equals("Integrated", StringComparison.OrdinalIgnoreCase) ||
credential.Username.Equals("Windows", StringComparison.OrdinalIgnoreCase))
{
builder.Add("Integrated Security=True");
}
else
{
// Usa SQL Server Authentication
builder.Add($"User Id={credential.Username}");
builder.Add($"Password={credential.Password}");
}
builder.Add($"Connection Timeout={credential.CommandTimeout}");
// Aggiungi Database solo se specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
if (credential.IgnoreSslErrors)
builder.Add("TrustServerCertificate=True");
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
} private static string BuildMySqlConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Server={credential.Host}",
$"Port={credential.Port}",
$"Uid={credential.Username}",
$"Pwd={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
};
// Aggiungi Database solo se specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
if (credential.IgnoreSslErrors)
builder.Add("SslMode=None");
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
} private static string BuildPostgreSqlConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Host={credential.Host}",
$"Port={credential.Port}",
$"Username={credential.Username}",
$"Password={credential.Password}",
$"Timeout={credential.CommandTimeout}"
};
// Aggiungi Database solo se specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
if (credential.IgnoreSslErrors)
builder.Add("SSL Mode=Disable");
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
} private static string BuildOracleConnectionString(DatabaseCredential credential)
{
var builder = new List<string>();
// Per Oracle, il formato cambia se c'è o meno un database specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
{
builder.Add($"Data Source={credential.Host}:{credential.Port}/{credential.DatabaseName}");
}
else
{
builder.Add($"Data Source={credential.Host}:{credential.Port}");
}
builder.AddRange(new[]
{
$"User Id={credential.Username}",
$"Password={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
});
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static string BuildSqliteConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Data Source={credential.DatabaseName}"
};
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static string BuildDb2ConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Server={credential.Host}:{credential.Port}",
$"Database={credential.DatabaseName}",
$"UID={credential.Username}",
$"PWD={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
};
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static string BuildSapHanaConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Server={credential.Host}:{credential.Port}",
$"DatabaseName={credential.DatabaseName}",
$"UserID={credential.Username}",
$"Password={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
};
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static string BuildOdbcConnectionString(DatabaseCredential credential)
{
// Se è già presente una connection string personalizzata, utilizzala
if (!string.IsNullOrEmpty(credential.ConnectionString))
return credential.ConnectionString;
var builder = new List<string>();
// Modalità DSN: usa il DSN configurato
if (credential.OdbcMode == OdbcConnectionMode.Dsn && !string.IsNullOrEmpty(credential.OdbcDsnName))
{
builder.Add($"DSN={credential.OdbcDsnName}");
// Aggiungi credenziali se fornite
if (!string.IsNullOrEmpty(credential.Username))
builder.Add($"UID={credential.Username}");
if (!string.IsNullOrEmpty(credential.Password))
builder.Add($"PWD={credential.Password}");
}
// Modalità Custom: costruisci manualmente la connection string
else
{
// Driver (se specificato nei parametri aggiuntivi)
if (credential.AdditionalParameters?.ContainsKey("Driver") == true)
{
builder.Add($"Driver={{{credential.AdditionalParameters["Driver"]}}}");
}
// Server/Host
if (!string.IsNullOrEmpty(credential.Host))
{
builder.Add($"Server={credential.Host}");
// Porta (se diversa da 0)
if (credential.Port > 0)
builder.Add($"Port={credential.Port}");
}
// Database
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
// Credenziali
if (!string.IsNullOrEmpty(credential.Username))
builder.Add($"UID={credential.Username}");
if (!string.IsNullOrEmpty(credential.Password))
builder.Add($"PWD={credential.Password}");
}
// Timeout
if (credential.CommandTimeout > 0)
builder.Add($"Connection Timeout={credential.CommandTimeout}");
// Parametri aggiuntivi (escludendo Driver se già aggiunto)
if (credential.AdditionalParameters != null)
{
foreach (var param in credential.AdditionalParameters)
{
if (param.Key != "Driver") // Driver già gestito sopra
builder.Add($"{param.Key}={param.Value}");
}
}
return string.Join(";", builder);
}
private static void AddAdditionalParameters(List<string> builder, Dictionary<string, string>? additionalParams)
{
if (additionalParams != null)
{
foreach (var param in additionalParams)
{
builder.Add($"{param.Key}={param.Value}");
}
}
}
}