using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Xml.Linq; namespace TrafagSalesExporter.Services; public class SapGatewayService : ISapGatewayService { private static readonly XNamespace AppNs = "http://www.w3.org/2007/app"; private static readonly XNamespace EdmNs = "http://docs.oasis-open.org/odata/ns/edm"; public async Task TestConnectionAsync(string serviceUrl, string username, string password, CancellationToken cancellationToken = default) { using var client = CreateClient(username, password); using var response = await client.GetAsync(BuildServiceUri(serviceUrl), cancellationToken); response.EnsureSuccessStatusCode(); } public async Task> GetEntitySetsAsync(string serviceUrl, string username, string password, CancellationToken cancellationToken = default) { using var client = CreateClient(username, password); var baseUrl = BuildServiceUri(serviceUrl); var entitySets = await TryReadEntitySetsFromServiceRootAsync(client, baseUrl, cancellationToken); if (entitySets.Count > 0) return entitySets; return await ReadEntitySetsFromMetadataAsync(client, baseUrl, cancellationToken); } public async Task>> GetEntityRowsAsync(string serviceUrl, string entitySet, string username, string password, CancellationToken cancellationToken = default) { using var client = CreateClient(username, password); var requestUrl = $"{BuildServiceUri(serviceUrl)}{entitySet}?$format=json"; using var response = await client.GetAsync(requestUrl, cancellationToken); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(cancellationToken); using var document = JsonDocument.Parse(json); if (!document.RootElement.TryGetProperty("d", out var dNode)) return []; if (!dNode.TryGetProperty("results", out var resultsNode) || resultsNode.ValueKind != JsonValueKind.Array) return []; var rows = new List>(); foreach (var item in resultsNode.EnumerateArray()) { var row = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var property in item.EnumerateObject()) { row[property.Name] = ConvertJsonValue(property.Value); } rows.Add(row); } return rows; } private static HttpClient CreateClient(string username, string password) { var client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(15); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/atomsvc+xml")); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))); return client; } private static string BuildServiceUri(string serviceUrl) { var trimmed = serviceUrl.Trim(); if (string.IsNullOrWhiteSpace(trimmed)) throw new InvalidOperationException("SAP Service URL darf nicht leer sein."); var entityPathMarker = "/sap/opu/odata/sap/"; var markerIndex = trimmed.IndexOf(entityPathMarker, StringComparison.OrdinalIgnoreCase); if (markerIndex >= 0) { var servicePath = trimmed[(markerIndex + entityPathMarker.Length)..].Trim('/'); var parts = servicePath.Split('/', StringSplitOptions.RemoveEmptyEntries); if (parts.Length > 1) { trimmed = $"{trimmed[..(markerIndex + entityPathMarker.Length)]}{parts[0]}/"; } } return trimmed.EndsWith('/') ? trimmed : $"{trimmed}/"; } private static async Task> TryReadEntitySetsFromServiceRootAsync(HttpClient client, string baseUrl, CancellationToken cancellationToken) { using var response = await client.GetAsync(baseUrl, cancellationToken); response.EnsureSuccessStatusCode(); var xml = await response.Content.ReadAsStringAsync(cancellationToken); var document = XDocument.Parse(xml); return document .Descendants(AppNs + "collection") .Select(x => x.Attribute("href")?.Value ?? string.Empty) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) .ToList(); } private static async Task> ReadEntitySetsFromMetadataAsync(HttpClient client, string baseUrl, CancellationToken cancellationToken) { using var response = await client.GetAsync($"{baseUrl}$metadata", cancellationToken); response.EnsureSuccessStatusCode(); var xml = await response.Content.ReadAsStringAsync(cancellationToken); var document = XDocument.Parse(xml); return document .Descendants(EdmNs + "EntitySet") .Select(x => x.Attribute("Name")?.Value ?? string.Empty) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) .ToList(); } private static object? ConvertJsonValue(JsonElement element) => element.ValueKind switch { JsonValueKind.String => element.GetString(), JsonValueKind.Number => element.ToString(), JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.Null => null, _ => element.ToString() }; }