Beim Arbeiten an einem C#-Projekt und XML-Dateien stand ich mal wieder vor einem Problem, bei dem ich von einer einfachen Lösungen ausgegangen war… was natürlich nicht der Fall ist.
Folgende Situation:
Das Programm erlaubt es dem Nutzer aktuelle Daten in XML-Dateien zu speichern und natürlich auch wieder zu laden. Um die Kontrollarbeit beim Laden zu erleichtern, gibt es immer eine aktuelle Dokumentypdefinition (DTD), die der XML-Datei zugewiesen wird. Die DTD mittels
DTD-Definition in XML
-
<!DOCTYPE Wurzelelement SYSTEM "datei.dtd">
lokal verlinkt. Dies führt aber zu dem Problem, dass das Programm beim Öffnen der XML-Datei die DTD auch immer am gleichen Ort erwartet. Dies ist natürlich problematisch sobald der Nutzer seine Datei irgendwo nach Belieben speichern möchte. Den absoluten Ort der DTD anzugeben, ist aber auch keine Lösung, da es dann natürlich beim Dateiaustausch oder Ähnlichem zu Problemen kommt.
Die Lösung liegt also darin, den Zugriff auf die DTD auf einen zentralen Ort umzubiegen. Nach reichlich Recherchearbeit und Experimentieren habe ich zwei mögliche Lösungen erarbeitet, die an der gleichen Stelle ansetzen. Das Suchen und Lesen der DTD übernimmt bei C#, wenn man wie ich mit XmlReader arbeitet, ein (abstrakter) XmlResolver. Also habe ich mir einen eigenen XmlResolver von der konkreten Klasse XmlUrlResolver abgeleitet und dort die zuständige Methode GetEntity() überschrieben. Es wird nun jeweils via Regex überprüft, ob eine DTD-Datei gesucht wird (Der Name wird in der Variablen file gespeichert…). Wenn dies der Fall ist, wird der Zugriff geändert:
Variante 1 – DTD liegt in einem Unterverzeichnis des Programmverzeichnis:
Hierbei wird der lokale Suchort auf den zentralen Ort umgebogen und der Get-Aufruf dann einfach an die Basisklasse weitergereicht:
Umbiegen des DTD-Verzeichnisses
-
string pathToExe = Assembly.GetExecutingAssembly().Location;
-
pathToExe = pathToexe.Substring(0,pathToExe.IndexOf("name.exe"));
-
Uri newUri = new Uri(pathToExe + "Unterverzeichnis\\" + file);
-
return base.GetEntity(newUri, role, ofObjectToReturn);
Die Parameter role und ofObjectToReturn werden einfach so übernommen, da sich da ja nichts ändert.
Variante 2 – DTD liegt als “Embedded Resource” vor:
Hierbei können wir uns das “reale” Speichern der DTD sparen und verminden so Fehlerquellen, wie Nutzer die DTDs löschen oder Ähnliches. Dazu wird nicht der Dateiort auf der Festplatte umgebogen, sondern wir lesen die DTD aus dem Speicher:
Zugriff auf eingebettete DTD
-
StreamReader r = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("namespace.Resources." + file));
-
return r.BaseStream;
Wichtig dabei ist, dass man auf die korrekte Schreibweise der Resourcen achtet. Sonst findet er da nichts und man wundert sich, warum es nicht geht (
).Dies und auch ob die Datei richtig eingebettet wurde kann man einfach über die Methode GetManifestResourceNames() überprüfen.
Wie bereits geschrieben, habe ich mir das eindeutig einfacher vorgestellt, aber nun ist’s ja doch endlich geschafft. Vielleicht kann das jemandem mit einem ähnlichen Problem ja helfen oder vielleicht hat jemand ja noch eine bessere Lösung.