From b75e57fe31204616878dc1ee122dcdf5e8068754 Mon Sep 17 00:00:00 2001 From: Alessio Dal Santo Date: Sun, 24 May 2026 23:11:22 +0200 Subject: [PATCH] [Feature] Aggiunto supporto OAuth2 client_credentials per Salesforce Implementato il flusso OAuth2 grant_type=client_credentials come alternativa al flusso password gia' esistente per l'autenticazione Salesforce server-to-server. La modifica e' completamente retrocompatibile (default rimane Password). ## Dettaglio modifiche ### CredentialManager/Models/CredentialModels.cs - Aggiunto enum SalesforceGrantType con valori Password e ClientCredentials - Aggiunta proprieta' GrantType (default: Password) su RestApiCredential - Aggiunta proprieta' GrantType (default: Password) su SalesforceCredential ### DataConnection/REST/Configuration/RestServiceOptions.cs - Aggiunta proprieta' SalesforceGrantType per passare il tipo di flusso al client ### DataConnection/REST/Implementations/SalesforceServiceClient.cs - Iniettato ILogger con NullLogger come fallback - Sostituiti ~165 Console.WriteLine con chiamate ILogger appropriate (LogDebug per dettagli, LogInformation per eventi, LogWarning/LogError per problemi) - Aggiunto AuthenticateWithPasswordAsync: incapsula il flusso grant_type=password - Aggiunto AuthenticateWithClientCredentialsAsync: implementa grant_type=client_credentials (richiede solo ClientId e ClientSecret, nessun utente, URL My Domain obbligatorio) - Aggiunto SendTokenRequestAsync: helper condiviso per la POST al token endpoint - Aggiornato AuthenticateAsync() override: instrada al flusso corretto in base a GrantType - Rimosso modificatore static da NormalizeNumericValues (usava _logger, causava CS0120) ### Data_Coupler/Services/DataConnectionFactory.cs - Mappatura del campo GrantType dalle opzioni Salesforce a RestServiceOptions - Passaggio dell'ILogger al costruttore di SalesforceServiceClient ### CredentialManager/Services/CredentialService.cs - SaveRestApiCredentialAsync (blocco Salesforce): serializza GrantType in AdditionalParameters - SaveSalesforceCredentialAsync: aggiunto GrantType nel dizionario iniziale - MapToRestApiCredential: deserializza GrantType da AdditionalParameters con Enum.TryParse - MapToSalesforceCredential: idem per il tipo SalesforceCredential ### DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs - TestSalesforceOAuthLogin aggiornato: per ClientCredentials invia solo client_id e client_secret (senza username/password/security_token); per Password comportamento invariato ### Data_Coupler/Pages/CredentialManagement.razor - Aggiunto dropdown 'Tipo di Autenticazione OAuth2' nella sezione Salesforce - I campi Username, Password e Security Token vengono nascosti quando si seleziona il flusso ClientCredentials - Alert contestuale: warning My Domain URL per ClientCredentials, info per Password - GrantType propagato correttamente in EditRestApiCredential e TestRestApiConnectionFromModal ### AGENTS.md - Aggiunta sezione di documentazione per la nuova funzionalita' OAuth2 client_credentials --- AGENTS.md | 38 ++ CredentialManager/Models/CredentialModels.cs | 24 + .../Services/CredentialService.cs | 8 +- .../DataConnectionCredentialService.cs | 34 +- .../REST/Configuration/RestServiceOptions.cs | 7 +- .../SalesforceServiceClient.cs | 481 ++++++++++-------- Data_Coupler/Pages/CredentialManagement.razor | 50 +- .../Services/DataConnectionFactory.cs | 8 +- 8 files changed, 402 insertions(+), 248 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 18f88ac..471178f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,44 @@ - **Backup e Ripristino**: Sistema completo di backup/restore per configurazioni e dati - **Amministrazione Avanzata**: Interfaccia unificata per gestione sistema e sicurezza +## 🚀 **NUOVE FUNZIONALITÀ - Salesforce OAuth2 Client Credentials Flow (2026)** + +### Supporto `grant_type=client_credentials` per autenticazione server-to-server +**Data Aggiornamento**: 2026 + +#### **Panoramica** +Aggiunto supporto per il flusso OAuth2 `client_credentials` come alternativa al flusso `password` già esistente. +Completamente retrocompatibile: il default rimane `Password`. + +#### **Enum `SalesforceGrantType`** (in `CredentialManager/Models/CredentialModels.cs`) +```csharp +public enum SalesforceGrantType +{ + Password, // grant_type=password — richiede Username, Password, SecurityToken (+ClientId/ClientSecret) + ClientCredentials // grant_type=client_credentials — server-to-server, nessun utente +} +``` + +#### **Differenze tra i flussi** +| Aspetto | `password` | `client_credentials` | +|---|---|---| +| Richiede Username/Password | ✅ Sì | ❌ No | +| Richiede SecurityToken | ✅ Sì (se non Connected App) | ❌ No | +| ClientId / ClientSecret | Opzionale | ✅ Obbligatorio | +| Base URL | login/test.salesforce.com | **My Domain URL** (es. `https://myorg.my.salesforce.com`) | +| Utente Salesforce | Necessario | Integration User (assegnato nella Connected App) | + +#### **File modificati** +- `CredentialManager/Models/CredentialModels.cs` — enum `SalesforceGrantType`, proprietà `GrantType` su `RestApiCredential` e `SalesforceCredential` +- `DataConnection/REST/Configuration/RestServiceOptions.cs` — proprietà `SalesforceGrantType` +- `DataConnection/REST/Implementations/SalesforceServiceClient.cs` — `AuthenticateWithPasswordAsync`, `AuthenticateWithClientCredentialsAsync`, `SendTokenRequestAsync`; `ILogger` iniettato; `NormalizeNumericValues` reso non-static +- `Data_Coupler/Services/DataConnectionFactory.cs` — mapping `GrantType`, logger passato al client +- `CredentialManager/Services/CredentialService.cs` — `GrantType` serializzato/deserializzato in `AdditionalParameters` JSON +- `DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs` — `TestSalesforceOAuthLogin` instrada per `GrantType` +- `Data_Coupler/Pages/CredentialManagement.razor` — dropdown "Tipo di Autenticazione OAuth2"; Username/Password/SecurityToken nascosti per `ClientCredentials`; warning My Domain URL + +--- + ## 🚀 **NUOVE FUNZIONALITÀ - Salesforce Optimizations (Febbraio 2026)** ### Salesforce Batch Describe via Composite API diff --git a/CredentialManager/Models/CredentialModels.cs b/CredentialManager/Models/CredentialModels.cs index 546bcc3..f0aa21a 100644 --- a/CredentialManager/Models/CredentialModels.cs +++ b/CredentialManager/Models/CredentialModels.cs @@ -22,6 +22,27 @@ public enum RestServiceType Salesforce } +/// +/// Tipo di flusso OAuth2 per Salesforce +/// +public enum SalesforceGrantType +{ + /// + /// Flusso Username/Password (grant_type=password). + /// Richiede: ClientId, ClientSecret, Username, Password (+SecurityToken se non IP-trusted). + /// URL di login: https://login.salesforce.com o https://test.salesforce.com. + /// + Password, + + /// + /// Flusso Client Credentials (grant_type=client_credentials) — server-to-server, senza utente. + /// Richiede: ClientId, ClientSecret. + /// URL obbligatorio: My Domain URL (es. https://myorg.my.salesforce.com). + /// La Connected App deve avere "Enable Client Credentials Flow" attivato e un Integration User assegnato. + /// + ClientCredentials +} + /// /// Tipi di database supportati (allineato con DataConnection.Enums.DatabaseType) /// @@ -106,6 +127,7 @@ public class RestApiCredential public string? ApiVersion { get; set; } = "59.0"; public bool IsSandbox { get; set; } = false; public bool UseSoapApi { get; set; } = false; + public SalesforceGrantType GrantType { get; set; } = SalesforceGrantType.Password; public string? RefreshToken { get; set; } public string? AccessToken { get; set; } public DateTime? TokenExpiry { get; set; } @@ -145,6 +167,8 @@ public class SalesforceCredential 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 + /// Tipo di flusso OAuth2 da utilizzare. Default: Password (retrocompatibile). + public SalesforceGrantType GrantType { get; set; } = SalesforceGrantType.Password; public string? RefreshToken { get; set; } public string? AccessToken { get; set; } public DateTime? TokenExpiry { get; set; } diff --git a/CredentialManager/Services/CredentialService.cs b/CredentialManager/Services/CredentialService.cs index b3c3727..ea09c93 100644 --- a/CredentialManager/Services/CredentialService.cs +++ b/CredentialManager/Services/CredentialService.cs @@ -233,6 +233,7 @@ public class CredentialService : ICredentialService additionalParams["ApiVersion"] = credential.ApiVersion; additionalParams["IsSandbox"] = credential.IsSandbox.ToString(); additionalParams["UseSoapApi"] = credential.UseSoapApi.ToString(); + additionalParams["GrantType"] = credential.GrantType.ToString(); if (!string.IsNullOrEmpty(credential.RefreshToken)) additionalParams["RefreshToken"] = credential.RefreshToken; if (!string.IsNullOrEmpty(credential.AccessToken)) @@ -523,7 +524,8 @@ public class CredentialService : ICredentialService ["SecurityToken"] = credential.SecurityToken, ["ApiVersion"] = credential.ApiVersion, ["IsSandbox"] = credential.IsSandbox.ToString(), - ["UseSoapApi"] = credential.UseSoapApi.ToString() + ["UseSoapApi"] = credential.UseSoapApi.ToString(), + ["GrantType"] = credential.GrantType.ToString() }; // Aggiungi ClientId e ClientSecret se forniti @@ -793,6 +795,8 @@ public class CredentialService : ICredentialService credential.IsSandbox = sandbox; if (additionalParams.TryGetValue("UseSoapApi", out var useSoap) && bool.TryParse(useSoap, out var soap)) credential.UseSoapApi = soap; + if (additionalParams.TryGetValue("GrantType", out var grantTypeStr) && Enum.TryParse(grantTypeStr, out var grantType)) + credential.GrantType = grantType; if (additionalParams.TryGetValue("RefreshToken", out var refreshToken)) credential.RefreshToken = refreshToken; if (additionalParams.TryGetValue("AccessToken", out var accessToken)) @@ -915,6 +919,8 @@ public class CredentialService : ICredentialService credential.IsSandbox = sandbox; if (additionalParams.TryGetValue("UseSoapApi", out var useSoap) && bool.TryParse(useSoap, out var soap)) credential.UseSoapApi = soap; + if (additionalParams.TryGetValue("GrantType", out var grantTypeStr) && Enum.TryParse(grantTypeStr, out var grantType)) + credential.GrantType = grantType; if (additionalParams.TryGetValue("RefreshToken", out var refreshToken)) credential.RefreshToken = refreshToken; if (additionalParams.TryGetValue("AccessToken", out var accessToken)) diff --git a/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs b/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs index c5309d7..03c0be0 100644 --- a/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs +++ b/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs @@ -863,23 +863,39 @@ public class DataConnectionCredentialService : IDataConnectionCredentialService try { var tokenUrl = credential.LoginUrl.TrimEnd('/') + "/services/oauth2/token"; + List> tokenData; - var tokenData = new List> + if (credential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials) { - new("grant_type", "password"), - new("username", credential.Username), - new("password", credential.Password + credential.SecurityToken), - new("client_id", credential.ClientId ?? ""), - new("client_secret", credential.ClientSecret ?? "") - }; + // Client Credentials flow — server-to-server, no user + tokenData = new List> + { + new("grant_type", "client_credentials"), + new("client_id", credential.ClientId ?? ""), + new("client_secret", credential.ClientSecret ?? "") + }; + } + else + { + // Password flow (default) + tokenData = new List> + { + 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 responseContent = await response.Content.ReadAsStringAsync(); - 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\n- Timeout: {credential.TimeoutSeconds}s"); + 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 { diff --git a/DataConnection/REST/Configuration/RestServiceOptions.cs b/DataConnection/REST/Configuration/RestServiceOptions.cs index 6413091..254aed8 100644 --- a/DataConnection/REST/Configuration/RestServiceOptions.cs +++ b/DataConnection/REST/Configuration/RestServiceOptions.cs @@ -41,6 +41,11 @@ namespace DataConnection.REST.Configuration /// public bool IgnoreSslErrors { get; set; } = false; - // Add other relevant configuration properties (e.g., OAuth settings, specific headers) + /// + /// Salesforce OAuth2 grant type. Default: Password (retrocompatibile). + /// ClientCredentials = server-to-server, senza utente. + /// + public CredentialManager.Models.SalesforceGrantType SalesforceGrantType { get; set; } + = CredentialManager.Models.SalesforceGrantType.Password; } } diff --git a/DataConnection/REST/Implementations/SalesforceServiceClient.cs b/DataConnection/REST/Implementations/SalesforceServiceClient.cs index 0977582..f4791a6 100644 --- a/DataConnection/REST/Implementations/SalesforceServiceClient.cs +++ b/DataConnection/REST/Implementations/SalesforceServiceClient.cs @@ -1,6 +1,8 @@ +using CredentialManager.Models; using DataConnection.REST.Configuration; using DataConnection.REST.Interfaces; using DataConnection.REST.Models; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -22,9 +24,10 @@ namespace DataConnection.REST.Implementations private string? _accessToken; private string? _instanceUrl; private DateTime _tokenExpiry; + private readonly ILogger _logger; /// - /// Configurazione JSON per garantire la compatibilità con Salesforce API + /// Configurazione JSON per garantire la compatibilità con Salesforce API /// Utilizza sempre la cultura invariante per i numeri per evitare problemi con virgole/punti decimali /// private static readonly JsonSerializerOptions SalesforceJsonOptions = new JsonSerializerOptions @@ -34,26 +37,23 @@ namespace DataConnection.REST.Implementations Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - public SalesforceServiceClient(HttpClient httpClient, RestServiceOptions options) + public SalesforceServiceClient(HttpClient httpClient, RestServiceOptions options, ILogger? logger = null) : base(httpClient, options) { + _logger = logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance; } /// - /// Authenticates with Salesforce using Username/Password OAuth2 flow. + /// Authenticates with Salesforce using Username/Password OAuth2 flow (grant_type=password). /// - /// Connected App Consumer Key - /// Connected App Consumer Secret - /// Salesforce username - /// Salesforce password + security token - /// Cancellation token - /// True if authentication is successful - public async Task AuthenticateAsync(string clientId, string clientSecret, string username, string password, CancellationToken cancellationToken = default) + public async Task AuthenticateWithPasswordAsync(string clientId, string clientSecret, string username, string password, CancellationToken cancellationToken = default) { try { var tokenEndpoint = "/services/oauth2/token"; - + _logger.LogInformation("Salesforce [password flow] authenticating. URL={Url}, Username={Username}", + $"{_httpClient.BaseAddress}{tokenEndpoint}", username); + var tokenRequest = new List> { new("grant_type", "password"), @@ -63,92 +63,127 @@ namespace DataConnection.REST.Implementations new("password", password) }; - var formContent = new FormUrlEncodedContent(tokenRequest); - - Console.WriteLine($"--- Salesforce Authentication Attempt ---"); - Console.WriteLine($"Target URL: {_httpClient.BaseAddress}{tokenEndpoint}"); - Console.WriteLine($"Username: {username}"); - Console.WriteLine($"--- End Salesforce Authentication Attempt ---"); + return await SendTokenRequestAsync(tokenRequest, "password", cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during Salesforce password authentication"); + return false; + } + } + /// + /// Authenticates with Salesforce using Client Credentials OAuth2 flow (grant_type=client_credentials). + /// Server-to-server integration — no user interaction required. + /// Prerequisites: Connected App with "Enable Client Credentials Flow" enabled and an Integration User assigned. + /// NOTE: Requires a My Domain URL (e.g. https://myorg.my.salesforce.com), NOT https://login.salesforce.com. + /// + public async Task AuthenticateWithClientCredentialsAsync(string clientId, string clientSecret, CancellationToken cancellationToken = default) + { + try + { + var tokenEndpoint = "/services/oauth2/token"; + _logger.LogInformation("Salesforce [client_credentials flow] authenticating. URL={Url}", + $"{_httpClient.BaseAddress}{tokenEndpoint}"); + + var tokenRequest = new List> + { + new("grant_type", "client_credentials"), + new("client_id", clientId), + new("client_secret", clientSecret) + }; + + return await SendTokenRequestAsync(tokenRequest, "client_credentials", cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during Salesforce client_credentials authentication"); + return false; + } + } + + /// + /// Sends a token request to the Salesforce OAuth2 endpoint and stores the resulting token. + /// + private async Task SendTokenRequestAsync(List> tokenParams, string flowName, CancellationToken cancellationToken) + { + try + { + var tokenEndpoint = "/services/oauth2/token"; + var formContent = new FormUrlEncodedContent(tokenParams); var response = await _httpClient.PostAsync(tokenEndpoint, formContent, cancellationToken); - + if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Salesforce Authentication failed: {response.StatusCode}"); - Console.WriteLine($"Error details: {errorContent}"); + _logger.LogWarning("Salesforce authentication ({Flow}) failed: {StatusCode}. Details: {Details}", + flowName, response.StatusCode, errorContent); return false; } var tokenResponse = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); - if (tokenResponse != null && !string.IsNullOrEmpty(tokenResponse.AccessToken)) + if (tokenResponse != null && !string.IsNullOrEmpty(tokenResponse.AccessToken)) { _accessToken = tokenResponse.AccessToken; _instanceUrl = tokenResponse.InstanceUrl; - _tokenExpiry = DateTime.UtcNow.AddSeconds(3600); // Default 1 hour, Salesforce doesn't always return expires_in - - // Don't change BaseAddress - we'll use absolute URLs for API calls - // Store the instance URL for building complete URLs later - - // Add Authorization header for future requests - _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken); - - Console.WriteLine($"Salesforce Authentication successful. Token expires around: {_tokenExpiry.ToLocalTime()}"); - Console.WriteLine($"Instance URL: {_instanceUrl}"); + _tokenExpiry = DateTime.UtcNow.AddSeconds(3600); // Salesforce doesn't always return expires_in + + _httpClient.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken); + + _logger.LogInformation("Salesforce authentication ({Flow}) successful. InstanceUrl={InstanceUrl}, TokenExpiry={Expiry}", + flowName, _instanceUrl, _tokenExpiry.ToLocalTime()); return true; } - Console.WriteLine("Salesforce Authentication response could not be parsed."); + _logger.LogWarning("Salesforce authentication ({Flow}): token response could not be parsed or access_token was empty.", flowName); return false; } catch (HttpRequestException ex) { - Console.WriteLine($"HTTP Request Error during Salesforce Authentication: {ex.Message}"); - if (ex.InnerException != null) - { - Console.WriteLine($"Inner Exception: {ex.InnerException.Message}"); - } + _logger.LogError(ex, "HTTP error during Salesforce authentication ({Flow})", flowName); return false; } catch (JsonException ex) { - Console.WriteLine($"JSON Parsing Error during Salesforce Authentication: {ex.Message}"); + _logger.LogError(ex, "JSON parsing error during Salesforce authentication ({Flow})", flowName); return false; } - catch (Exception ex) - { - Console.WriteLine($"Error during Salesforce Authentication: {ex.Message}"); - return false; - } - } /// - /// Authenticates with Salesforce using the credentials from options. + } + + /// + /// Authenticates with Salesforce using the grant type configured in options. + /// Routes to or + /// based on . /// - /// Cancellation token - /// True if authentication is successful public override async Task AuthenticateAsync(CancellationToken cancellationToken = default) { - // For Salesforce, we need ClientId, ClientSecret, Username, and Password - // These should be provided in the options - + var clientId = _options.ApiKey; + var clientSecret = _options.AuthToken; + + if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + { + _logger.LogError("Salesforce authentication requires ClientId (ApiKey) and ClientSecret (AuthToken) in options. " + + "ClientId={HasClientId}, ClientSecret={HasSecret}", + !string.IsNullOrEmpty(clientId), !string.IsNullOrEmpty(clientSecret)); + return false; + } + + if (_options.SalesforceGrantType == SalesforceGrantType.ClientCredentials) + { + return await AuthenticateWithClientCredentialsAsync(clientId, clientSecret, cancellationToken); + } + + // Default: password flow if (string.IsNullOrEmpty(_options.Username) || string.IsNullOrEmpty(_options.Password)) { - Console.WriteLine($"Salesforce authentication requires username and password in options. Username: '{_options.Username}', Password: '{(!string.IsNullOrEmpty(_options.Password) ? "***SET***" : "***NULL***")}'"); + _logger.LogError("Salesforce password flow requires Username and Password in options. " + + "Username={HasUsername}, Password={HasPassword}", + !string.IsNullOrEmpty(_options.Username), !string.IsNullOrEmpty(_options.Password)); return false; } - if (string.IsNullOrEmpty(_options.ApiKey) || string.IsNullOrEmpty(_options.AuthToken)) - { - Console.WriteLine($"Salesforce authentication requires ApiKey (ClientId) and AuthToken (ClientSecret) in options. ApiKey: '{_options.ApiKey}', AuthToken: '{(!string.IsNullOrEmpty(_options.AuthToken) ? "***SET***" : "***NULL***")}'"); - return false; - } - - // Use the actual credentials from options - var clientId = _options.ApiKey; // ClientId should be in ApiKey field - var clientSecret = _options.AuthToken; // ClientSecret should be in AuthToken field - - Console.WriteLine($"Using Salesforce credentials - ClientId: {clientId}, Username: {_options.Username}"); - - return await AuthenticateAsync(clientId, clientSecret, _options.Username, _options.Password, cancellationToken); + return await AuthenticateWithPasswordAsync(clientId, clientSecret, _options.Username, _options.Password, cancellationToken); } /// @@ -168,7 +203,7 @@ namespace DataConnection.REST.Implementations { if (!IsAuthenticated()) { - Console.WriteLine("Error: Not authenticated to Salesforce. Cannot discover metadata."); + _logger.LogDebug("Error: Not authenticated to Salesforce. Cannot discover metadata."); return new List(); } @@ -188,7 +223,7 @@ namespace DataConnection.REST.Implementations .Select(s => s.Name!) .ToList(); - Console.WriteLine($"DiscoverEntities: {sObjectNames.Count} SObjects. Using Composite Batch API ({Math.Ceiling((double)sObjectNames.Count / 25)} request(s) instead of {sObjectNames.Count})."); + _logger.LogDebug($"DiscoverEntities: {sObjectNames.Count} SObjects. Using Composite Batch API ({Math.Ceiling((double)sObjectNames.Count / 25)} request(s) instead of {sObjectNames.Count})."); // Step 2: batch describe all SObjects via Composite Batch API (25 per request) var describeResults = await BatchDescribeSObjectsAsync(sObjectNames, cancellationToken); @@ -216,15 +251,15 @@ namespace DataConnection.REST.Implementations } catch (HttpRequestException ex) { - Console.WriteLine($"HTTP Request Error during Salesforce metadata discovery: {ex.Message}"); + _logger.LogDebug($"HTTP Request Error during Salesforce metadata discovery: {ex.Message}"); } catch (JsonException ex) { - Console.WriteLine($"JSON Parsing Error during Salesforce metadata discovery: {ex.Message}"); + _logger.LogDebug($"JSON Parsing Error during Salesforce metadata discovery: {ex.Message}"); } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce metadata discovery: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce metadata discovery: {ex.Message}"); } return entities; @@ -239,7 +274,7 @@ namespace DataConnection.REST.Implementations { if (!IsAuthenticated()) { - Console.WriteLine("Error: Not authenticated to Salesforce. Cannot discover metadata."); + _logger.LogDebug("Error: Not authenticated to Salesforce. Cannot discover metadata."); return new List(); } @@ -272,15 +307,15 @@ namespace DataConnection.REST.Implementations } catch (HttpRequestException ex) { - Console.WriteLine($"HTTP Request Error during Salesforce metadata discovery: {ex.Message}"); + _logger.LogDebug($"HTTP Request Error during Salesforce metadata discovery: {ex.Message}"); } catch (JsonException ex) { - Console.WriteLine($"JSON Parsing Error during Salesforce metadata discovery: {ex.Message}"); + _logger.LogDebug($"JSON Parsing Error during Salesforce metadata discovery: {ex.Message}"); } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce metadata discovery: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce metadata discovery: {ex.Message}"); } return entities.OrderBy(e => e.Name).ToList(); @@ -296,7 +331,7 @@ namespace DataConnection.REST.Implementations { if (!IsAuthenticated()) { - Console.WriteLine("Error: Not authenticated to Salesforce. Cannot discover entity details."); + _logger.LogDebug("Error: Not authenticated to Salesforce. Cannot discover entity details."); return null; } @@ -308,7 +343,7 @@ namespace DataConnection.REST.Implementations if (!describeResponse.IsSuccessStatusCode) { - Console.WriteLine($"Failed to get details for SObject {entityName}: {describeResponse.StatusCode}"); + _logger.LogDebug($"Failed to get details for SObject {entityName}: {describeResponse.StatusCode}"); return null; } @@ -341,15 +376,15 @@ namespace DataConnection.REST.Implementations } catch (HttpRequestException ex) { - Console.WriteLine($"HTTP Request Error during Salesforce entity details discovery: {ex.Message}"); + _logger.LogDebug($"HTTP Request Error during Salesforce entity details discovery: {ex.Message}"); } catch (JsonException ex) { - Console.WriteLine($"JSON Parsing Error during Salesforce entity details discovery: {ex.Message}"); + _logger.LogDebug($"JSON Parsing Error during Salesforce entity details discovery: {ex.Message}"); } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce entity details discovery: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce entity details discovery: {ex.Message}"); } return null; @@ -374,14 +409,14 @@ namespace DataConnection.REST.Implementations batches.Add((chunk, (i / maxBatchSize) + 1)); } - Console.WriteLine($"BatchDescribeSObjects: {sObjectNames.Count} objects → {batches.Count} Composite Batch request(s)"); + _logger.LogDebug($"BatchDescribeSObjects: {sObjectNames.Count} objects → {batches.Count} Composite Batch request(s)"); var batchEndpoint = $"{_instanceUrl}/services/data/v60.0/composite/batch"; // Execute all batches in parallel var batchTasks = batches.Select(async b => { - Console.WriteLine($"BatchDescribeSObjects: sending batch {b.BatchNumber}/{batches.Count} ({b.Names.Count} objects)"); + _logger.LogDebug($"BatchDescribeSObjects: sending batch {b.BatchNumber}/{batches.Count} ({b.Names.Count} objects)"); var batchRequest = new SalesforceBatchDescribeRequest { BatchRequests = b.Names.Select(name => new SalesforceBatchDescribeSubRequest @@ -405,7 +440,7 @@ namespace DataConnection.REST.Implementations if (!response.IsSuccessStatusCode) { var err = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"BatchDescribeSObjects batch {b.BatchNumber} failed: {response.StatusCode} - {err}"); + _logger.LogDebug($"BatchDescribeSObjects batch {b.BatchNumber} failed: {response.StatusCode} - {err}"); foreach (var name in b.Names) batchResults[name] = null; return batchResults; } @@ -434,13 +469,13 @@ namespace DataConnection.REST.Implementations } catch (JsonException ex) { - Console.WriteLine($"BatchDescribeSObjects: failed to parse describe for {objectName}: {ex.Message}"); + _logger.LogDebug($"BatchDescribeSObjects: failed to parse describe for {objectName}: {ex.Message}"); batchResults[objectName] = null; } } else { - Console.WriteLine($"BatchDescribeSObjects: describe for {objectName} returned status {subResponse.StatusCode}"); + _logger.LogDebug($"BatchDescribeSObjects: describe for {objectName} returned status {subResponse.StatusCode}"); batchResults[objectName] = null; } } @@ -448,7 +483,7 @@ namespace DataConnection.REST.Implementations } catch (Exception ex) { - Console.WriteLine($"BatchDescribeSObjects: exception in batch {b.BatchNumber}: {ex.Message}"); + _logger.LogDebug($"BatchDescribeSObjects: exception in batch {b.BatchNumber}: {ex.Message}"); foreach (var name in b.Names) batchResults[name] = null; } @@ -461,7 +496,7 @@ namespace DataConnection.REST.Implementations allResults[kvp.Key] = kvp.Value; var successCount = allResults.Values.Count(v => v != null); - Console.WriteLine($"BatchDescribeSObjects completed: {successCount}/{sObjectNames.Count} objects described successfully."); + _logger.LogDebug($"BatchDescribeSObjects completed: {successCount}/{sObjectNames.Count} objects described successfully."); return allResults; } @@ -476,17 +511,17 @@ namespace DataConnection.REST.Implementations { if (!IsAuthenticated()) { - Console.WriteLine("Error: Not authenticated to Salesforce. Cannot create entity."); + _logger.LogDebug("Error: Not authenticated to Salesforce. Cannot create entity."); return null; } // Salesforce REST API endpoint for creating SObjects var createUri = $"{_instanceUrl}/services/data/v60.0/sobjects/{entityName}/"; try { - Console.WriteLine($"--- Salesforce Entity Creation Attempt ---"); - Console.WriteLine($"SObject: {entityName}"); - Console.WriteLine($"Target URL: {createUri}"); - Console.WriteLine($"Data: {JsonSerializer.Serialize(entityData, SalesforceJsonOptions)}"); + _logger.LogDebug($"--- Salesforce Entity Creation Attempt ---"); + _logger.LogDebug($"SObject: {entityName}"); + _logger.LogDebug($"Target URL: {createUri}"); + _logger.LogDebug($"Data: {JsonSerializer.Serialize(entityData, SalesforceJsonOptions)}"); // Normalizza i valori numerici per evitare problemi con virgole decimali var normalizedData = NormalizeNumericValues(entityData); @@ -503,16 +538,16 @@ namespace DataConnection.REST.Implementations if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Salesforce Entity Creation failed: {response.StatusCode}"); - Console.WriteLine($"Error details: {errorContent}"); - Console.WriteLine($"--- End Salesforce Entity Creation Attempt (Failed) ---"); + _logger.LogDebug($"Salesforce Entity Creation failed: {response.StatusCode}"); + _logger.LogDebug($"Error details: {errorContent}"); + _logger.LogDebug($"--- End Salesforce Entity Creation Attempt (Failed) ---"); return null; } var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Salesforce Entity Creation successful"); - Console.WriteLine($"Response: {responseContent}"); - Console.WriteLine($"--- End Salesforce Entity Creation Attempt (Success) ---"); + _logger.LogDebug($"Salesforce Entity Creation successful"); + _logger.LogDebug($"Response: {responseContent}"); + _logger.LogDebug($"--- End Salesforce Entity Creation Attempt (Success) ---"); if (string.IsNullOrEmpty(responseContent)) return entityData; // Return original data if no response content @@ -535,24 +570,24 @@ namespace DataConnection.REST.Implementations } catch (HttpRequestException ex) { - Console.WriteLine($"HTTP Request Error during Salesforce entity creation: {ex.Message}"); + _logger.LogDebug($"HTTP Request Error during Salesforce entity creation: {ex.Message}"); if (ex.InnerException != null) { - Console.WriteLine($"Inner Exception: {ex.InnerException.Message}"); + _logger.LogDebug($"Inner Exception: {ex.InnerException.Message}"); } - Console.WriteLine($"--- End Salesforce Entity Creation Attempt (Exception) ---"); + _logger.LogDebug($"--- End Salesforce Entity Creation Attempt (Exception) ---"); return null; } catch (JsonException ex) { - Console.WriteLine($"JSON Parsing Error during Salesforce entity creation: {ex.Message}"); - Console.WriteLine($"--- End Salesforce Entity Creation Attempt (JsonException) ---"); + _logger.LogDebug($"JSON Parsing Error during Salesforce entity creation: {ex.Message}"); + _logger.LogDebug($"--- End Salesforce Entity Creation Attempt (JsonException) ---"); return null; } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce entity creation: {ex.Message}"); - Console.WriteLine($"--- End Salesforce Entity Creation Attempt (Exception) ---"); + _logger.LogDebug($"Error during Salesforce entity creation: {ex.Message}"); + _logger.LogDebug($"--- End Salesforce Entity Creation Attempt (Exception) ---"); return null; } } @@ -563,26 +598,26 @@ namespace DataConnection.REST.Implementations // Per ora, semplicemente tentiamo la creazione try { - Console.WriteLine($"--- Starting Salesforce Entity Upsert: {entityName} ---"); - Console.WriteLine($"Entity Data: {string.Join(", ", entityData.Select(kvp => $"{kvp.Key}={kvp.Value}"))}"); + _logger.LogDebug($"--- Starting Salesforce Entity Upsert: {entityName} ---"); + _logger.LogDebug($"Entity Data: {string.Join(", ", entityData.Select(kvp => $"{kvp.Key}={kvp.Value}"))}"); // Prima tenta la creazione var result = await CreateEntityAsync(entityName, entityData, cancellationToken); if (result != null) { - Console.WriteLine($"Upsert completed successfully via CREATE for {entityName}"); + _logger.LogDebug($"Upsert completed successfully via CREATE for {entityName}"); return result; } // Se la creazione fallisce, potresti implementare qui la logica di aggiornamento // Per ora, restituiamo null - Console.WriteLine($"Upsert failed for {entityName}"); + _logger.LogDebug($"Upsert failed for {entityName}"); return null; } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce entity upsert: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce entity upsert: {ex.Message}"); return null; } } /// @@ -597,17 +632,17 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Salesforce Entity Search: {entityName} ---"); - Console.WriteLine($"Key Fields: {string.Join(", ", keyFields.Select(kvp => $"{kvp.Key}={kvp.Value}"))}"); + _logger.LogDebug($"--- Starting Salesforce Entity Search: {entityName} ---"); + _logger.LogDebug($"Key Fields: {string.Join(", ", keyFields.Select(kvp => $"{kvp.Key}={kvp.Value}"))}"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for entity search"); + _logger.LogDebug("Authentication failed for entity search"); return new List>(); } - // 🔍 IMPORTANTE: L'approccio External ID GET funziona SOLO per campi marcati come External ID in Salesforce - // Per la maggior parte dei campi, è più affidabile usare direttamente SOQL query + // 🔍 IMPORTANTE: L'approccio External ID GET funziona SOLO per campi marcati come External ID in Salesforce + // Per la maggior parte dei campi, è più affidabile usare direttamente SOQL query // Se vuoi abilitare il tentativo External ID, decommenta il blocco sotto /* EXTERNAL ID GET - DISABILITATO PER DEFAULT @@ -622,8 +657,8 @@ namespace DataConnection.REST.Implementations // Tentativo 1: GET con External ID (funziona SOLO per campi External ID) // Endpoint: /sobjects/{objectType}/{fieldName}/{fieldValue} var externalIdEndpoint = $"{_instanceUrl}/services/data/v60.0/sobjects/{entityName}/{fieldName}/{Uri.EscapeDataString(fieldValue)}"; - Console.WriteLine($"⚠️ Tentativo GET con External ID: {externalIdEndpoint}"); - Console.WriteLine($"⚠️ NOTA: Questo funziona SOLO se '{fieldName}' è marcato come External ID in Salesforce"); + _logger.LogDebug($"⚠️ Tentativo GET con External ID: {externalIdEndpoint}"); + _logger.LogDebug($"⚠️ NOTA: Questo funziona SOLO se '{fieldName}' è marcato come External ID in Salesforce"); var getResponse = await _httpClient.GetAsync(externalIdEndpoint, cancellationToken); @@ -632,29 +667,29 @@ namespace DataConnection.REST.Implementations var entity = await getResponse.Content.ReadFromJsonAsync>(cancellationToken: cancellationToken); if (entity != null) { - Console.WriteLine($"✅ Trovato tramite External ID GET: Id={entity.GetValueOrDefault("Id")}"); + _logger.LogDebug($"✅ Trovato tramite External ID GET: Id={entity.GetValueOrDefault("Id")}"); return new List> { entity }; } } else if (getResponse.StatusCode == System.Net.HttpStatusCode.NotFound) { - Console.WriteLine($"⚠️ External ID GET ha restituito 404 - Il campo '{fieldName}' probabilmente non è External ID"); - Console.WriteLine($" Uso SOQL query come fallback (metodo universale)"); + _logger.LogDebug($"⚠️ External ID GET ha restituito 404 - Il campo '{fieldName}' probabilmente non è External ID"); + _logger.LogDebug($" Uso SOQL query come fallback (metodo universale)"); } else { - Console.WriteLine($"External ID GET non disponibile (Status: {getResponse.StatusCode}), uso SOQL query"); + _logger.LogDebug($"External ID GET non disponibile (Status: {getResponse.StatusCode}), uso SOQL query"); } } catch (Exception externalIdEx) { - Console.WriteLine($"External ID GET fallito: {externalIdEx.Message}, uso SOQL query"); + _logger.LogDebug($"External ID GET fallito: {externalIdEx.Message}, uso SOQL query"); } } */ - // 🔍 SOQL Query: metodo universale che funziona per TUTTI i campi (External ID o no) - Console.WriteLine("📋 Usando SOQL Query per la ricerca (metodo universale)..."); + // 🔍 SOQL Query: metodo universale che funziona per TUTTI i campi (External ID o no) + _logger.LogDebug("📋 Usando SOQL Query per la ricerca (metodo universale)..."); // Costruisci le condizioni WHERE var whereConditions = new List(); @@ -663,11 +698,11 @@ namespace DataConnection.REST.Implementations var fieldName = kvp.Key; var fieldValue = kvp.Value; - Console.WriteLine($" 🔎 Ricerca per campo: {fieldName} = {fieldValue}"); + _logger.LogDebug($" 🔎 Ricerca per campo: {fieldName} = {fieldValue}"); var value = fieldValue?.ToString() ?? ""; - // Se il valore è una stringa, aggiungi le virgolette ed escape + // Se il valore è una stringa, aggiungi le virgolette ed escape if (fieldValue is string) { value = $"'{value.Replace("'", "\\'")}'"; // Escape delle virgolette @@ -681,27 +716,27 @@ namespace DataConnection.REST.Implementations fieldsToSelect.AddRange(keyFields.Keys.Where(k => k != "Id")); var query = $"SELECT {string.Join(", ", fieldsToSelect)} FROM {entityName} WHERE {string.Join(" AND ", whereConditions)}"; - Console.WriteLine($"📝 SOQL Query: {query}"); + _logger.LogDebug($"📝 SOQL Query: {query}"); // Usa l'endpoint query con autenticazione corretta var encodedQuery = Uri.EscapeDataString(query); var queryEndpoint = $"{_instanceUrl}/services/data/v60.0/query/?q={encodedQuery}"; - Console.WriteLine($"Query Endpoint: {queryEndpoint}"); + _logger.LogDebug($"Query Endpoint: {queryEndpoint}"); - // Usa GET con autenticazione inclusa nell'HttpClient (già configurato) + // Usa GET con autenticazione inclusa nell'HttpClient (già configurato) var response = await _httpClient.GetAsync(queryEndpoint, cancellationToken); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"SOQL Query failed: {response.StatusCode}"); - Console.WriteLine($"Error details: {errorContent}"); + _logger.LogDebug($"SOQL Query failed: {response.StatusCode}"); + _logger.LogDebug($"Error details: {errorContent}"); return new List>(); } var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"SOQL Response: {responseContent}"); + _logger.LogDebug($"SOQL Response: {responseContent}"); var queryResponse = JsonSerializer.Deserialize(responseContent, SalesforceJsonOptions); @@ -711,22 +746,22 @@ namespace DataConnection.REST.Implementations record as Dictionary ?? new Dictionary() ).ToList(); - Console.WriteLine($"✅ Trovati {results.Count} record tramite SOQL"); + _logger.LogDebug($"✅ Trovati {results.Count} record tramite SOQL"); foreach (var result in results) { - Console.WriteLine($" - Id: {result.GetValueOrDefault("Id")}, Campi: {string.Join(", ", result.Keys)}"); + _logger.LogDebug($" - Id: {result.GetValueOrDefault("Id")}, Campi: {string.Join(", ", result.Keys)}"); } return results; } - Console.WriteLine("Nessun record trovato"); + _logger.LogDebug("Nessun record trovato"); return new List>(); } catch (Exception ex) { - Console.WriteLine($"❌ Errore durante la ricerca Salesforce: {ex.Message}"); - Console.WriteLine($"Stack Trace: {ex.StackTrace}"); + _logger.LogDebug($"❌ Errore durante la ricerca Salesforce: {ex.Message}"); + _logger.LogDebug($"Stack Trace: {ex.StackTrace}"); return new List>(); } } @@ -741,7 +776,7 @@ namespace DataConnection.REST.Implementations { if (!IsAuthenticated()) { - Console.WriteLine("Error: Not authenticated to Salesforce. Cannot perform batch queries."); + _logger.LogDebug("Error: Not authenticated to Salesforce. Cannot perform batch queries."); return new List(); } @@ -763,12 +798,12 @@ namespace DataConnection.REST.Implementations } var totalBatches = batches.Count; - Console.WriteLine($"--- Starting parallel execution of {totalBatches} query batch(es) with {queries.Count} total queries ---"); + _logger.LogDebug($"--- Starting parallel execution of {totalBatches} query batch(es) with {queries.Count} total queries ---"); // Execute all batches in parallel var batchTasks = batches.Select(async b => { - Console.WriteLine($"--- Processing Query Batch {b.batchNumber}/{totalBatches}: {b.batch.Count} queries (parallel) ---"); + _logger.LogDebug($"--- Processing Query Batch {b.batchNumber}/{totalBatches}: {b.batch.Count} queries (parallel) ---"); return await ExecuteQueryBatchAsync(b.batch, b.startIndex, cancellationToken); }); @@ -781,7 +816,7 @@ namespace DataConnection.REST.Implementations allResults.AddRange(result); } - Console.WriteLine($"All query batches completed: {allResults.Count(r => r.Success)} success, {allResults.Count(r => !r.Success)} failed"); + _logger.LogDebug($"All query batches completed: {allResults.Count(r => r.Success)} success, {allResults.Count(r => !r.Success)} failed"); return allResults.OrderBy(r => r.QueryIndex).ToList(); } @@ -818,8 +853,8 @@ namespace DataConnection.REST.Implementations if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Salesforce Batch Query failed: {response.StatusCode}"); - Console.WriteLine($"Error details: {errorContent}"); + _logger.LogDebug($"Salesforce Batch Query failed: {response.StatusCode}"); + _logger.LogDebug($"Error details: {errorContent}"); // Return error results for all queries in this batch return queries.Select((query, index) => new BatchQueryResult @@ -884,7 +919,7 @@ namespace DataConnection.REST.Implementations } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce batch query: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce batch query: {ex.Message}"); // Return error results for all queries in this batch return queries.Select((query, index) => new BatchQueryResult @@ -909,12 +944,12 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Salesforce Batch Entity Search: {entityName} ---"); - Console.WriteLine($"Searching for {keyFieldsList.Count} different key combinations"); + _logger.LogDebug($"--- Starting Salesforce Batch Entity Search: {entityName} ---"); + _logger.LogDebug($"Searching for {keyFieldsList.Count} different key combinations"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for batch entity search"); + _logger.LogDebug("Authentication failed for batch entity search"); return new Dictionary>>(); } @@ -937,12 +972,12 @@ namespace DataConnection.REST.Implementations var query = $"SELECT Id FROM {entityName} WHERE {string.Join(" AND ", whereConditions)}"; queries.Add(query); - Console.WriteLine($"Query {i}: {query}"); + _logger.LogDebug($"Query {i}: {query}"); } if (!queries.Any()) { - Console.WriteLine("No valid queries generated for batch search"); + _logger.LogDebug("No valid queries generated for batch search"); return new Dictionary>>(); } @@ -957,13 +992,13 @@ namespace DataConnection.REST.Implementations } var totalFound = results.Values.Sum(list => list.Count); - Console.WriteLine($"Batch entity search completed: {totalFound} total entities found across {results.Count} queries"); + _logger.LogDebug($"Batch entity search completed: {totalFound} total entities found across {results.Count} queries"); return results; } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce batch entity search: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce batch entity search: {ex.Message}"); return new Dictionary>>(); } } @@ -980,12 +1015,12 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Salesforce Batch Entity Retrieval: {entityName} ---"); - Console.WriteLine($"Retrieving {entityIds.Count} entities by ID"); + _logger.LogDebug($"--- Starting Salesforce Batch Entity Retrieval: {entityName} ---"); + _logger.LogDebug($"Retrieving {entityIds.Count} entities by ID"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for batch entity retrieval"); + _logger.LogDebug("Authentication failed for batch entity retrieval"); return new List>(); } @@ -1024,7 +1059,7 @@ namespace DataConnection.REST.Implementations queries.Add(query); } - Console.WriteLine($"Created {queries.Count} batch queries for {entityIds.Count} entity IDs"); + _logger.LogDebug($"Created {queries.Count} batch queries for {entityIds.Count} entity IDs"); // Execute batch queries var batchResults = await BatchExecuteQueriesAsync(queries, cancellationToken); @@ -1036,13 +1071,13 @@ namespace DataConnection.REST.Implementations allRecords.AddRange(batchResult.Records); } - Console.WriteLine($"Successfully retrieved {allRecords.Count} entities out of {entityIds.Count} requested"); + _logger.LogDebug($"Successfully retrieved {allRecords.Count} entities out of {entityIds.Count} requested"); return allRecords; } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce batch entity retrieval: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce batch entity retrieval: {ex.Message}"); return new List>(); } } @@ -1060,11 +1095,11 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Salesforce Full Entity Extraction: {entityName} ---"); + _logger.LogDebug($"--- Starting Salesforce Full Entity Extraction: {entityName} ---"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for entity extraction"); + _logger.LogDebug("Authentication failed for entity extraction"); return new List>(); } @@ -1100,7 +1135,7 @@ namespace DataConnection.REST.Implementations } query += " ORDER BY Id"; // Add ordering for consistent pagination - Console.WriteLine($"Initial extraction query: {query}"); + _logger.LogDebug($"Initial extraction query: {query}"); var allRecords = new List>(); var currentQuery = query; @@ -1110,7 +1145,7 @@ namespace DataConnection.REST.Implementations while (hasMore && (maxRecords == 0 || allRecords.Count < maxRecords)) { pageCount++; - Console.WriteLine($"Extracting page {pageCount}..."); + _logger.LogDebug($"Extracting page {pageCount}..."); var encodedQuery = Uri.EscapeDataString(currentQuery); var queryEndpoint = $"/services/data/v60.0/query/?q={encodedQuery}"; @@ -1132,7 +1167,7 @@ namespace DataConnection.REST.Implementations } allRecords.AddRange(pageRecords); - Console.WriteLine($"Page {pageCount}: Retrieved {pageRecords.Count} records, total: {allRecords.Count}"); + _logger.LogDebug($"Page {pageCount}: Retrieved {pageRecords.Count} records, total: {allRecords.Count}"); // Check if there are more records hasMore = !response.Done && !string.IsNullOrEmpty(response.NextRecordsUrl); @@ -1146,24 +1181,24 @@ namespace DataConnection.REST.Implementations } else { - Console.WriteLine($"No response received for page {pageCount}"); + _logger.LogDebug($"No response received for page {pageCount}"); hasMore = false; } // Prevent infinite loops if (pageCount > 1000) { - Console.WriteLine("Maximum page limit reached (1000), stopping extraction"); + _logger.LogDebug("Maximum page limit reached (1000), stopping extraction"); break; } } - Console.WriteLine($"Entity extraction completed: {allRecords.Count} total records extracted in {pageCount} pages"); + _logger.LogDebug($"Entity extraction completed: {allRecords.Count} total records extracted in {pageCount} pages"); return allRecords; } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce entity extraction: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce entity extraction: {ex.Message}"); return new List>(); } } @@ -1182,12 +1217,12 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Salesforce Parallel Entity Extraction: {entityName} ---"); - Console.WriteLine($"Using {whereClauses.Count} parallel extraction criteria"); + _logger.LogDebug($"--- Starting Salesforce Parallel Entity Extraction: {entityName} ---"); + _logger.LogDebug($"Using {whereClauses.Count} parallel extraction criteria"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for parallel entity extraction"); + _logger.LogDebug("Authentication failed for parallel entity extraction"); return new List>(); } @@ -1203,14 +1238,14 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"Starting parallel extraction {index + 1}/{whereClauses.Count}: {whereClause}"); + _logger.LogDebug($"Starting parallel extraction {index + 1}/{whereClauses.Count}: {whereClause}"); var records = await ExtractAllEntitiesAsync(entityName, fieldsToSelect, whereClause, maxRecordsPerQuery, cancellationToken); - Console.WriteLine($"Parallel extraction {index + 1} completed: {records.Count} records"); + _logger.LogDebug($"Parallel extraction {index + 1} completed: {records.Count} records"); return records; } catch (Exception ex) { - Console.WriteLine($"Error in parallel extraction {index + 1}: {ex.Message}"); + _logger.LogDebug($"Error in parallel extraction {index + 1}: {ex.Message}"); return new List>(); } }).ToList(); @@ -1234,15 +1269,15 @@ namespace DataConnection.REST.Implementations var duplicatesRemoved = combinedRecords.Count - deduplicatedRecords.Count; if (duplicatesRemoved > 0) { - Console.WriteLine($"Removed {duplicatesRemoved} duplicate records"); + _logger.LogDebug($"Removed {duplicatesRemoved} duplicate records"); } - Console.WriteLine($"Parallel entity extraction completed: {deduplicatedRecords.Count} unique records from {whereClauses.Count} parallel queries"); + _logger.LogDebug($"Parallel entity extraction completed: {deduplicatedRecords.Count} unique records from {whereClauses.Count} parallel queries"); return deduplicatedRecords; } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce parallel entity extraction: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce parallel entity extraction: {ex.Message}"); return new List>(); } } @@ -1277,7 +1312,7 @@ namespace DataConnection.REST.Implementations currentDate = chunkEndDate; } - Console.WriteLine($"Created {whereClauses.Count} date-based chunks for parallel extraction between {startDate:yyyy-MM-dd} and {endDate:yyyy-MM-dd}"); + _logger.LogDebug($"Created {whereClauses.Count} date-based chunks for parallel extraction between {startDate:yyyy-MM-dd} and {endDate:yyyy-MM-dd}"); return whereClauses; } @@ -1294,7 +1329,7 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Large Dataset Extraction: {entityName} ---"); + _logger.LogDebug($"--- Starting Large Dataset Extraction: {entityName} ---"); // First, try to get a count to determine if we need parallel processing var countQuery = $"SELECT COUNT() FROM {entityName}"; @@ -1303,7 +1338,7 @@ namespace DataConnection.REST.Implementations countQuery += $" WHERE {baseWhereClause}"; } - Console.WriteLine($"Checking dataset size with query: {countQuery}"); + _logger.LogDebug($"Checking dataset size with query: {countQuery}"); try { @@ -1311,12 +1346,12 @@ namespace DataConnection.REST.Implementations if (countResponse?.ContainsKey("totalSize") == true && int.TryParse(countResponse["totalSize"].ToString(), out int totalRecords)) { - Console.WriteLine($"Dataset contains approximately {totalRecords} records"); + _logger.LogDebug($"Dataset contains approximately {totalRecords} records"); // If dataset is large (>10,000 records), use parallel extraction if (totalRecords > 10000) { - Console.WriteLine("Large dataset detected, using parallel extraction with date-based chunking"); + _logger.LogDebug("Large dataset detected, using parallel extraction with date-based chunking"); // Create date-based chunks for the last 2 years by default var endDate = DateTime.UtcNow; @@ -1336,16 +1371,16 @@ namespace DataConnection.REST.Implementations } catch (Exception ex) { - Console.WriteLine($"Could not determine dataset size, proceeding with standard extraction: {ex.Message}"); + _logger.LogDebug($"Could not determine dataset size, proceeding with standard extraction: {ex.Message}"); } // For smaller datasets or if count failed, use standard extraction - Console.WriteLine("Using standard sequential extraction"); + _logger.LogDebug("Using standard sequential extraction"); return await ExtractAllEntitiesAsync(entityName, fieldsToSelect, baseWhereClause, maxRecords, cancellationToken); } catch (Exception ex) { - Console.WriteLine($"Error during large dataset extraction: {ex.Message}"); + _logger.LogDebug($"Error during large dataset extraction: {ex.Message}"); return new List>(); } } @@ -1365,14 +1400,14 @@ namespace DataConnection.REST.Implementations var startTime = DateTime.UtcNow.AddHours(-hoursBack); var whereClause = $"LastModifiedDate >= {startTime:yyyy-MM-ddTHH:mm:ssZ}"; - Console.WriteLine($"--- Extracting recently modified {entityName} (last {hoursBack} hours) ---"); - Console.WriteLine($"Using WHERE clause: {whereClause}"); + _logger.LogDebug($"--- Extracting recently modified {entityName} (last {hoursBack} hours) ---"); + _logger.LogDebug($"Using WHERE clause: {whereClause}"); return await ExtractAllEntitiesAsync(entityName, fieldsToSelect, whereClause, 0, cancellationToken); } catch (Exception ex) { - Console.WriteLine($"Error during recent entities extraction: {ex.Message}"); + _logger.LogDebug($"Error during recent entities extraction: {ex.Message}"); return new List>(); } } /// @@ -1386,11 +1421,11 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Salesforce Entity Delete: {entityName}/{entityId} ---"); + _logger.LogDebug($"--- Starting Salesforce Entity Delete: {entityName}/{entityId} ---"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for entity deletion"); + _logger.LogDebug("Authentication failed for entity deletion"); return false; } @@ -1404,19 +1439,19 @@ namespace DataConnection.REST.Implementations if (response.IsSuccessStatusCode) { - Console.WriteLine($"Entity {entityName}/{entityId} deleted successfully"); + _logger.LogDebug($"Entity {entityName}/{entityId} deleted successfully"); return true; } else { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Failed to delete entity {entityName}/{entityId}. Status: {response.StatusCode}, Error: {errorContent}"); + _logger.LogDebug($"Failed to delete entity {entityName}/{entityId}. Status: {response.StatusCode}, Error: {errorContent}"); return false; } } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce entity deletion: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce entity deletion: {ex.Message}"); return false; } } @@ -1433,11 +1468,11 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Starting Salesforce Entity Update: {entityName}/{entityId} ---"); + _logger.LogDebug($"--- Starting Salesforce Entity Update: {entityName}/{entityId} ---"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for entity update"); + _logger.LogDebug("Authentication failed for entity update"); return null; } @@ -1452,7 +1487,7 @@ namespace DataConnection.REST.Implementations if (response.IsSuccessStatusCode) { - Console.WriteLine($"Entity {entityName}/{entityId} updated successfully"); + _logger.LogDebug($"Entity {entityName}/{entityId} updated successfully"); // Ritorna i dati aggiornati includendo l'ID var updatedData = new Dictionary(entityData) @@ -1464,13 +1499,13 @@ namespace DataConnection.REST.Implementations else { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Failed to update entity {entityName}/{entityId}. Status: {response.StatusCode}, Error: {errorContent}"); + _logger.LogDebug($"Failed to update entity {entityName}/{entityId}. Status: {response.StatusCode}, Error: {errorContent}"); return null; } } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce entity update: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce entity update: {ex.Message}"); return null; } } @@ -1487,15 +1522,15 @@ namespace DataConnection.REST.Implementations return true; } - Console.WriteLine("Client not authenticated, attempting to authenticate..."); + _logger.LogDebug("Client not authenticated, attempting to authenticate..."); return await AuthenticateAsync(cancellationToken); } /// - /// Normalizza i valori numerici in un dictionary per garantire compatibilità con Salesforce API + /// Normalizza i valori numerici in un dictionary per garantire compatibilità con Salesforce API /// Converte valori decimali con virgola in formato con punto decimale /// - private static Dictionary NormalizeNumericValues(Dictionary data) + private Dictionary NormalizeNumericValues(Dictionary data) { var normalizedData = new Dictionary(); bool hasNormalized = false; @@ -1506,7 +1541,7 @@ namespace DataConnection.REST.Implementations if (value != null) { - // Se è una stringa che rappresenta un numero decimale con virgola, convertila + // Se è una stringa che rappresenta un numero decimale con virgola, convertila if (value is string stringValue && IsNumericWithComma(stringValue)) { if (decimal.TryParse(stringValue, System.Globalization.NumberStyles.Number, @@ -1515,7 +1550,7 @@ namespace DataConnection.REST.Implementations // Converte in double usando cultura invariante per garantire punto decimale var normalizedValue = double.Parse(decimalValue.ToString(System.Globalization.CultureInfo.InvariantCulture)); normalizedData[kvp.Key] = normalizedValue; - Console.WriteLine($"NUMERIC NORMALIZATION: {kvp.Key}: '{stringValue}' → {normalizedValue}"); + _logger.LogDebug($"NUMERIC NORMALIZATION: {kvp.Key}: '{stringValue}' → {normalizedValue}"); hasNormalized = true; } else @@ -1523,12 +1558,12 @@ namespace DataConnection.REST.Implementations normalizedData[kvp.Key] = value; } } - // Se è già un decimal, convertilo in double con cultura invariante + // Se è già un decimal, convertilo in double con cultura invariante else if (value is decimal dec) { var normalizedValue = double.Parse(dec.ToString(System.Globalization.CultureInfo.InvariantCulture)); normalizedData[kvp.Key] = normalizedValue; - Console.WriteLine($"DECIMAL NORMALIZATION: {kvp.Key}: {dec} → {normalizedValue}"); + _logger.LogDebug($"DECIMAL NORMALIZATION: {kvp.Key}: {dec} → {normalizedValue}"); hasNormalized = true; } else @@ -1544,7 +1579,7 @@ namespace DataConnection.REST.Implementations if (hasNormalized) { - Console.WriteLine($"NORMALIZATION SUMMARY: Processed {data.Count} fields, normalized {normalizedData.Count(kvp => kvp.Value is double)} numeric values"); + _logger.LogDebug($"NORMALIZATION SUMMARY: Processed {data.Count} fields, normalized {normalizedData.Count(kvp => kvp.Value is double)} numeric values"); } return normalizedData; @@ -1631,17 +1666,17 @@ namespace DataConnection.REST.Implementations { try { - Console.WriteLine($"--- Searching for duplicates in {entityName} by required fields ---"); + _logger.LogDebug($"--- Searching for duplicates in {entityName} by required fields ---"); if (!await EnsureAuthenticatedAsync(cancellationToken)) { - Console.WriteLine("Authentication failed for required fields search"); + _logger.LogDebug("Authentication failed for required fields search"); return new List>(); } if (!requiredFields.Any()) { - Console.WriteLine("No required fields provided for duplicate search"); + _logger.LogDebug("No required fields provided for duplicate search"); return new List>(); } @@ -1653,16 +1688,16 @@ namespace DataConnection.REST.Implementations if (batchResults.ContainsKey(0)) { var results = batchResults[0]; - Console.WriteLine($"Found {results.Count} potential duplicates for required fields: {string.Join(", ", requiredFields.Select(kv => $"{kv.Key}={kv.Value}"))}"); + _logger.LogDebug($"Found {results.Count} potential duplicates for required fields: {string.Join(", ", requiredFields.Select(kv => $"{kv.Key}={kv.Value}"))}"); return results; } - Console.WriteLine("No duplicates found"); + _logger.LogDebug("No duplicates found"); return new List>(); } catch (Exception ex) { - Console.WriteLine($"Error during required fields search: {ex.Message}"); + _logger.LogDebug($"Error during required fields search: {ex.Message}"); return new List>(); } } @@ -1812,7 +1847,7 @@ namespace DataConnection.REST.Implementations { if (!IsAuthenticated()) { - Console.WriteLine("Error: Not authenticated to Salesforce. Cannot perform batch create."); + _logger.LogDebug("Error: Not authenticated to Salesforce. Cannot perform batch create."); return new List(); } @@ -1834,12 +1869,12 @@ namespace DataConnection.REST.Implementations } var totalBatches = batches.Count; - Console.WriteLine($"--- Starting parallel processing of {totalBatches} batch(es) with {entityDataList.Count} total records ---"); + _logger.LogDebug($"--- Starting parallel processing of {totalBatches} batch(es) with {entityDataList.Count} total records ---"); // Execute all batches in parallel var batchTasks = batches.Select(async b => { - Console.WriteLine($"--- Processing Batch {b.batchNumber}/{totalBatches}: {b.batch.Count} records (parallel) ---"); + _logger.LogDebug($"--- Processing Batch {b.batchNumber}/{totalBatches}: {b.batch.Count} records (parallel) ---"); return await ExecuteCreateBatchAsync(entityName, b.batch, b.startIndex, cancellationToken); }); @@ -1852,7 +1887,7 @@ namespace DataConnection.REST.Implementations allResults.AddRange(result); } - Console.WriteLine($"All batches completed: {allResults.Count(r => r.Success)} success, {allResults.Count(r => !r.Success)} failed"); + _logger.LogDebug($"All batches completed: {allResults.Count(r => r.Success)} success, {allResults.Count(r => !r.Success)} failed"); return allResults; } @@ -1893,8 +1928,8 @@ namespace DataConnection.REST.Implementations if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Salesforce Batch Create failed: {response.StatusCode}"); - Console.WriteLine($"Error details: {errorContent}"); + _logger.LogDebug($"Salesforce Batch Create failed: {response.StatusCode}"); + _logger.LogDebug($"Error details: {errorContent}"); // Return error results for all operations in this batch return batch.Select((_, index) => new CompositeOperationResult @@ -1948,7 +1983,7 @@ namespace DataConnection.REST.Implementations } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce batch create: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce batch create: {ex.Message}"); // Return error results for all operations in this batch return batch.Select((_, index) => new CompositeOperationResult @@ -1971,7 +2006,7 @@ namespace DataConnection.REST.Implementations { if (!IsAuthenticated()) { - Console.WriteLine("Error: Not authenticated to Salesforce. Cannot perform batch update."); + _logger.LogDebug("Error: Not authenticated to Salesforce. Cannot perform batch update."); return new List(); } @@ -1994,12 +2029,12 @@ namespace DataConnection.REST.Implementations } var totalBatches = batches.Count; - Console.WriteLine($"--- Starting parallel processing of {totalBatches} update batch(es) with {updateList.Count} total records ---"); + _logger.LogDebug($"--- Starting parallel processing of {totalBatches} update batch(es) with {updateList.Count} total records ---"); // Execute all batches in parallel var batchTasks = batches.Select(async b => { - Console.WriteLine($"--- Processing Update Batch {b.batchNumber}/{totalBatches}: {b.batch.Count} records (parallel) ---"); + _logger.LogDebug($"--- Processing Update Batch {b.batchNumber}/{totalBatches}: {b.batch.Count} records (parallel) ---"); return await ExecuteUpdateBatchAsync(entityName, b.batch, b.startIndex, cancellationToken); }); @@ -2012,7 +2047,7 @@ namespace DataConnection.REST.Implementations allResults.AddRange(result); } - Console.WriteLine($"All update batches completed: {allResults.Count(r => r.Success)} success, {allResults.Count(r => !r.Success)} failed"); + _logger.LogDebug($"All update batches completed: {allResults.Count(r => r.Success)} success, {allResults.Count(r => !r.Success)} failed"); return allResults; } @@ -2058,8 +2093,8 @@ namespace DataConnection.REST.Implementations if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine($"Salesforce Batch Update failed: {response.StatusCode}"); - Console.WriteLine($"Error details: {errorContent}"); + _logger.LogDebug($"Salesforce Batch Update failed: {response.StatusCode}"); + _logger.LogDebug($"Error details: {errorContent}"); // Return error results for all operations in this batch return batch.Select((kvp, idx) => new CompositeOperationResult @@ -2114,7 +2149,7 @@ namespace DataConnection.REST.Implementations } catch (Exception ex) { - Console.WriteLine($"Error during Salesforce batch update: {ex.Message}"); + _logger.LogDebug($"Error during Salesforce batch update: {ex.Message}"); // Return error results for all operations in this batch return batch.Select((kvp, idx) => new CompositeOperationResult @@ -2127,4 +2162,4 @@ namespace DataConnection.REST.Implementations } } } -} \ No newline at end of file +} diff --git a/Data_Coupler/Pages/CredentialManagement.razor b/Data_Coupler/Pages/CredentialManagement.razor index bdfbd49..a1e362e 100644 --- a/Data_Coupler/Pages/CredentialManagement.razor +++ b/Data_Coupler/Pages/CredentialManagement.razor @@ -663,11 +663,30 @@ else } @if (currentRestApiCredential.ServiceType == RestServiceType.Salesforce) { -
- Opzioni di Autenticazione:
- • Username/Password + Security Token: Autenticazione standard
- • Username/Password + Client ID/Secret: Autenticazione OAuth
- • Il Security Token è richiesto solo se non si configura una Connected App (Client ID/Secret) +
+
+ + + + + + @if (currentRestApiCredential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials) + { +
+ + client_credentials: il Base URL deve essere il My Domain URL della tua org + (es. https://myorg.my.salesforce.com), non login.salesforce.com.
+ Richiede: Connected App con "Enable Client Credentials Flow" attivato e un Integration User assegnato. +
+ } + else + { +
+ + password flow: Username, Password + Security Token. Client ID/Secret facoltativi (Connected App). +
+ } +
@@ -687,11 +706,15 @@ else
Esempio: 59.0
-
- - -
Token di sicurezza Salesforce (richiesto solo se non si usa OAuth o Connected App)
+ @if (currentRestApiCredential.GrantType != CredentialManager.Models.SalesforceGrantType.ClientCredentials) + { +
+ + +
Token di sicurezza Salesforce (richiesto solo se non si usa OAuth o Connected App)
+
+ }
@@ -738,8 +761,9 @@ else } - @if (currentRestApiCredential.ServiceType != RestServiceType.Generic || - (string.IsNullOrEmpty(currentRestApiCredential.ApiKey) && string.IsNullOrEmpty(currentRestApiCredential.AuthToken))) + @if ((currentRestApiCredential.ServiceType != RestServiceType.Generic || + (string.IsNullOrEmpty(currentRestApiCredential.ApiKey) && string.IsNullOrEmpty(currentRestApiCredential.AuthToken))) && + !(currentRestApiCredential.ServiceType == RestServiceType.Salesforce && currentRestApiCredential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials)) {
@@ -1312,6 +1336,7 @@ else ApiVersion = credential.ApiVersion, IsSandbox = credential.IsSandbox, UseSoapApi = credential.UseSoapApi, + GrantType = credential.GrantType, RefreshToken = credential.RefreshToken, AccessToken = credential.AccessToken, TokenExpiry = credential.TokenExpiry @@ -1533,7 +1558,8 @@ else ClientSecret = currentRestApiCredential.ClientSecret, ApiVersion = currentRestApiCredential.ApiVersion, IsSandbox = currentRestApiCredential.IsSandbox, - UseSoapApi = currentRestApiCredential.UseSoapApi + UseSoapApi = currentRestApiCredential.UseSoapApi, + GrantType = currentRestApiCredential.GrantType }; // Salviamo temporaneamente la credenziale per il test await CredentialService.SaveRestApiCredentialAsync(tempCredential); diff --git a/Data_Coupler/Services/DataConnectionFactory.cs b/Data_Coupler/Services/DataConnectionFactory.cs index efdaa2e..4139870 100644 --- a/Data_Coupler/Services/DataConnectionFactory.cs +++ b/Data_Coupler/Services/DataConnectionFactory.cs @@ -133,7 +133,9 @@ namespace Data_Coupler.Services // Per Salesforce usiamo i campi specifici ClientId e ClientSecret options.ApiKey = credential.ClientId; // ClientId -> ApiKey options.AuthToken = credential.ClientSecret; // ClientSecret -> AuthToken - _logger.LogInformation("Salesforce mapping - ClientId: '{ClientId}', ClientSecret: {HasSecret}, Username: '{Username}', Password: {HasPassword}", + 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, @@ -215,7 +217,9 @@ namespace Data_Coupler.Services { var httpClientFactory = _serviceProvider.GetRequiredService(); var httpClient = httpClientFactory.CreateClient(); - return new SalesforceServiceClient(httpClient, options); + var loggerFactory = _serviceProvider.GetService(); + var sfLogger = loggerFactory?.CreateLogger(); + return new DataConnection.REST.Implementations.SalesforceServiceClient(httpClient, options, sfLogger); } ///