XSD.exe /c - Klassengenerierung richtig gemacht
Einleitung:XSD.exe hat so seine Tücken, ich glaube das zeigt auch der große Unmut, der im Internet zu finden ist. Dabei muss das doch nicht sein

Ich habe festgestellt, dass wenn man XSD verwenden möchte, dass es sinnvoll ist den Berg zum Propheten zu tragen. Im Folgenden erkläre ich, was das Problem eigentlich so dabei ist, und wie man das geschickt umschiffen kann.
Problembeschreibung:XSD generiert lediglich aus den Teilen Klassen und Felder, von denen es denkt, dass diese zu gebrauchen sind. Dabei beachtet es nur die Elemente in den Schemas, Complex- und SimpleTypes für sich betrachtet lässt es außen vor.
Beispiel:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Ein SimpleElement, das ebenfalls in der Klassengeneration beachtet werden soll -->
<xs:simpleType name="SimpleRootElement">
<xs:restriction base="xs:string">
<xs:enumeration value="TestVal1" />
<xs:enumeration value="TestVal2" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
Hier meldet XSD die Fehlermeldung
Warning: cannot generate classes because no top-level elements with complex type were found.Genauso sieht es aus, wenn das Schema so aussehen würde:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Ein normales Element, das kein ComplexType verwendet -->
<xs:element name="CommonRootElement" type="xs:string" />
</xs:schema>
Und wiederum folgendes Schema, das ebenfalls die o.g. Fehlermeldung provoziert:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Ein normales Element, das kein ComplexType verwendet -->
<xs:element name="CommonRootElement" type="xs:string" />
<!-- Ein ComplexType, aus dem eine Klasse generiert werden soll -->
<xs:complexType name="ComplexTypeWhichIsReferenced">
<xs:sequence>
<xs:element name="CommonInnerElement1" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
Was will das "doofe" XSD denn überhaupt, warum erzeugt es mir aus dem Schema keine Klassen?
Problemerklärung:XSD will zwingend ein ElementType, das im Root des Xml-Baumes des Schemas liegt. Weiterhin will es zwingend mindestens einen ComplexType definiert wissen, welches von solch einem Root-Element referenziert wird. Also folgendes Schema ist als Basis für XSD ausreichend, so dass es mit einer Klassengenerierung beginnt:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll
Hier gibt es allerdings ein Top level element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced2' veweist-->
<xs:element name="TestElement2" type="ComplexTypeWhichIsReferenced2" />
<xs:complexType name="ComplexTypeWhichIsReferenced2">
<xs:sequence>
<xs:element name="CommonInnerElement2" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
Ergebnis (Kommentare und Attribute entfernt):
public partial class ComplexTypeWhichIsReferenced2 {
private string commonInnerElement2Field;
/// <remarks/>
public string CommonInnerElement2 {
get {
return this.commonInnerElement2Field;
}
set {
this.commonInnerElement2Field = value;
}
}
}Na das ist doch soweit super, oder? Jetzt hat XSD endlich ein Ergebnis produziert, das auch einem prüfenden Blick Stand hält.
Aber...
Erweiterte Problembeschreibung:Wenn jetzt Kombinationen aus den oben genannten Schemas und deren Fehlermeldungen mit dem funktionierenden Beispiel zusammenspielen, dann wird es gefährlich. Denn dann wird XSD keine Fehlermeldung ausspucken und aber nur einen Teil für die Klassengenerierung heranziehen. Das ist denke ich das größte Manko, das diesem Tool angelastet wird. So denkt der Benutzer, dass das Tool eine gewisse undurchschaubare Eigenintelligenz verwendet.
Ich habe hier einmal ein kleines Schema entwickelt, das sehr viele Probleme auf einmal betrachtet:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Ein SimpleElement, das ebenfalls in der Klassengeneration beachtet werden soll -->
<xs:simpleType name="SimpleRootElement">
<xs:restriction base="xs:string">
<xs:enumeration value="TestVal1" />
<xs:enumeration value="TestVal2" />
</xs:restriction>
</xs:simpleType>
<!-- Ein normales Element, das kein ComplexType verwendet -->
<xs:element name="CommonRootElement" type="xs:string" />
<!-- Ein ComplexType, aus dem eine Klasse generiert werden soll -->
<xs:complexType name="ComplexTypeWhichIsReferenced">
<xs:sequence>
<xs:element name="CommonInnerElement1" type="xs:string" />
</xs:sequence>
</xs:complexType>
<!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll
Hier gibt es allerdings ein Top level element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced2' veweist-->
<xs:element name="TestElement2" type="ComplexTypeWhichIsReferenced2" />
<xs:complexType name="ComplexTypeWhichIsReferenced2">
<xs:sequence>
<xs:element name="CommonInnerElement2" type="xs:string" />
</xs:sequence>
</xs:complexType>
<!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll
Hier gibt es ein inneres Element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced' veweist-->
<xs:complexType name="TestInnerLink">
<xs:sequence>
<xs:element name="ComplexInnerElement" type="ComplexTypeWhichIsReferenced" />
</xs:sequence>
</xs:complexType>
</xs:schema>
Wenn man das so mittels XSD durch den Klassengenerator jagt, dann entsteht folgender C# Code:
public partial class ComplexTypeWhichIsReferenced2 {
private string commonInnerElement2Field;
/// <remarks/>
public string CommonInnerElement2 {
get {
return this.commonInnerElement2Field;
}
set {
this.commonInnerElement2Field = value;
}
}
}Was unschwer zu erkennen ist: Das ist genau das Gleiche, wie in dem oben genannten funktionierenden Minischema. Also hier hat XSD alles andere als das
TestElement2 ignoriert!
Lösung:Die Lösung ist aus der oben erwähnten Fehlermeldung heraus begründet. XSD betrachtet nur den Inhalt zur Klassengenerierung, welcher als Root-Element auf andere Complex-Typen verweist.
Das könnte man nun manuell machen, oder es automatisiert anwenden. Ich habe hier ein kleines Tool geschrieben, das die notwendigen Elemente einfach dem Schema hinzufügt. Die Logik im Schema ändert sich quasi nicht, aber XSD ist glücklich und das Ergebnis sieht sehr viel besser aus.
Das Tool macht von der Logik her folgendes (Codeausschnitt):
/// <summary>
/// Methode, die ein XML Schema erweitert, so dass keine ClassType ohne Element existieren,
/// das darauf verweist.
/// </summary>
/// <remarks>Verhindert die Warnung:
/// cannot generate classes because no top-level elements with complex type were found.</remarks>
/// <param name="xsdFullFilename">Der voll qualifizierte Dateiname zur XSD Schemadatei.</param>
/// <param name="throwError">Definiert, ob im Fehlerfall eine Exception geworfen werden soll.</param>
/// <returns>True, wenn die Erweiterung der Schemadatei erfolgreich war, andernfalls False.</returns>
public static bool GenerateDummyElementsForClassTypes(string xsdFullFilename, bool throwError)
{
XmlDocument schemaDoc = null;
bool retVal = false;
List<string> rootElements = new List<string>();
Dictionary<string, string> rootElementsToAddToSchema = new Dictionary<string, string>();
try
{
// Überprüfung, ob Schemadatei vorhanden
if (!System.IO.File.Exists(xsdFullFilename))
{
throw new System.IO.FileNotFoundException(String.Format("Konnte die Datei '{0}' nicht finden!", xsdFullFilename));
}
// Versuch das Schema in ein XmlDokument zu laden
schemaDoc = new XmlDocument();
schemaDoc.Load(xsdFullFilename);
// Alle Root-Elements herausfinden
ExtractAllElements(schemaDoc, rootElements);
// Alle Root-ComplexTypes herausfinden
ExtractAllClassTypes(schemaDoc, rootElements, rootElementsToAddToSchema);
// Alle vorgemerkten Root-Elements dem Schema hinzufügen
AddNecessaryRootElementsToSchemafile(xsdFullFilename, schemaDoc, rootElementsToAddToSchema);
retVal = true;
}
catch
{
// wenn gewünscht, dann Fehler schmeißen, ansonsten nur den
// Rückgabewert auf "False" setzen.
if (throwError)
{
throw;
}
retVal = false;
}
return retVal;
}Es gibt einen vollständigen Download des VisualStudio 2010 Projektes hier:
XsdPrepare.zipDurch Anwendung der Methode
GenerateDummyElementsForClassTypes entsteht aus dem kompletten Schema oben folgendes:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Ein SimpleElement, das ebenfalls in der Klassengeneration beachtet werden soll -->
<xs:simpleType name="SimpleRootElement">
<xs:restriction base="xs:string">
<xs:enumeration value="TestVal1" />
<xs:enumeration value="TestVal2" />
</xs:restriction>
</xs:simpleType>
<!-- Ein normales Element, das kein ComplexType verwendet -->
<xs:element name="CommonRootElement" type="xs:string" />
<!-- Ein ComplexType, aus dem eine Klasse generiert werden soll -->
<xs:complexType name="ComplexTypeWhichIsReferenced">
<xs:sequence>
<xs:element name="CommonInnerElement1" type="xs:string" />
</xs:sequence>
</xs:complexType>
<!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll
Hier gibt es allerdings ein Top level element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced2' veweist-->
<xs:element name="TestElement2" type="ComplexTypeWhichIsReferenced2" />
<xs:complexType name="ComplexTypeWhichIsReferenced2">
<xs:sequence>
<xs:element name="CommonInnerElement2" type="xs:string" />
</xs:sequence>
</xs:complexType>
<!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll
Hier gibt es ein inneres Element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced' veweist-->
<xs:complexType name="TestInnerLink">
<xs:sequence>
<xs:element name="ComplexInnerElement" type="ComplexTypeWhichIsReferenced" />
</xs:sequence>
</xs:complexType>
<xs:element name="RootElement0" type="SimpleRootElement" />
<xs:element name="RootElement1" type="TestInnerLink" />
</xs:schema>
Und wenn man dann XSD nochmal drüberlaufen lässt, dann entsteht folgender Code:
public partial class ComplexTypeWhichIsReferenced2 {
private string commonInnerElement2Field;
/// <remarks/>
public string CommonInnerElement2 {
get {
return this.commonInnerElement2Field;
}
set {
this.commonInnerElement2Field = value;
}
}
}
public partial class ComplexTypeWhichIsReferenced {
private string commonInnerElement1Field;
/// <remarks/>
public string CommonInnerElement1 {
get {
return this.commonInnerElement1Field;
}
set {
this.commonInnerElement1Field = value;
}
}
}
public enum SimpleRootElement {
/// <remarks/>
TestVal1,
/// <remarks/>
TestVal2,
}
public partial class TestInnerLink {
private ComplexTypeWhichIsReferenced complexInnerElementField;
/// <remarks/>
public ComplexTypeWhichIsReferenced ComplexInnerElement {
get {
return this.complexInnerElementField;
}
set {
this.complexInnerElementField = value;
}
}
}Wenn man jetzt ein letztes Mal genau drauf schaut, dann hat XSD den Fall
CommonRootElement nicht betrachtet, aber hier muss man zu dessen Schutz sagen, hier wüsste ich auch nicht, wie man hieraus eine Klasse basteln soll.
Aber mit meinem kleinen Helfertool gehen nun (hoffentlich) keine Inhalte mehr durch die Klassengenerierung verloren, und so kann man das auch in automatisierten Prozessen dann einsetzen.
Ich hoffe, dass die nähere Betrachtung hilfreich war, und dass ihr etwas mit dem Tool anfangen könnt. Über Kommentare und Verbesserungsvorschläge würde ich mich freuen.
Es gibt einen vollständigen Download des VisualStudio 2010 Projektes
hier:
XsdPrepare.zip