-Aggiunta dei servizi di connessione REST base, di SAP e di Salesforce
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
using DataConnection.REST.Configuration;
|
using DataConnection.REST.Configuration;
|
||||||
using DataConnection.REST.Interfaces;
|
using DataConnection.REST.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -98,6 +100,35 @@ namespace DataConnection.REST.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual async Task<Dictionary<string, object>?> CreateEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Default implementation - derived classes should override this for service-specific logic
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _httpClient.PostAsJsonAsync($"/{entityName}", entityData, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
if (string.IsNullOrEmpty(responseContent))
|
||||||
|
return entityData; // Return original data if no response content
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
return JsonSerializer.Deserialize<Dictionary<string, object>>(responseContent, options);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"HTTP Request Error during entity creation: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during entity creation: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Implement other methods (PUT, DELETE, etc.) similarly
|
// Implement other methods (PUT, DELETE, etc.) similarly
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -0,0 +1,509 @@
|
|||||||
|
using DataConnection.REST.Configuration;
|
||||||
|
using DataConnection.REST.Interfaces;
|
||||||
|
using DataConnection.REST.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DataConnection.REST.Implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client specific for Salesforce REST API.
|
||||||
|
/// Handles OAuth2 authentication and metadata discovery.
|
||||||
|
/// </summary>
|
||||||
|
public class SalesforceServiceClient : BaseRestServiceClient, IRestMetadataDiscovery
|
||||||
|
{
|
||||||
|
private string? _accessToken;
|
||||||
|
private string? _instanceUrl;
|
||||||
|
private DateTime _tokenExpiry;
|
||||||
|
|
||||||
|
public SalesforceServiceClient(HttpClient httpClient, RestServiceOptions options)
|
||||||
|
: base(httpClient, options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Authenticates with Salesforce using Username/Password OAuth2 flow.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Connected App Consumer Key</param>
|
||||||
|
/// <param name="clientSecret">Connected App Consumer Secret</param>
|
||||||
|
/// <param name="username">Salesforce username</param>
|
||||||
|
/// <param name="password">Salesforce password + security token</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>True if authentication is successful</returns>
|
||||||
|
public async Task<bool> AuthenticateAsync(string clientId, string clientSecret, string username, string password, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tokenEndpoint = "/services/oauth2/token";
|
||||||
|
|
||||||
|
var tokenRequest = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new("grant_type", "password"),
|
||||||
|
new("client_id", clientId),
|
||||||
|
new("client_secret", clientSecret),
|
||||||
|
new("username", username),
|
||||||
|
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 ---");
|
||||||
|
|
||||||
|
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}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResponse = await response.Content.ReadFromJsonAsync<SalesforceTokenResponse>(cancellationToken: cancellationToken);
|
||||||
|
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}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Salesforce Authentication response could not be parsed.");
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"JSON Parsing Error during Salesforce Authentication: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during Salesforce Authentication: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the current access token is active.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAuthenticated()
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(_accessToken) && DateTime.UtcNow < _tokenExpiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers SObjects (entities) and their fields from Salesforce.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>A list of discovered entity information</returns>
|
||||||
|
public async Task<List<RestEntityInfo>> DiscoverEntitiesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!IsAuthenticated())
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Not authenticated to Salesforce. Cannot discover metadata.");
|
||||||
|
return new List<RestEntityInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var entities = new List<RestEntityInfo>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// First, get list of all SObjects
|
||||||
|
var sobjectsEndpoint = $"{_instanceUrl}/services/data/v60.0/sobjects/";
|
||||||
|
var response = await _httpClient.GetAsync(sobjectsEndpoint, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var sobjectsResponse = await response.Content.ReadFromJsonAsync<SalesforceSObjectsResponse>(cancellationToken: cancellationToken); if (sobjectsResponse?.SObjects != null)
|
||||||
|
{
|
||||||
|
// For demo purposes, limit to first 20 objects to avoid too many API calls
|
||||||
|
var limitedSObjects = sobjectsResponse.SObjects.ToList();
|
||||||
|
|
||||||
|
// Process SObjects in parallel for better performance
|
||||||
|
var semaphore = new SemaphoreSlim(20, 20); // Limit concurrent requests to 5
|
||||||
|
var tasks = limitedSObjects.Where(sobject => !string.IsNullOrEmpty(sobject.Name))
|
||||||
|
.Select(async sobject =>
|
||||||
|
{
|
||||||
|
await semaphore.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get detailed field information for each SObject
|
||||||
|
var describeEndpoint = $"{_instanceUrl}/services/data/v60.0/sobjects/{sobject.Name}/describe/";
|
||||||
|
var describeResponse = await _httpClient.GetAsync(describeEndpoint, cancellationToken);
|
||||||
|
|
||||||
|
if (describeResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var describeResult = await describeResponse.Content.ReadFromJsonAsync<SalesforceDescribeResponse>(cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (describeResult?.Fields != null)
|
||||||
|
{
|
||||||
|
var entityInfo = new RestEntityInfo
|
||||||
|
{
|
||||||
|
Name = sobject.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var field in describeResult.Fields)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(field.Name)) continue;
|
||||||
|
|
||||||
|
var propInfo = new RestPropertyInfo
|
||||||
|
{
|
||||||
|
Name = field.Name,
|
||||||
|
Type = field.Type ?? "string",
|
||||||
|
IsKey = field.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)
|
||||||
|
};
|
||||||
|
entityInfo.Properties.Add(propInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error describing SObject {sobject.Name}: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var results = await Task.WhenAll(tasks);
|
||||||
|
entities.AddRange(results.Where(result => result != null)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"HTTP Request Error during Salesforce metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"JSON Parsing Error during Salesforce metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during Salesforce metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers a list of available SObjects from Salesforce without detailed field information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>A list of discovered SObject summaries</returns>
|
||||||
|
public async Task<List<RestEntitySummary>> DiscoverEntitySummariesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!IsAuthenticated())
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Not authenticated to Salesforce. Cannot discover metadata.");
|
||||||
|
return new List<RestEntitySummary>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var entities = new List<RestEntitySummary>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get list of all SObjects
|
||||||
|
var sobjectsEndpoint = $"{_instanceUrl}/services/data/v60.0/sobjects/";
|
||||||
|
var response = await _httpClient.GetAsync(sobjectsEndpoint, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var sobjectsResponse = await response.Content.ReadFromJsonAsync<SalesforceSObjectsResponse>(cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (sobjectsResponse?.SObjects != null)
|
||||||
|
{
|
||||||
|
foreach (var sobject in sobjectsResponse.SObjects)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(sobject.Name)) continue;
|
||||||
|
|
||||||
|
var entitySummary = new RestEntitySummary
|
||||||
|
{
|
||||||
|
Name = sobject.Name,
|
||||||
|
Label = sobject.Label ?? sobject.Name,
|
||||||
|
IsCustom = sobject.Custom,
|
||||||
|
EntityType = "SObject"
|
||||||
|
};
|
||||||
|
entities.Add(entitySummary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"HTTP Request Error during Salesforce metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"JSON Parsing Error during Salesforce metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during Salesforce metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities.OrderBy(e => e.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers detailed information for a specific SObject including all its fields.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityName">The name of the SObject to get details for</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>Detailed SObject information or null if not found</returns>
|
||||||
|
public async Task<RestEntityInfo?> DiscoverEntityDetailsAsync(string entityName, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!IsAuthenticated())
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Not authenticated to Salesforce. Cannot discover entity details.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get detailed field information for the specific SObject
|
||||||
|
var describeEndpoint = $"{_instanceUrl}/services/data/v60.0/sobjects/{entityName}/describe/";
|
||||||
|
var describeResponse = await _httpClient.GetAsync(describeEndpoint, cancellationToken);
|
||||||
|
|
||||||
|
if (!describeResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to get details for SObject {entityName}: {describeResponse.StatusCode}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var describeResult = await describeResponse.Content.ReadFromJsonAsync<SalesforceDescribeResponse>(cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (describeResult?.Fields != null)
|
||||||
|
{
|
||||||
|
var entityInfo = new RestEntityInfo
|
||||||
|
{
|
||||||
|
Name = entityName
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var field in describeResult.Fields)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(field.Name)) continue;
|
||||||
|
|
||||||
|
var propInfo = new RestPropertyInfo
|
||||||
|
{
|
||||||
|
Name = field.Name,
|
||||||
|
Type = field.Type ?? "string",
|
||||||
|
IsKey = field.Name.Equals("Id", StringComparison.OrdinalIgnoreCase),
|
||||||
|
IsRequired = !field.Nillable,
|
||||||
|
MaxLength = field.Length > 0 ? field.Length : null
|
||||||
|
};
|
||||||
|
entityInfo.Properties.Add(propInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"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}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during Salesforce entity details discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new SObject in Salesforce.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityName">The name of the SObject to create (e.g., "Account", "Contact").</param>
|
||||||
|
/// <param name="entityData">The data for the new SObject as key-value pairs.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>The created entity data or null if creation failed.</returns>
|
||||||
|
public override async Task<Dictionary<string, object>?> CreateEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!IsAuthenticated())
|
||||||
|
{
|
||||||
|
Console.WriteLine("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: {System.Text.Json.JsonSerializer.Serialize(entityData)}");
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsJsonAsync(createUri, entityData, cancellationToken);
|
||||||
|
|
||||||
|
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) ---");
|
||||||
|
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) ---");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(responseContent))
|
||||||
|
return entityData; // Return original data if no response content
|
||||||
|
|
||||||
|
// Salesforce returns creation result with Id and success status
|
||||||
|
var creationResult = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(responseContent);
|
||||||
|
|
||||||
|
// Merge the original data with the creation result (which includes the new Id)
|
||||||
|
if (creationResult != null)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, object>(entityData);
|
||||||
|
foreach (var kvp in creationResult)
|
||||||
|
{
|
||||||
|
result[kvp.Key] = kvp.Value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationResult;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"HTTP Request Error during Salesforce entity creation: {ex.Message}");
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
|
||||||
|
}
|
||||||
|
Console.WriteLine($"--- 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) ---");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during Salesforce entity creation: {ex.Message}");
|
||||||
|
Console.WriteLine($"--- End Salesforce Entity Creation Attempt (Exception) ---");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Nested classes for deserializing Salesforce responses ---
|
||||||
|
private class SalesforceTokenResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("access_token")]
|
||||||
|
public string AccessToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("instance_url")]
|
||||||
|
public string InstanceUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("token_type")]
|
||||||
|
public string TokenType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("issued_at")]
|
||||||
|
public string IssuedAt { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("signature")]
|
||||||
|
public string Signature { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SalesforceSObjectsResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("encoding")]
|
||||||
|
public string Encoding { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("maxBatchSize")]
|
||||||
|
public int MaxBatchSize { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("sobjects")]
|
||||||
|
public List<SalesforceSObject> SObjects { get; set; } = new List<SalesforceSObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SalesforceSObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("label")]
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("custom")]
|
||||||
|
public bool Custom { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("keyPrefix")]
|
||||||
|
public string KeyPrefix { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SalesforceDescribeResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("label")]
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("fields")]
|
||||||
|
public List<SalesforceField> Fields { get; set; } = new List<SalesforceField>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SalesforceField
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("label")]
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("length")]
|
||||||
|
public int Length { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("nillable")]
|
||||||
|
public bool Nillable { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("unique")]
|
||||||
|
public bool Unique { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using DataConnection.REST.Configuration;
|
using DataConnection.REST.Configuration;
|
||||||
using DataConnection.REST.Interfaces;
|
using DataConnection.REST.Interfaces;
|
||||||
using DataConnection.REST.Models; // Added for metadata models
|
using DataConnection.REST.Models; // Added for metadata models
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -9,6 +10,7 @@ using System.Net.Http;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@@ -79,11 +81,19 @@ namespace DataConnection.REST.Implementations
|
|||||||
public async Task<bool> LoginAsync(string companyDB, string userName, string password, CancellationToken cancellationToken = default)
|
public async Task<bool> LoginAsync(string companyDB, string userName, string password, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var loginPayload = new { CompanyDB = companyDB, UserName = userName, Password = password };
|
var loginPayload = new { CompanyDB = companyDB, UserName = userName, Password = password };
|
||||||
var loginUri = "/b1s/v2/Login"; // Assuming v1, adjust if needed
|
var loginUri = "/b1s/v2/Login"; // Assuming v2, adjust if needed // --- Logging Aggiunto ---
|
||||||
|
var absoluteUri = _sapHttpClient.BaseAddress != null ? new Uri(_sapHttpClient.BaseAddress, loginUri).ToString() : loginUri;
|
||||||
|
Console.WriteLine($"--- SAP B1 Login Attempt ---");
|
||||||
|
Console.WriteLine($"Target URL: {absoluteUri}");
|
||||||
|
Console.WriteLine($"Payload: CompanyDB='{companyDB}', UserName='{userName}', Password='{(string.IsNullOrEmpty(password) ? "" : password.Substring(0, Math.Min(1, password.Length)) + "...")}'"); // Non loggare la password completa in produzione!
|
||||||
|
// Verifica se IgnoreSslErrors è attivo (preso dalle opzioni passate al costruttore)
|
||||||
|
Console.WriteLine($"Ignore SSL Errors: {_options.IgnoreSslErrors}");
|
||||||
|
// --- Fine Logging ---
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Use the internal HttpClient configured with CookieContainer
|
// Use the internal HttpClient configured with CookieContainer
|
||||||
|
// Pass the loginPayload object directly, PostAsJsonAsync handles serialization
|
||||||
var response = await _sapHttpClient.PostAsJsonAsync(loginUri, loginPayload, cancellationToken);
|
var response = await _sapHttpClient.PostAsJsonAsync(loginUri, loginPayload, cancellationToken);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
@@ -92,6 +102,7 @@ namespace DataConnection.REST.Implementations
|
|||||||
Console.WriteLine($"SAP B1 Login failed: {response.StatusCode}");
|
Console.WriteLine($"SAP B1 Login failed: {response.StatusCode}");
|
||||||
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
Console.WriteLine($"Error details: {errorContent}");
|
Console.WriteLine($"Error details: {errorContent}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Login Attempt (Failed) ---"); // Log fine tentativo
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +118,7 @@ namespace DataConnection.REST.Implementations
|
|||||||
_sessionTimeout = DateTime.UtcNow.AddSeconds(sessionInfo.SessionTimeout);
|
_sessionTimeout = DateTime.UtcNow.AddSeconds(sessionInfo.SessionTimeout);
|
||||||
|
|
||||||
Console.WriteLine($"SAP B1 Login successful. Session expires around: {_sessionTimeout.ToLocalTime()}");
|
Console.WriteLine($"SAP B1 Login successful. Session expires around: {_sessionTimeout.ToLocalTime()}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Login Attempt (Success) ---"); // Log fine tentativo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,18 +128,30 @@ namespace DataConnection.REST.Implementations
|
|||||||
catch (HttpRequestException ex)
|
catch (HttpRequestException ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"HTTP Request Error during SAP B1 Login: {ex.Message}");
|
Console.WriteLine($"HTTP Request Error during SAP B1 Login: {ex.Message}");
|
||||||
|
// Logga dettagli sull'eccezione interna se presente (spesso utile per problemi SSL)
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
|
||||||
|
}
|
||||||
|
Console.WriteLine($"--- End SAP B1 Login Attempt (Exception) ---"); // Log fine tentativo
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (JsonException ex)
|
catch (JsonException ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"JSON Parsing Error during SAP B1 Login: {ex.Message}");
|
Console.WriteLine($"JSON Parsing Error during SAP B1 Login: {ex.Message}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Login Attempt (JsonException) ---"); // Log fine tentativo
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error during SAP B1 Login: {ex.Message}");
|
Console.WriteLine($"Error during SAP B1 Login: {ex.Message}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Login Attempt (Exception) ---"); // Log fine tentativo
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// finally // Aggiunto per sicurezza - Rimosso perchè non necessario qui
|
||||||
|
// {
|
||||||
|
// // Potrebbe essere utile loggare qui se necessario
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -276,6 +300,248 @@ namespace DataConnection.REST.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new entity in SAP Business One using the Service Layer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityName">The name of the entity to create (e.g., "Items", "BusinessPartners").</param>
|
||||||
|
/// <param name="entityData">The data for the new entity as key-value pairs.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>The created entity data or null if creation failed.</returns>
|
||||||
|
public override async Task<Dictionary<string, object>?> CreateEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Ensure session is active before attempting creation
|
||||||
|
if (!IsSessionActive())
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Not logged into SAP B1 Service Layer. Cannot create entity.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAP B1 Service Layer typically uses plural entity names for endpoints
|
||||||
|
var createUri = $"/b1s/v2/{entityName}";
|
||||||
|
|
||||||
|
try
|
||||||
|
{ Console.WriteLine($"--- SAP B1 Entity Creation Attempt ---");
|
||||||
|
Console.WriteLine($"Entity: {entityName}");
|
||||||
|
Console.WriteLine($"Target URL: {(_sapHttpClient.BaseAddress != null ? new Uri(_sapHttpClient.BaseAddress, createUri) : createUri)}");
|
||||||
|
Console.WriteLine($"Data: {System.Text.Json.JsonSerializer.Serialize(entityData)}");
|
||||||
|
|
||||||
|
var response = await _sapHttpClient.PostAsJsonAsync(createUri, entityData, cancellationToken);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
Console.WriteLine($"SAP B1 Entity Creation failed: {response.StatusCode}");
|
||||||
|
Console.WriteLine($"Error details: {errorContent}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Entity Creation Attempt (Failed) ---");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
Console.WriteLine($"SAP B1 Entity Creation successful");
|
||||||
|
Console.WriteLine($"Response: {responseContent}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Entity Creation Attempt (Success) ---");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(responseContent))
|
||||||
|
return entityData; // Return original data if no response content
|
||||||
|
|
||||||
|
return System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(responseContent);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"HTTP Request Error during SAP B1 entity creation: {ex.Message}");
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
|
||||||
|
}
|
||||||
|
Console.WriteLine($"--- End SAP B1 Entity Creation Attempt (Exception) ---");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"JSON Parsing Error during SAP B1 entity creation: {ex.Message}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Entity Creation Attempt (JsonException) ---");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during SAP B1 entity creation: {ex.Message}");
|
||||||
|
Console.WriteLine($"--- End SAP B1 Entity Creation Attempt (Exception) ---");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers a list of available entities from SAP B1 Service Layer without detailed field information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>A list of discovered entity summaries</returns>
|
||||||
|
public async Task<List<RestEntitySummary>> DiscoverEntitySummariesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!IsSessionActive())
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Not logged into SAP B1 Service Layer. Cannot discover metadata.");
|
||||||
|
return new List<RestEntitySummary>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var entities = new List<RestEntitySummary>();
|
||||||
|
var metadataUri = "/b1s/v2/$metadata";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _sapHttpClient.GetAsync(metadataUri, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var xmlContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
var edmx = XDocument.Parse(xmlContent);
|
||||||
|
|
||||||
|
// Define XML namespaces
|
||||||
|
XNamespace edm = "http://schemas.microsoft.com/ado/2009/11/edm";
|
||||||
|
XNamespace edmxNs = "http://schemas.microsoft.com/ado/2007/06/edmx";
|
||||||
|
XNamespace defaultEdmNs = edmx.Root?.GetDefaultNamespace() ?? edm;
|
||||||
|
XNamespace dataServicesNs = edmx.Root?.Element(edmxNs + "DataServices")?.GetDefaultNamespace() ?? defaultEdmNs;
|
||||||
|
|
||||||
|
var schemaElement = edmx.Descendants(defaultEdmNs + "Schema").FirstOrDefault() ??
|
||||||
|
edmx.Descendants(dataServicesNs + "Schema").FirstOrDefault();
|
||||||
|
|
||||||
|
if (schemaElement == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Could not find Schema element in $metadata response.");
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all EntityType elements within the Schema
|
||||||
|
foreach (var entityTypeElement in schemaElement.Elements(defaultEdmNs + "EntityType"))
|
||||||
|
{
|
||||||
|
var entityName = entityTypeElement.Attribute("Name")?.Value;
|
||||||
|
if (string.IsNullOrEmpty(entityName)) continue;
|
||||||
|
|
||||||
|
var entitySummary = new RestEntitySummary
|
||||||
|
{
|
||||||
|
Name = entityName,
|
||||||
|
Label = entityName, // SAP B1 typically doesn't have separate labels
|
||||||
|
IsCustom = entityName.StartsWith("U_", StringComparison.OrdinalIgnoreCase),
|
||||||
|
EntityType = "EntityType"
|
||||||
|
};
|
||||||
|
entities.Add(entitySummary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"HTTP Request Error during SAP B1 metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (XmlException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"XML Parsing Error during SAP B1 metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during SAP B1 metadata discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities.OrderBy(e => e.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers detailed information for a specific entity including all its properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityName">The name of the entity to get details for</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>Detailed entity information or null if not found</returns>
|
||||||
|
public async Task<RestEntityInfo?> DiscoverEntityDetailsAsync(string entityName, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!IsSessionActive())
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Not logged into SAP B1 Service Layer. Cannot discover entity details.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadataUri = "/b1s/v2/$metadata";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _sapHttpClient.GetAsync(metadataUri, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var xmlContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
var edmx = XDocument.Parse(xmlContent);
|
||||||
|
|
||||||
|
// Define XML namespaces
|
||||||
|
XNamespace edm = "http://schemas.microsoft.com/ado/2009/11/edm";
|
||||||
|
XNamespace edmxNs = "http://schemas.microsoft.com/ado/2007/06/edmx";
|
||||||
|
XNamespace defaultEdmNs = edmx.Root?.GetDefaultNamespace() ?? edm;
|
||||||
|
XNamespace dataServicesNs = edmx.Root?.Element(edmxNs + "DataServices")?.GetDefaultNamespace() ?? defaultEdmNs;
|
||||||
|
|
||||||
|
var schemaElement = edmx.Descendants(defaultEdmNs + "Schema").FirstOrDefault() ??
|
||||||
|
edmx.Descendants(dataServicesNs + "Schema").FirstOrDefault();
|
||||||
|
|
||||||
|
if (schemaElement == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Could not find Schema element in $metadata response.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the specific EntityType element
|
||||||
|
var entityTypeElement = schemaElement.Elements(defaultEdmNs + "EntityType")
|
||||||
|
.FirstOrDefault(e => e.Attribute("Name")?.Value == entityName);
|
||||||
|
|
||||||
|
if (entityTypeElement == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error: Could not find EntityType '{entityName}' in $metadata response.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityInfo = new RestEntityInfo
|
||||||
|
{
|
||||||
|
Name = entityName
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find key properties for this entity type
|
||||||
|
var keyProperties = new HashSet<string>();
|
||||||
|
var keyElement = entityTypeElement.Element(defaultEdmNs + "Key");
|
||||||
|
if (keyElement != null)
|
||||||
|
{
|
||||||
|
foreach (var propRef in keyElement.Elements(defaultEdmNs + "PropertyRef"))
|
||||||
|
{
|
||||||
|
var keyName = propRef.Attribute("Name")?.Value;
|
||||||
|
if (!string.IsNullOrEmpty(keyName)) keyProperties.Add(keyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find properties for this entity type
|
||||||
|
foreach (var propertyElement in entityTypeElement.Elements(defaultEdmNs + "Property"))
|
||||||
|
{
|
||||||
|
var propName = propertyElement.Attribute("Name")?.Value;
|
||||||
|
if (string.IsNullOrEmpty(propName)) continue;
|
||||||
|
|
||||||
|
var propInfo = new RestPropertyInfo
|
||||||
|
{
|
||||||
|
Name = propName,
|
||||||
|
Type = propertyElement.Attribute("Type")?.Value ?? string.Empty,
|
||||||
|
IsKey = keyProperties.Contains(propName),
|
||||||
|
IsRequired = propertyElement.Attribute("Nullable")?.Value?.ToLower() == "false",
|
||||||
|
MaxLength = int.TryParse(propertyElement.Attribute("MaxLength")?.Value, out var maxLen) ? maxLen : null
|
||||||
|
};
|
||||||
|
entityInfo.Properties.Add(propInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityInfo;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"HTTP Request Error during SAP B1 entity details discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (XmlException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"XML Parsing Error during SAP B1 entity details discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during SAP B1 entity details discovery: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to get cookie value
|
// Helper to get cookie value
|
||||||
private string? GetCookieValue(string cookieName)
|
private string? GetCookieValue(string cookieName)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,21 @@ namespace DataConnection.REST.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRestMetadataDiscovery
|
public interface IRestMetadataDiscovery
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers a list of available entities from the REST service without detailed field information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>A list of discovered entity summaries.</returns>
|
||||||
|
Task<List<RestEntitySummary>> DiscoverEntitySummariesAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers detailed information for a specific entity including all its properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityName">The name of the entity to get details for.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>Detailed entity information or null if not found.</returns>
|
||||||
|
Task<RestEntityInfo?> DiscoverEntityDetailsAsync(string entityName, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Discovers entities and their properties from the REST service metadata.
|
/// Discovers entities and their properties from the REST service metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DataConnection.REST.Interfaces
|
namespace DataConnection.REST.Interfaces
|
||||||
@@ -27,6 +28,15 @@ namespace DataConnection.REST.Interfaces
|
|||||||
/// <returns>The deserialized response content.</returns>
|
/// <returns>The deserialized response content.</returns>
|
||||||
Task<TResponse?> PostAsync<TRequest, TResponse>(string requestUri, TRequest payload, CancellationToken cancellationToken = default);
|
Task<TResponse?> PostAsync<TRequest, TResponse>(string requestUri, TRequest payload, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new entity by sending a POST request with the provided data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityName">The name of the entity to create.</param>
|
||||||
|
/// <param name="entityData">The data for the new entity as key-value pairs.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>The created entity data or null if creation failed.</returns>
|
||||||
|
Task<Dictionary<string, object>?> CreateEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
// Add other methods as needed (PUT, DELETE, PATCH, etc.)
|
// Add other methods as needed (PUT, DELETE, PATCH, etc.)
|
||||||
// Consider adding methods for handling raw HttpResponseMessage or string responses
|
// Consider adding methods for handling raw HttpResponseMessage or string responses
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace DataConnection.REST.Models
|
namespace DataConnection.REST.Models
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents basic information about a discovered REST entity without detailed field information.
|
||||||
|
/// </summary>
|
||||||
|
public class RestEntitySummary
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
public bool IsCustom { get; set; } = false;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public string EntityType { get; set; } = string.Empty; // For distinguishing between different types of entities
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents information about a discovered REST entity (resource).
|
/// Represents information about a discovered REST entity (resource).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -20,6 +32,20 @@ namespace DataConnection.REST.Models
|
|||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string Type { get; set; } = string.Empty; // Type as defined in the metadata (e.g., Edm.String, Edm.Int32)
|
public string Type { get; set; } = string.Empty; // Type as defined in the metadata (e.g., Edm.String, Edm.Int32)
|
||||||
public bool IsKey { get; set; } = false;
|
public bool IsKey { get; set; } = false;
|
||||||
|
public bool IsRequired { get; set; } = false;
|
||||||
|
public bool IsReadOnly { get; set; } = false;
|
||||||
|
public int? MaxLength { get; set; }
|
||||||
// Add other relevant info like Nullable, MaxLength etc. if needed
|
// Add other relevant info like Nullable, MaxLength etc. if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the result of a REST entity creation operation.
|
||||||
|
/// </summary>
|
||||||
|
public class RestEntityCreationResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
public Dictionary<string, object>? CreatedEntity { get; set; }
|
||||||
|
public string? CreatedEntityId { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user