This commit is contained in:
2026-04-14 10:54:52 +02:00
parent df90a4a172
commit 36a22202bf
14 changed files with 678 additions and 78 deletions
@@ -0,0 +1,140 @@
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<List<string>> 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<List<Dictionary<string, object?>>> 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<Dictionary<string, object?>>();
foreach (var item in resultsNode.EnumerateArray())
{
var row = new Dictionary<string, object?>(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<List<string>> 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<List<string>> 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()
};
}