Wenn man in der Textdatei die Anwendung mit Parametern zum Start angibt, kann man das wunderbar als Backdoor verwenden, um Schadcode zu laden.
Herrlich :-)
Danke für die Gedankenanstöße. Da die Anwendung auch eine Netzwerkfreigabe benötigt, würde sich diese anbieten, um darüber zu kommunizieren. Auf den entsprechenden Clients dann eine einfache Applikation mit einem File-Watcher. Aber auch die anderen Ansätze sind gut und ich werde mir das übers Wochenende anschauen.
Nochmals Dankeschön an alle und schönes Schwitzen!
Es wäre kein Problem, ein Service oder ein automatisch im Benutzerkontext startendes Programm auf der Arbeitsstation zu installieren, welches dann diese Aufgabe übernimmt. Welche Kommunikationswege sind aber in diesem Fall am geeignetsten?
ein Programm startet weitere lokale Programme wie u. a. Office-Programme (Excel, Outlook, Word usw.). Bisher funktioniert alles ohne Probleme.
Seit kurzem wird dieses Programm auch als Remote-Desktop-Anwendung auf einem Remote-Desktop-Server (früher Terminal-Server) ausgeführt – auch das funktioniert gut. Allerdings fehlen auf dem Server einige der vom Programm gestartete Programm wie z. B. die genannten Office-Programme. Es ist nicht erwünscht, die Office-Programme auf dem Remote-Desktop-Server zu installieren.
Nun "darf" ich überlegen, ob es eine Möglichkeit gibt, dass die Anwendung, die auf dem Remote-Desktop-Server ausgeführt wird, Programme auf dem Client startet. Name, Parameter usw. der zu startenden Programme können in der Anwendung hinterlegt werden.
Mir geht es nicht darum, wie ich etwas machen kann, sondern um das "Was".
Welche Technologien sind für die gestellte Aufgabe am besten geeignet?
Warum die Methoden umständlich per Reflection in ein Dictionary packen gerade wenn die statisch sind und nicht bei der Definition.
Ganz einfach: Es werden einmalig beim Programmstart die Methoden der passenden Klasse zur Nachverarbeitung der Spaltenwerte der Quelle ermittelt und diese in ein temporäres Dictionary gepackt. Gleichzeitig werden alle Spalten inkl. deren Spaltennamen in der Quelle ermittelt. Nun findet ein Abgleich statt: Gibt es für die gerade untersuchte Spalte in der Quelle eine passende Methode (Nachverarbeitungsmethode)? – Die Kriterien wurden bereits eingangs dargelegt (static, public, keine Parameter, Rückgabe bool und Name wie die Spalte aber Präfix dazu).
Wenn eine Nachverarbeitungsmethode gefunden wurde, wird diese mit der sonstigen Feldinformation gespeichert.
Ich hatte auch erwähnt, dass der Pflegeaufwand auf ein Minimum reduziert werden soll: Kommen weitere Spalten in der Quelle dazu bzw. müssen bestehende Spalten anders behandelt werden, so soll es reichen, die jeweilige Klasse für die Quelle um die passenden Methoden (Signatur und Name müssen passen) zu erweitern. Das restliche Programm soll unangetastet bleiben.
Im Übrigen werden die Methoden durch ihr Präfix im Editor bei der Codevervollständigung alle schön gebündelt angezeigt. Auch ein Vorteil.
Sofern du die Methoden manuell aufrufst dann stimmt das. Ich bin davon ausgegangen, dass du die Methoden nur via Reflection aufrufst.
Eigentlich werden sie nicht manuell aufgerufen – da hast du Recht. Dennoch mag ich die Namensgebung, welche eine direkte Zuordnung zwischen einer Spalte und deren Verarbeitung herstellt. Das mag auch historische Gründe haben: Unser Hauptprodukt (eine große, 30 Jahre alte Datenbankanwendung) fußt darauf und wir sind immer gut damit zu recht gekommen. Die Macht der Gewöhnung...
Zitat von dannoe
Zitat
Für diesen einen Fall nutzen uns die Attributen leider nichts: [...]
Warum die Attribute dabei nichts nutzen sollen, hast du aber nicht beschrieben.
Bei dieser Anwendung sehe ich keinen Mehrwert darin, wenn wir weiterhin auf unserer alten Zuordnung Feld/Spalte --> Methode beharren.
Zitat von dannoe
Nach deiner ausführlicheren Beschreibung, würde ich es jetzt so umsetzen:
[File("Parts_22")]
public class FilePostProcessor_Parts_22 : BaseProcessor
{
[PostProcess("PartNumber")]
public static bool TransformPartNumberIntoXyz()
{
// do something
}
[PostProcess("Art2NoH")]
public static bool TransformArt2NoHIntoZyx()
{
// do something
}
}
BaseProcess ist deine Basisklasse mit der gemeinsamen Funktionalität.
Ein weiterer Vorteil wenn du die Attribute verwendest:
- Du kannst einen Roslyn Analyzer bauen, der bei vorhandenem PostProcess Attribute die Methodensignatur erzwingt.
Ja, richtig, aber durch unsere Vorgehensweise wird eine Methode nie aufgerufen, wenn Sie nicht die richtige Signatur und Namen hat.
Zitat von dannoe
- Du kannst zur Runtime überprüfen ob alle Methoden die das Attribute haben, auch die richtige Signatur haben.
In deinem Fall kann es schnell passieren, dass eine Signatur falsch ist und das fällt dann fast nicht auf. (Außer dass die Nachverarbeitung des Feldes nicht stattfindet)
Auch richtig, aber auch das ist in dieser Anwendung sekundär: Nachverarbeitung haben einen definierten Zweck und werden ohne ausführliche Tests nicht implementiert. Der erste Test ist der Aufruf über Reflection – erst wenn dieser stattfindet, wird die Logik implementiert.
Zitat von dannoe
Ich hab das jetzt nur der Vollständigkeit halber geschrieben. Vielleicht hat mal jemand ein ähnliches Problem und sucht etwas Inspiration.
Auf jeden Fall bedanke ich mich ausdrücklich bei dir, denn du hast mir einige Denkanstöße gegeben, die ich anderorts gut gebrauchen kann.
Für diesen einen Fall nutzen uns die Attributen leider nichts: Z. B. liefert die Quelle Daten über die Datei "Parts_22", welche u. a. über 102 Spalten verfügt – 58 davon brauchen eine sogenannte Nachverarbeitung, so dass es dafür 58 Methoden gibt.
In Anlehnung an die Quelle bekommt die Klasse ebenfalls den Namen "Parts_22" – alle solche Klassen werden von einer anderen Klasse abgeleitet, die gemeinsame Funktionalität bietet.
Wenn nun für eine Spalte eine Nachverarbeitungsmethode notwendig ist, so heißt diese Methode wie die nachzuverarbeitende Spalte mit dem Präfix "nv". Wird nun beispielweise die Quelle um eine Spalte erweitert (z. B. Art2NoH), so wird diese zwar an das Zielsystem übertragen, mangels Nachverarbeitungsmethode aber nicht weiter beachtet. Ist das aber erwünscht, so reicht es, die Klasse Parts_22 um die Methode nvArt2NoH mit der passenden Signatur zu erweitern und schon wird diese ausgeführt.
Im Übrigen werden die Methoden durch ihr Präfix im Editor bei der Codevervollständigung alle schön gebündelt angezeigt. Auch ein Vorteil.
Das ist der Hintergrund.
Und deutsch und englisch: Manchmal verwende ich auch spanisch... :-)
Nochmals lieben Dank für die vielen Vorschläge und hasta la próxima!
Danke euch Dreien! Beides funktioniert, wobei ich mich für die Einschränkung entschieden habe, da sie etwas durchsichtiger ist.
Ich verstehe, dass das Festzurren am Namen für ernste Mienen sorgt. Für meine Anwendung ist das aber so gewollt: Es sollen Klassen erstellt werden, die u. a. Werte aus anderen Quellen verarbeiten und an ein anderes System überstellen – also eine typische Schnittstelle zwischen zwei Systemen.
Jede Schnittstelle ist eine Klasse, die sog. Nachverarbeitungsmethoden bietet bzw. bieten kann. Diese Nachverarbeitungsmethoden werden für jedes Feld/Spalte automatisch aufgerufen, sofern sie in der benutzten Klasse vorhanden sind und unter anderem deren Methodenname den Feldname mit Präfix "nv" entsprechen. Beispiel: PartNumber ==> nvPartNumber.
Da es um sehr viele Schnittstellen mit vielen Feldern/Spalten geht, soll der Programmier- und Verwaltungsaufwand auf ein Minimum reduziert werden, daher diese Mimik: Es reicht, eine Klasse um eine passende Nachverarbeitungsmethode zu ergänzen, damit diese automatisch aufgerufen wird.
ich brauche wieder einmal euer Expertenwissen. Ich habe unten stehenden Code einer generischen Methode. Diese untersucht die über <T> übermittelte Klasse, wobei sie die Methoden der Klasse ermittelt, die alle nachstehend aufgeführten Kriterien erfüllen:
public
static
Name beginnt mit "nv"
keine Parameter
Rückgabewert bool
Das funktioniert auch. Nun aber zu meinem Problem:
Wie kann ich die über T übergebende Klasse instanziieren? So was wie "var Test = new typeof(T)()" oder ähnlich. Ich stehe auf dem Schlauch.
namespace myTest
{
class Program
{
static void Main(string[] args)
{
myMethod<KlasseA>()
myMethod<KlasseB>()
myMethod<KlasseC>()
}
public void myMethod<T>()
{
// Nimmt alle zur <T> passenden Methoden auf.
var MethodMap = new Dictionary<string, Func<bool>>();
// Uns interessieren nur öffentliche, statische Methoden.
MethodInfo[] myMethods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Static);
// Darüber hinaus möchten wir nur die Methoden, die mit "nv"" beginnen, parameterlos sind und bool zurückliefern.
foreach (MethodInfo myMethod in myMethods.Where(m => m.Name.StartsWith(c.PostProcessingPrefix) == true && m.GetParameters().Length == 0 && m.ReturnType == typeof(bool)))
{
MethodInfo? Method = typeof(T).GetMethod(myMethod.Name, Type.EmptyTypes);
if (Method != null)
{
MethodMap.Add(myMethod.Name, (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), Method));
}
}
// Wie kann ich an dieser Stelle die über T übergebende Klasse instanziieren?
}
}
public class KlasseA
{
...
}
public class KlasseB
{
...
}
public class KlasseC
{
...
}
}
Du hast mich kurz vor dem Ins-Bett-gehen nochmals angetriggert. Ich habe das Beispiel etwas modifiziert, wodurch es m. E. klarer wird:
using System.Linq.Expressions;
using System.Reflection;
Dictionary<string, Action<int>> MethodenMap = new Dictionary<string, Action<int>>();
for (int i = 1; i ≤ 3; i++)
{
MethodenMap.Add($"Func{i}", CreateAction($"Func{i}"));
}
for (int i = 1; i ≤ 3; i++)
{
MethodenMap[$"Func{i}"](i);
}
Console.WriteLine("Enter, um zu beenden...");
Console.ReadLine();
static Action<int> CreateAction(string FuncName)
{
MethodInfo? Methode = typeof(test).GetMethod(FuncName, new[] { typeof(int) });
if (Methode == null) throw new ArgumentNullException ("Methode");
if (Methode.IsStatic == false) throw new ArgumentException ("Die übergebende Methode muss statisch sein.", "Methode");
if (Methode.IsGenericMethod == true) throw new ArgumentException ("Die übergebende Methode darf nicht generisch sein.", "Methode");
// Danke an Palladin007 – es sieht schon übersichtlicher aus! ;-)
return (Action<int>)Delegate.CreateDelegate(typeof(Action<int>), Methode);
//return (Action<int>)Methode.CreateDelegate(Expression.GetDelegateType
// (
// Methode.GetParameters()
// .Select(p => p.ParameterType)
// .Concat(new[] { Methode.ReturnType })
// .ToArray())
// );
}
public static class test
{
public static void Func1(int a)
{
Console.WriteLine($"Func{a}");
}
public static void Func2(int a)
{
Console.WriteLine($"Func{a}");
}
public static void Func3(int a)
{
Console.WriteLine($"Func{a}");
}
}
Nachtrag
Ich habe das ganze , wie von Palladin007 empfohlen, auf Action umgestellt:
Dictionary<string, Action<int>> MethodenMap = new Dictionary<string, Action<int>>();
for (int i = 1; i ≤ 3; i++)
{
MethodenMap.Add($"Func{i}", GenerateDelegate(typeof(test).GetMethod($"Func{i}", new[] { typeof(int) })) as Action<int>);
}
for (int i = 1; i ≤ 3; i++)
{
MethodenMap[$"Func{i}"](i);
}
Der Aufruf MethodenMap[$"Func{i}"](i); ist dadurch in der Tat eleganter, aber da habe ich gleich eine weitere Frage:
Ist die Umwandlung des Delegate in eine Action mit dem as-Operator (hier as Action<int>) der richtige Weg oder gibt es andere (bessere) Möglichkeiten?
Reflection brauche ich nur einmal, um in einer Factory die Delegates in einem Dictionary abzulegen. Gleichzeitig löse ich das Problem bzw. erfülle ich mir den Wunsch :-), static zu verwenden. Später kann ich direkt über den Spaltennamen die passende Methode über DynamicInvoke aufrufen.
using System.Linq.Expressions;
using System.Reflection;
Dictionary<string, Delegate> MethodenMap = new Dictionary<string, Delegate>();
for (int i = 1; i ≤ 3; i++)
{
MethodenMap.Add($"Func{i}", GenerateDelegate(typeof(test).GetMethod($"Func{i}", new[] { typeof(int) })));
}
for (int i = 1; i ≤ 3; i++)
{
MethodenMap[$"Func{i}"].DynamicInvoke(i);
}
Console.WriteLine("Enter, um zu beenden...");
Console.ReadLine();
static Delegate GenerateDelegate(MethodInfo Methode)
{
if (Methode == null) throw new ArgumentNullException ("Methode");
if (Methode.IsStatic == false) throw new ArgumentException ("Die übergebende Methode muss statisch sein.", "Methode");
if (Methode.IsGenericMethod == true) throw new ArgumentException ("Die übergebende Methode darf nicht generisch sein.", "Methode");
return Methode.CreateDelegate(Expression.GetDelegateType(
Methode.GetParameters()
.Select(p => p.ParameterType)
.Concat(new[] { Methode.ReturnType })
.ToArray()));
}
public static class test
{
public static void Func1(int a)
{
Console.WriteLine($"Func{a}");
}
public static void Func2(int a)
{
Console.WriteLine($"Func{a}");
}
public static void Func3(int a)
{
Console.WriteLine($"Func{a}");
}
}
Danke für eure Tipps – diese haben mich auf die richtige Spur gebracht.
Ich bekomme große CSV-Dateien mit zum Teil mehreren hundert Spalten. Aus der ersten Spalte erhalte ich die Spaltennamen, deren Werte ab der zweiten Zeile geprüft und verarbeitet werden müssen. Diese Verarbeitung findet in einer für die jeweilige Spalte extra programmierten Methode statt. Somit hat man u. U. hunderte solcher Methoden. Nun möchte ich vermeiden, eine elend lange Switch-Anweisung mit hunderten von Cases zu programmieren, um jeweils die richtige Methode aufzurufen.
Sicher, ich könnte auch ein Dictionary verwenden, dessen Key der Spaltennamen ist und als Wert den passenden Funktionszeiger enthält. Das wäre mein zweiter Kandidat. Dennoch wäre es mir lieber, die Methoden direkt anhand des jeweiligen Spaltennamens aufzurufen, indem die aufzurufenden Methoden den Spaltennamen entsprechen. Kommen Spalten dazu, so muss ich lediglich die passenden Methoden implementieren, ohne mich mit einem Dictionary oder einer Switch-Anweisung befassen zu müssen – beim Dictionary würde ich zudem hunderte von Codezeilen sparen; bei der Switch-Anweisung sogar noch mehr.
ich möchte fragen, ob es bessere Möglichkeiten gibt, Methoden aufzurufen, deren Namen erst zur Laufzeit bekannt sind?
var klasse = new test();
for (int i = 1; i ≤ 3; i++)
{
_ = klasse.GetType().GetMethod($"Func{i}").Invoke(klasse, new object[] { i });
}
public class test
{
public void Func1(int a)
{
Console.WriteLine($"Func{a}");
}
public void Func2(int a)
{
Console.WriteLine($"Func{a}");
}
public void Func3(int a)
{
Console.WriteLine($"Func{a}");
}
}
Dann gleich eine Anschlussfrage:
Wie mache ich es, wenn die Klasse test in eine statische Klasse überführt wird?
Sicher wäre so eine Hilfe nicht für jeden richtig. Es gibt auch viele anderen Eigenschaften von VS, die man wenig oder gar nie braucht – das liegt i. d. R. daran, dass nicht jeder dieselben Anforderungen hat. Das ist auch gut so: Jeder soll sich seine Umgebung so zusammenstellen, wie sie für ihn im Rahmen seiner Möglichkeiten am sinnvollsten ist.
Ich habe auf visualstudio.com den Wunsch geäußert und hoffe, dass er irgendwann in die Tat umgesetzt wird:
Mein Kollege meinte auch, ich bräuchte keinen so langen Schlauch, denn so große bzw. Lange Methoden wären sowieso ein Zeichen gegen das gute Software-Engineering. Mag in vielen Fällen stimmen, nicht aber in allen. Darüber hinaus wäre es schön, wenn man über eine Klasse, die mehr als 50 Zeilen hat (was durchaus vorkommen kann), über mehr als 100 Zeilen scrollen könnte. Ich würde gerne die Wahl haben, wenn solche breite Monitore sowieso zum Standard geworden sind.
Klar kann ich einen flachen 40-Zoll-Moni hochkant betreiben – dann hätte ich auch einen langen Scrollbereich, bekäme es aber mit den Nackenmuskeln zu tun 🤕
Ich hoffe, ich kann meine Frage einigermaßen verständlich stellen.
Monitore bekommen immer mehr Auflösung in der Breite nicht so aber in der Höhe. So gibt es z. B. Monitore im Format 21:9 mit einer gebogenen Fläche. Sicher toll für Spiele aber nicht wirklich geeignet, um langen Programmcode darzustellen – hier wären Monitore mit eine hohen vertikalen Auflösung besser, aber einen Curved-Monitor hochkantig zu drehen? 😵💫
Eine gute Abhilfe wäre es, ein Fenster, welches eine Datei geladen hat, vertikal so zu teilen, dass beim Scrollen der Inhalt beider Fenster bewegt wird. In anderen Worten keine zwei unabhängige Fenster (das geht heute schon) sondern dass derselbe Inhalt sich über beide geteilte Fenster erstreckt.
Angenommen, es passen vertikal 50 Zeilen auf dem Bildschirm. Nun teile ich das Fenster vertikal, wodurch ich im ersten (linken) Fenster die Zeilen 1–50 und im zweiten (rechten) die Zeilen 51–100 darstellen kann. Wenn ich den Cursor nach unten bewege, soll dieser am unteren Rand (Zeile 50) auf die Zeile 51 (Erste Zeile des zweiten Fensters) gehen, wenn ich noch einmal auf Pfeil nach unten drücke. Dann scrollt dieser bis zur Zeile 100. Scrolle ich dann weiter, so scrollen beide Fenster wie in einem Schlauch. Damit könnte ich in diesem fiktiven Beispiel 100 Zeilen gleichzeitig darstellen und darin arbeiten.
Was in VS geht, sind vertikale Dokumentgruppen, in denen ich dieselbe Datei an unterschiedlichen Stellen darstellen und bearbeiten kann. Aber ein fensterübergreifendes Scrollen geht nicht (siehe Bild mit drei Dokumentgruppen mit jeweils derselben Datei):
Nun meine Frage: Gibt es eine Möglichkeit, dieses Vorhaben zu realisieren? Eventuell mit Hilfe von einer Erweiterung?
ich mache meine ersten Schritte mit .Net 6 und versuche einen Dienst mit Hilfe von BackgroundService zu erstellen. Die Grundlagen sind von Microsoft relativ gut beschrieben, aber eine Sache will mir nicht gelingen: Das Abrufen von Parametern im Worker Service.
In meinem Beispiel würde ich gerne die Parameter der Kommandozeile abrufen. Diese stehen auch im Worker in "Configuration" zur Verfügung, ich weiß aber nicht, wie ich den Wert programmtechnisch abrufen muss.
@Papst und @Abt
Nochmals Danke für die Unterstützung! Das Programm funktioniert nun und dieser Thread kann geschlossen bzw. als erledigt markiert werden.
Ich habe die Stelle in der Doku gefunden und sie mir angeschaut. Darauf hin habe ich den Code entsprechend geändert. Jetzt schaue ich mir die Beispiele an und versuche ich mich an die Authentifizierung.
ich habe zu 99,99% nichts mit Zugriff aufs Internet zu tun, daher fällt es mir etwas schwer, den richtigen Einstieg zu finden.
Ich muss den Inhalt einer Seite auslesen und im Abhängigkeit vom Ergebnis eine Seite aufrufen, die passwortgeschützt ist. Das Auslesen der Seite funktioniert ohne nennenswerte Probleme – dazu verwende ich HttpClient:
static string GetContent(string IP)
{
using (var client = new HttpClient())
{
var result = client.GetAsync(IP).Result.Content.ReadAsStringAsync().Result;
int start = result.IndexOf("<body>") + "<body>".Length;
int length = result.IndexOf("</body>") - start;
return result.Substring(start, length);
}
}
Nun möchte ich in Abhängigkeit des Ergebnisses folgenden Befehl absetzen:
Das setzt aber auch voraus, dass man ein Feature-Stop durchsetzt - zumindest solange die einzelnen Umbauten durch sind.
Steht der Zwischenstand, kann man wieder ein paar Features einbauen und macht danach mit dem Umbau weiter.
Wie oft im Leben, liegt die Wahrheit irgendwo zwischen drin... Einiges ist schon ausgelagert und Neues versuchen wir in .NET zu implementieren. Durch unser Geschäftsmodell müssen wir jedoch ständig in der Lage sein, sehr schnell auf gewisse Kundenanforderungen zu reagieren – das zeichnet uns aus und davon leben wir. Das mag in anderen Bereichen vollkommen anders sein, nicht jedoch bei uns.
Ich denke auch, wir haben die Lösung, indem wir von den heutigen zwei Schichten auf drei gehen (hatte ich kurz erwähnt in einem früheren Beitrag). Dann sind wir nicht mehr am alten Framework gefesselt.
Lieben Dank euch beiden! Ja, so ist die Software auch aufgebaut. Das funktioniert auch gut. Mein Wunsch war/ist, wie mehrfach erläutert, eher etwas kosmetisches.
Typische deutsche Tugenden sind die Kristallkugeln so mancher Guru. Ich bin keiner und daher habe ich auch keine Kristallkugel. Allerdings ist ein Kommilitone aus meinem Ingenieurstudium in einer sehr hohen SAP-Etage beschäftigt. Da höre ich was anderes aus erster Quelle. Also könnte ich von deiner Aussage ebenfalls behaupten, die wäre ein schizophrenischer Quark.
Mich wundern ein wenig einige Umgangsformen, die vollkommen unnötig, etwas an sozialer Kompetenz und Empathie vermissen lassen. Wenn ich was zu sagen habe, versuche es mit einer klaren und vor allem freundlichen Sprache. Wenn ich nichts zu sagen habe, dann halte ich einfach meine Klappe.
Danke für deine Beiträge und ein schönes Wochenende!
Das wäre ich strikt trennen. Wartung und kritische Bugfixes ok, aber wenn neue Features rein sollen, baut ihr euch nur riesige Probleme.
Wie Abt schon schrieb, ist eine Misch-Variante nicht möglich, es bleibt also nur die Möglichkeit, jede Änderung in beide Versionen (mit und ohne Gupta) zu pflegen, aber spätestens wenn Ihr auch noch strukturell umbaut, wird das zur Hölle.
Richtig. Allerdings liegt das Problem darin, dass wir zusammen mit dem Kunden in die Tiefe und nicht in die Breite gehen. In anderen Worten: Wir haben – nicht ganz gewollt bzw. geplant :-) – ein großes System entwickelt, mit dem wir in der Lage sind, auf Kundenwünsche bis ins Detail einzugehen. Dadurch haben wir eine überschaubare Kundenbasis, die eine Art Symbiose darstellt. Wären wir in die Breite gegangen, könnten wir mit den vorhandenen Entwicklungsressourcen nicht so in die Tiefe gehen, ohne die Kosten explodieren zu lassen. Deswegen müssen wir weiterhin in der Lage bleiben, jeden Kundenwunsch in akzeptabler Zeit und zu einigermaßen überschaubaren Kosten umsetzen zu können.
Gupta bestand bzw. besteht heute noch in erster Linie aus zwei Schichten: dem SQL-Datenbankserver SQLBase und dem fetten Client mit der Geschäftslogik. Das ist heute in nicht mehr zeitgemäß und ich denke, hier sollten wir ansetzen: Neue Funktionalität packen wir in eine weitere Serverschicht (ASP.NET), wodurch wir das moderne .NET problemlos nutzen können. Wir müssen nun auf der Client-Seite Gupta dazu bringen, damit zu arbeiten, womit wir die gewünschte Brücke bauen könnten. Hier spielt keine große Rolle, dass man dies mit dem .NET-Framework macht.