Eigentlich beschäftige ich mich mit der Vollautomatisierung von Belegen. Ein Detail Aspekt ist die Zusammenführung aus verschieden Quellen auf einen FileShare. Ganz ohne AI oder LLM.
In unserem Fall liegen Eingangsrechungen von Lieferanten (meist Trainern) auf unserem externen Server. Samt Überweisungsdaten im XML Format.
Diese Daten sollen nun in unser internes CRM überführt werden. Dazu habe ich mir zwei Fake Services auf Basis von einfachen ASPX Seiten gebaut.
Nur damit man sich das vorstellen kann der VB. NET Code
1: <%@ Page Language="VB" %>
2:
3: <%@ Import Namespace="System.IO" %>
4: <%@ Import Namespace="Microsoft.AspNet.FriendlyUrls" %>
5: <script runat="server">
6:
7: Private Const BASE_PATH As String = "c:\inetpub\pdf\cash\"
8: Private Const API_KEY As String = "CHANGEME"
9:
10: Protected Sub Page_Load(sender As Object, e As EventArgs)
11: Dim mode As String = Request.QueryString("mode")
12: If Not String.IsNullOrWhiteSpace(mode) AndAlso mode.Equals("ping", StringComparison.OrdinalIgnoreCase) Then
13: Response.StatusCode = 200
14: Response.ContentType = "text/plain"
15: Response.Write("ok")
16: Response.End()
17: Return
18: End If
19:
20: Dim id = Request.QueryString("id")
21:
22: Dim pdfPath = Path.Combine(BASE_PATH, id & ".pdf")
23: If Not File.Exists(pdfPath) Then
24: Response.StatusCode = 404
25: Response.Write("pdf not found " + pdfPath)
26: Response.End()
27: Return
28: End If
29:
30: Dim bytes = File.ReadAllBytes(pdfPath)
31: Response.Clear()
32: Response.ContentType = "application/pdf"
33:
34: ' Show im Browser (inline). Wenn du immer Download willst: attachment statt inline
35: Response.AddHeader("Content-Disposition", "inline; filename=" & id & ".pdf")
36: Response.OutputStream.Write(bytes, 0, bytes.Length)
37: Response.End()
38: End Sub
39:
40: Private Function GetId() As String
41: Dim seg = TryGetFriendlySegment()
42: If Not String.IsNullOrWhiteSpace(seg) Then Return seg
43: Return Request.QueryString("id")
44: End Function
45:
46: Private Function TryGetFriendlySegment() As String
47: Try
48: Dim segs = Request.GetFriendlyUrlSegments()
49: If segs IsNot Nothing AndAlso segs.Count > 0 Then Return segs(0)
50: Catch
51: End Try
52: Return Nothing
53: End Function
54: </script>
In meiner Server side Blazor App werden diese Services aufgerufen und so das PDF in Memory des Webservers geholt. Auch hier der Code nur für die Vollständigkeit
1: private async Task<byte[]> GetBytesAsync(string url)
2: {
3: using var req = new HttpRequestMessage(HttpMethod.Get, url);
4: req.Headers.Add("X-Api-Key", ApiKey);
5: using var resp = await Http.SendAsync(req);
6: if (!resp.IsSuccessStatusCode)
7: {
8: var t = await resp.Content.ReadAsStringAsync();
9: throw new Exception($"Download fehlgeschlagen: {(int)resp.StatusCode} {resp.ReasonPhrase} - {t}");
10: }
11: return await resp.Content.ReadAsByteArrayAsync();
12: }
Die Daten sollen aber final auf einem File Share eines Windows Servers landen. Dieser steht innerhalb der Domain. Der Blazor Server außerhalb.
Der Benutzer auf der Windows Maschine hat aber das Recht auf den File Share zu schreiben. Das geht nur per JavaScript und einem Call auf eine REST API. Und genau diese API baue ich nun und installiere diese als Service auf dem Windows Server. Etwas was ich noch nie gemacht habe, aber mit ChatGPT in Minuten erledigt wurde.
Starten wir per CLI Kommando
dotnet new web -n BelegKorbAgent
Die .csproj Datei wird dann mit Visual Studio geöffnet und program.cs zum Miniwebserver.
Zunächst braucht man einen Listener für HTTP Kommandos
1: var listener = new HttpListener();
2: listener.Prefixes.Add(prefix);
3: listener.Start();
4: Console.WriteLine("BelegKorbAgent läuft auf " + prefix);
Dann lauscht man für die Ewigkeit in einer Schleife. Der Header erlaubt eine sogenannte Cross Origin Zugriff, wie in JavaScript benötigt, wenn JS und API nicht in der selben Domain liegen.
1: while (true)
2: {
3: var ctx = await listener.GetContextAsync();
4: ctx.Response.AddHeader("Access-Control-Allow-Origin", "*");
5: ctx.Response.AddHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
6: ctx.Response.AddHeader("Access-Control-Allow-Headers", "*");
Wenn HTTP Kommandos einlangen, braucht man unterschiedliche Logik. Im Code auskommentiert, eine Begrenzung auf bestimmte IP Adressen. Dort könnte man auch eine APIKey abfangen. Der Bereich Options dient dem Preflight für CORS.
POST samt URL und Querystring dient dem eigentlichen Datei Transfer der PDF über alle Grenzen hinweg.
1: _ = Task.Run(async () =>
2: {
3: try
4: {
5: var ip = ctx.Request.RemoteEndPoint?.Address.ToString() ?? "";
6: //if (!allowedIps.Contains(ip))
7: //{
8: // ctx.Response.StatusCode = 403;
9: // await Write(ctx, "forbidden");
10: // return;
11: //}
12: if (ctx.Request.HttpMethod == "OPTIONS")
13: {
14: ctx.Response.StatusCode = 200;
15: ctx.Response.Close();
16: return;
17: }
18:
19:
20: if (ctx.Request.HttpMethod != "POST" ||
21: ctx.Request.Url?.AbsolutePath != "/belegkorb/store")
22: {
23: ctx.Response.StatusCode = 404;
24: await Write(ctx, "not found");
25: return;
26: }
27:
28: var id = ctx.Request.QueryString["id"];
29: if (string.IsNullOrWhiteSpace(id))
30: {
31: ctx.Response.StatusCode = 400;
32: await Write(ctx, "missing id");
33: return;
34: }
35:
36: var target = Path.Combine(sharePath, id );
37: if (File.Exists(target))
38: {
39: ctx.Response.StatusCode = 409;
40: await Write(ctx, "exists");
41: return;
42: }
43:
44: await using var fs = File.Create(target);
45: await ctx.Request.InputStream.CopyToAsync(fs);
46:
47: await Write(ctx, "ok");
48: }
49: catch (Exception ex)
50: {
51: ctx.Response.StatusCode = 500;
52: await Write(ctx, ex.Message);
53: }
54: finally
55: {
56: ctx.Response.Close();
57: }
58: });
59: }
Allerdings unterbindet Windows den Listener. Wäre zu einfach. Was ist zu tun? Powershell console im Admin Mode und
netsh http add urlacl url=http://+:5010/belegkorb/ user=Jeder
Ich weis, everyone ist aus Sicherheitsgründen nicht perfekt. Jetzt kann man bereits im Debugging Mode den Service testen.
Um daraus einen Windows Service zu machen, müssen auf der Command Shell noch zwei Befehle ausgeführt werden
sc.exe create BelegKorbAgent binPath= "C:\BelegKorbAgent\BelegKorbAgent.exe"
sc.exe start BelegKorbAgent
Hier ist jetzt keine KI oder MCP Protokoll im Einsatz. Aber die Konzepte und Probleme sind die gleichen.
Praxisnahe Ergänzungen
Wer das Thema strukturiert weiterdenken, praxisnah einordnen oder ähnliche Themen vertiefen möchte, findet in folgenden Trainings passende Anknüpfungspunkte: