a873dce31b
- Modificato GetAllRecordsAsync per utilizzare la stessa connection string del discovery schema - Aggiunto metodo CreateConnection per creare connessioni DB appropriate per tipo - Migliorata gestione nomi tabelle con schema (es. "dbo.OCRD") - Rimossi metodi obsoleti di creazione entità (UpdateEntityData, CreateNewEntity) - Eliminati riferimenti a variabili non dichiarate (newEntityData, isCreatingEntity) - Aggiunto logging debug per connection string e query SQL - Completata implementazione trasferimento dati da database a REST API Il trasferimento dati ora utilizza la stessa connessione per discovery e estrazione, risolvendo problemi di accesso alle tabelle durante l'operazione di upsert.
599 lines
28 KiB
C#
599 lines
28 KiB
C#
using DataConnection.REST.Configuration;
|
|
using DataConnection.REST.Interfaces;
|
|
using DataConnection.REST.Models; // Added for metadata models
|
|
using Microsoft.EntityFrameworkCore.Storage.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
using System.Xml.Linq; // Added for XML parsing
|
|
|
|
namespace DataConnection.REST.Implementations
|
|
{
|
|
/// <summary>
|
|
/// Client specific for SAP Business One Service Layer.
|
|
/// Handles session-based authentication and metadata discovery.
|
|
/// </summary>
|
|
public class SapB1ServiceClient : BaseRestServiceClient, IRestMetadataDiscovery // Implement the interface
|
|
{
|
|
private CookieContainer _cookieContainer;
|
|
private HttpClientHandler _httpClientHandler;
|
|
private HttpClient _sapHttpClient; // Use a dedicated HttpClient with cookie handling
|
|
|
|
// SAP B1 Specific Login Info
|
|
private string? _b1SessionId;
|
|
private string? _routeId;
|
|
private DateTime _sessionTimeout;
|
|
|
|
// Consider making options specific to SAP B1 if needed
|
|
public SapB1ServiceClient(RestServiceOptions options)
|
|
: base(CreateConfiguredHttpClient(options, out var handler, out var container), options)
|
|
{
|
|
// Store handler and container for direct cookie access if needed
|
|
_httpClientHandler = handler;
|
|
_cookieContainer = container;
|
|
_sapHttpClient = _httpClient; // Use the base class's configured HttpClient
|
|
}
|
|
|
|
private static HttpClient CreateConfiguredHttpClient(RestServiceOptions options, out HttpClientHandler handler, out CookieContainer container)
|
|
{
|
|
container = new CookieContainer();
|
|
handler = new HttpClientHandler
|
|
{
|
|
CookieContainer = container,
|
|
UseCookies = true,
|
|
// Service Layer might use self-signed certificates in test environments
|
|
// Use cautiously in production!
|
|
// ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
|
};
|
|
|
|
// Conditionally ignore SSL errors based on options
|
|
if (options.IgnoreSslErrors)
|
|
{
|
|
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
|
// Log a warning when ignoring SSL errors
|
|
Console.WriteLine("Warning: Ignoring SSL certificate errors. Use only for trusted development environments.");
|
|
}
|
|
|
|
var client = new HttpClient(handler);
|
|
|
|
// Configure base address, timeout etc. from base class logic
|
|
// The base constructor will call ConfigureHttpClient which uses _options
|
|
return client;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs into SAP Business One Service Layer.
|
|
/// </summary>
|
|
/// <param name="companyDB">The Company Database name.</param>
|
|
/// <param name="userName">Service Layer username.</param>
|
|
/// <param name="password">Service Layer password.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>True if login is successful, false otherwise.</returns>
|
|
public async Task<bool> LoginAsync(string companyDB, string userName, string password, CancellationToken cancellationToken = default)
|
|
{
|
|
var loginPayload = new { CompanyDB = companyDB, UserName = userName, Password = password };
|
|
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
|
|
{
|
|
// Use the internal HttpClient configured with CookieContainer
|
|
// Pass the loginPayload object directly, PostAsJsonAsync handles serialization
|
|
var response = await _sapHttpClient.PostAsJsonAsync(loginUri, loginPayload, cancellationToken);
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
// Log error details
|
|
Console.WriteLine($"SAP B1 Login failed: {response.StatusCode}");
|
|
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
Console.WriteLine($"Error details: {errorContent}");
|
|
Console.WriteLine($"--- End SAP B1 Login Attempt (Failed) ---"); // Log fine tentativo
|
|
return false;
|
|
}
|
|
|
|
// Extract session details from response (if needed directly) and cookies
|
|
var sessionInfo = await response.Content.ReadFromJsonAsync<SapB1SessionInfo>(cancellationToken: cancellationToken);
|
|
|
|
// Cookies are automatically handled by HttpClientHandler and CookieContainer
|
|
// Optionally, store session details if needed for manual checks or info
|
|
if (sessionInfo != null)
|
|
{
|
|
_b1SessionId = GetCookieValue("B1SESSION");
|
|
_routeId = GetCookieValue("ROUTEID"); // Important for sticky sessions in load-balanced environments
|
|
_sessionTimeout = DateTime.UtcNow.AddSeconds(sessionInfo.SessionTimeout);
|
|
|
|
Console.WriteLine($"SAP B1 Login successful. Session expires around: {_sessionTimeout.ToLocalTime()}");
|
|
Console.WriteLine($"--- End SAP B1 Login Attempt (Success) ---"); // Log fine tentativo
|
|
return true;
|
|
}
|
|
|
|
Console.WriteLine("SAP B1 Login response could not be parsed.");
|
|
return false;
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
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;
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
Console.WriteLine($"JSON Parsing Error during SAP B1 Login: {ex.Message}");
|
|
Console.WriteLine($"--- End SAP B1 Login Attempt (JsonException) ---"); // Log fine tentativo
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error during SAP B1 Login: {ex.Message}");
|
|
Console.WriteLine($"--- End SAP B1 Login Attempt (Exception) ---"); // Log fine tentativo
|
|
return false;
|
|
}
|
|
// finally // Aggiunto per sicurezza - Rimosso perchè non necessario qui
|
|
// {
|
|
// // Potrebbe essere utile loggare qui se necessario
|
|
// }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs out from SAP Business One Service Layer.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>Task representing the asynchronous operation.</returns>
|
|
public async Task LogoutAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var logoutUri = "/b1s/v2/Logout";
|
|
try
|
|
{
|
|
// Send POST request to Logout endpoint. Cookies are sent automatically.
|
|
var response = await _sapHttpClient.PostAsync(logoutUri, null, cancellationToken);
|
|
response.EnsureSuccessStatusCode(); // Check if logout was successful
|
|
|
|
// Clear local session state
|
|
_b1SessionId = null;
|
|
_routeId = null;
|
|
// Optionally clear cookies from container if needed, though they become invalid
|
|
ClearCookies();
|
|
Console.WriteLine("SAP B1 Logout successful.");
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
// Log or handle error, maybe session already expired
|
|
Console.WriteLine($"HTTP Request Error during SAP B1 Logout: {ex.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error during SAP B1 Logout: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the current session is active based on timeout.
|
|
/// </summary>
|
|
public bool IsSessionActive()
|
|
{
|
|
return !string.IsNullOrEmpty(_b1SessionId) && DateTime.UtcNow < _sessionTimeout;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Discovers entities and their properties from the SAP B1 Service Layer metadata.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>A list of discovered entity information.</returns>
|
|
public async Task<List<RestEntityInfo>> DiscoverEntitiesAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
// Ensure session is active before attempting discovery
|
|
if (!IsSessionActive())
|
|
{
|
|
// Or attempt login? For now, throw exception or return empty list
|
|
Console.WriteLine("Error: Not logged into SAP B1 Service Layer. Cannot discover metadata.");
|
|
// Consider throwing a specific exception
|
|
return new List<RestEntityInfo>();
|
|
}
|
|
|
|
var metadataUri = "/b1s/v2/$metadata"; // Adjust version (v1/v2) if necessary
|
|
var entities = new List<RestEntityInfo>();
|
|
|
|
try
|
|
{
|
|
// Use the internal HttpClient which handles cookies
|
|
var response = await _sapHttpClient.GetAsync(metadataUri, cancellationToken);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
var xmlContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
var edmx = XDocument.Parse(xmlContent);
|
|
|
|
// Define XML namespaces (adjust if different in your specific $metadata)
|
|
XNamespace edm = "http://schemas.microsoft.com/ado/2009/11/edm";
|
|
XNamespace edmxNs = "http://schemas.microsoft.com/ado/2007/06/edmx"; // Older namespace sometimes used
|
|
XNamespace defaultEdmNs = edmx.Root?.GetDefaultNamespace() ?? edm; // Get default namespace if present
|
|
XNamespace dataServicesNs = edmx.Root?.Element(edmxNs + "DataServices")?.GetDefaultNamespace() ?? defaultEdmNs;
|
|
|
|
|
|
// Find the Schema element (might be nested)
|
|
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 entityInfo = new RestEntityInfo
|
|
{
|
|
Name = entityTypeElement.Attribute("Name")?.Value ?? string.Empty
|
|
};
|
|
|
|
if (string.IsNullOrEmpty(entityInfo.Name)) continue; // Skip if name is missing
|
|
|
|
// 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)
|
|
// Add extraction for Nullable, MaxLength etc. if needed from attributes
|
|
};
|
|
entityInfo.Properties.Add(propInfo);
|
|
}
|
|
|
|
entities.Add(entityInfo);
|
|
}
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
Console.WriteLine($"HTTP Request Error during metadata discovery: {ex.Message}");
|
|
// Handle error appropriately
|
|
}
|
|
catch (XmlException ex)
|
|
{
|
|
Console.WriteLine($"XML Parsing Error during metadata discovery: {ex.Message}");
|
|
// Handle error appropriately
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error during metadata discovery: {ex.Message}");
|
|
// Handle error appropriately
|
|
}
|
|
|
|
return entities;
|
|
}
|
|
|
|
|
|
/// <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;
|
|
} /// <summary>
|
|
/// Authenticates with SAP B1 Service Layer using the credentials from options.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>True if authentication is successful</returns>
|
|
public override async Task<bool> AuthenticateAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrEmpty(_options.Username) || string.IsNullOrEmpty(_options.Password))
|
|
{
|
|
Console.WriteLine("SAP B1 authentication requires username and password in options");
|
|
return false;
|
|
}
|
|
|
|
// For SAP B1, we also need the company database name
|
|
// We'll check multiple fields for the company DB name
|
|
var companyDB = !string.IsNullOrEmpty(_options.ApiKey) ? _options.ApiKey :
|
|
!string.IsNullOrEmpty(_options.AuthToken) ? _options.AuthToken :
|
|
"SBODEMOUS"; // Default fallback
|
|
|
|
Console.WriteLine($"Using SAP B1 credentials - CompanyDB: {companyDB}, Username: {_options.Username}");
|
|
|
|
return await LoginAsync(companyDB, _options.Username, _options.Password, cancellationToken);
|
|
}
|
|
|
|
// Helper to get cookie value
|
|
private string? GetCookieValue(string cookieName)
|
|
{
|
|
if (_httpClient.BaseAddress == null) return null;
|
|
var cookies = _cookieContainer.GetCookies(_httpClient.BaseAddress);
|
|
return cookies[cookieName]?.Value;
|
|
}
|
|
|
|
// Helper to clear cookies
|
|
private void ClearCookies()
|
|
{
|
|
if (_httpClient.BaseAddress == null) return;
|
|
var cookies = _cookieContainer.GetCookies(_httpClient.BaseAddress);
|
|
foreach (Cookie cookie in cookies)
|
|
{
|
|
cookie.Expired = true;
|
|
}
|
|
}
|
|
|
|
|
|
// Ensure logout on dispose? Optional, depends on desired lifecycle management.
|
|
// public override void Dispose() { ... LogoutAsync().Wait(); ... base.Dispose(); }
|
|
|
|
// --- Nested class for deserializing Login response ---
|
|
private class SapB1SessionInfo
|
|
{
|
|
public string SessionId { get; set; } = string.Empty;
|
|
public string Version { get; set; } = string.Empty;
|
|
public int SessionTimeout { get; set; } // In seconds
|
|
}
|
|
}
|
|
}
|