Customizing XSD->Classes code generation, the "easy"
way
Note: this entry
has moved.
Update: check a more
thorough explanation of this techique in
Code Generation in the .NET Framework Using XML Schema article published in the
MSDN XML DevCenter, and the companion post on
the VS.NET custom tool
for it.
I've always been disgusted by the imposibility to customize
XSD.EXE tool. I've even thought about
some workaround
for the all-public-fields issue. However, I was
WRONG. It's perfectly
possible to generate fully customized classes from an XSD
Schema, if not by calling XSD.EXE, by reusing the very same
classes it uses.
This is achievable without any reflection hack! All public
(althought certainly undocumented) classes and methods are
used.
The "trick" involves using two key classes:
XmlSchemaImporter and
XmlCodeExporter, both from the
System.Xml.Serialization
namespace.
// Load the schema to process.
XmlSchema xsd = XmlSchema.Read( stm, null );
// Collection of schemas for the XmlSchemaImporter
XmlSchemas xsds = new XmlSchemas();
xsds.Add( xsd );
XmlSchemaImporter imp = new XmlSchemaImporter( xsds );
// System.CodeDom namespace for the XmlCodeExporter to put classes in
CodeNamespace ns = new CodeNamespace( "Generated" );
XmlCodeExporter exp = new XmlCodeExporter( ns );
// Iterate schema items (top-level elements only) and generate code for each
foreach ( XmlSchemaObject item in xsd.Items )
{
if ( item is XmlSchemaElement )
{
// Import the mapping first
XmlTypeMapping map = imp.ImportTypeMapping(
new XmlQualifiedName( ( ( XmlSchemaElement ) item ).Name,
xsd.TargetNamespace ) );
// Export the code finally
exp.ExportTypeMapping( map );
}
}
// Code generator to build code with.
ICodeGenerator generator = new CSharpCodeProvider().CreateGenerator();
// Generate untouched version
using ( StreamWriter sw = new StreamWriter( @"E:\Generated.Full.cs", false ) )
{
generator.GenerateCodeFromNamespace(
ns, sw, new CodeGeneratorOptions() );
}
The CodeNamespace variable
ns contains a full CodeDom
hierarchy with all the types that were generated. Therefore,
we can easily customize their definitions by adding
attributes, custom methods, etc. Even converting those
annoying public fields to properties, which is now much more
robust than the find-and-replace method I used on a previous
life :):
+
FieldsToProperties method
static void FieldsToProperties(CodeNamespace ns)
{
// Copy the colletion to an array for safety. We will be
// changing this collection.
CodeTypeDeclaration[] types = new CodeTypeDeclaration[ns.Types.Count];
ns.Types.CopyTo( types, 0 );
// Turn fields into properties
foreach ( CodeTypeDeclaration type in types )
{
// Copy the colletion to an array for safety. We will be
// changing this collection.
CodeTypeMember[] members = new CodeTypeMember[type.Members.Count];
type.Members.CopyTo(members, 0);
foreach ( CodeTypeMember member in members )
{
// Process fields only.
if ( member is CodeMemberField )
{
CodeMemberProperty prop = new CodeMemberProperty();
prop.Name = member.Name;
// Rename field
member.Name = String.Concat( "_", member.Name.ToLower() );
prop.Attributes = member.Attributes;
prop.Type = ( ( CodeMemberField )member ).Type;
prop.HasGet = true;
prop.HasSet = true;
// Add get/set statements pointing to field.
prop.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeFieldReferenceExpression( new CodeThisReferenceExpression(),
member.Name ) ) );
prop.SetStatements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression( new CodeThisReferenceExpression(),
member.Name ),
new CodeArgumentReferenceExpression( "value" ) ) );
// Copy attributes from field to the property.
prop.CustomAttributes.AddRange(member.CustomAttributes);
member.CustomAttributes.Clear();
// Make field private
member.Attributes = MemberAttributes.Private;
// Finally add the property to the type
type.Members.Add( prop );
}
}
}
}
Now, simply passing the namespace generated by the previous
code will result in custom classes with properties instead
of fields, with the appropriate XmlSerialization attributes
as generated initially. Below is the customized complete
schema for the Pubs database:
+
Pubs XSD schema customized.
namespace Lagash.Generated {
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.lagash.com/schemas")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://www.lagash.com/schemas", IsNullable=false)]
public class dsPubs {
/// <remarks/>
private dsPubsPublishers[] _items;
[System.Xml.Serialization.XmlElementAttribute("publishers")]
public dsPubsPublishers[] Items {
get {
return this._items;
}
set {
this._items = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.lagash.com/schemas")]
public class dsPubsPublishers {
/// <remarks/>
private string _pub_id;
/// <remarks/>
private string _pub_name;
/// <remarks/>
private string _city;
/// <remarks/>
private string _state;
/// <remarks/>
private string _country;
/// <remarks/>
private dsPubsPublishersTitles[] _titles;
public string pub_id {
get {
return this._pub_id;
}
set {
this._pub_id = value;
}
}
public string pub_name {
get {
return this._pub_name;
}
set {
this._pub_name = value;
}
}
public string city {
get {
return this._city;
}
set {
this._city = value;
}
}
public string state {
get {
return this._state;
}
set {
this._state = value;
}
}
public string country {
get {
return this._country;
}
set {
this._country = value;
}
}
[System.Xml.Serialization.XmlElementAttribute("titles")]
public dsPubsPublishersTitles[] titles {
get {
return this._titles;
}
set {
this._titles = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.lagash.com/schemas")]
public class dsPubsPublishersTitles {
/// <remarks/>
private string _title_id;
/// <remarks/>
private string _title;
/// <remarks/>
private string _type;
/// <remarks/>
private string _pub_id;
/// <remarks/>
private string _price;
/// <remarks/>
private string _advance;
/// <remarks/>
private string _royalty;
/// <remarks/>
private string _ytd_sales;
/// <remarks/>
private string _notes;
/// <remarks/>
private string _pubdate;
public string title_id {
get {
return this._title_id;
}
set {
this._title_id = value;
}
}
public string title {
get {
return this._title;
}
set {
this._title = value;
}
}
public string type {
get {
return this._type;
}
set {
this._type = value;
}
}
public string pub_id {
get {
return this._pub_id;
}
set {
this._pub_id = value;
}
}
public string price {
get {
return this._price;
}
set {
this._price = value;
}
}
public string advance {
get {
return this._advance;
}
set {
this._advance = value;
}
}
public string royalty {
get {
return this._royalty;
}
set {
this._royalty = value;
}
}
public string ytd_sales {
get {
return this._ytd_sales;
}
set {
this._ytd_sales = value;
}
}
public string notes {
get {
return this._notes;
}
set {
this._notes = value;
}
}
public string pubdate {
get {
return this._pubdate;
}
set {
this._pubdate = value;
}
}
}
}
+
Complete Pubs XSD
<?xml version="1.0" ?>
<xs:schema id="dsPubs" targetNamespace="http://www.lagash.com/schemas" xmlns:mstns="http://www.lagash.com/schemas"
xmlns="http://www.lagash.com/schemas" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true" msdata:EnforceConstraints="False">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="publishers">
<xs:complexType>
<xs:sequence>
<xs:element name="pub_id" type="xs:string" minOccurs="0" />
<xs:element name="pub_name" type="xs:string" minOccurs="0" />
<xs:element name="city" type="xs:string" minOccurs="0" />
<xs:element name="state" type="xs:string" minOccurs="0" />
<xs:element name="country" type="xs:string" minOccurs="0" />
<xs:element name="titles" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title_id" type="xs:string" minOccurs="0" />
<xs:element name="title" type="xs:string" minOccurs="0" />
<xs:element name="type" type="xs:string" minOccurs="0" />
<xs:element name="pub_id" type="xs:string" minOccurs="0" />
<xs:element name="price" type="xs:string" minOccurs="0" />
<xs:element name="advance" type="xs:string" minOccurs="0" />
<xs:element name="royalty" type="xs:string" minOccurs="0" />
<xs:element name="ytd_sales" type="xs:string" minOccurs="0" />
<xs:element name="notes" type="xs:string" minOccurs="0" />
<xs:element name="pubdate" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>