XSD Tools in .NET8 – Part4 – XsdExe- Advanced

Mark PelfMark Pelf
10 min read

XSD Tools in .NET8 – Part4 – XsdExe- Advanced

A practical guide to XSD tools available in .NET8 environment.

Abstract: A practical guide to XML and XSD tools available in .NET8 environment, focusing on generating and using C# classes to process some XML valid for some given XSD (technology as of September 2024).

I was recently doing some work related to XML and XSD processing in .NET8 environment and created several proof-of-concept applications to evaluate the tools available. These articles are the result of my prototyping work.

1.1 List of tools used/tested

Here are the tools used/tested:

  • Visual Studio 2022
  • XSD.EXE (Microsoft license, part of VS2022)
  • XmlSchemaClassGenerator (Open Source/Freeware)
  • LinqToXsdCore (Open Source/Freeware)
  • Liquid XML Objects (Commercial license)

1.2 Articles in this series

For technical reasons, I will organize this text into several articles:

  • XSD Tools in .NET8 – Part1 – VS2022
  • XSD Tools in .NET8 – Part2 – C# validation
  • XSD Tools in .NET8 – Part3 – XsdExe – Simple
  • XSD Tools in .NET8 – Part4 – XsdExe - Advanced
  • XSD Tools in .NET8 – Part5 – XmlSchemaClassGenerator – Simple
  • XSD Tools in .NET8 – Part6 – XmlSchemaClassGenerator – Advanced
  • XSD Tools in .NET8 – Part7 – LinqToXsdCore – Simple
  • XSD Tools in .NET8 – Part8 – LinqToXsdCore – Advanced
  • XSD Tools in .NET8 – Part9 – LiquidXMLObjects – Simple
  • XSD Tools in .NET8 – Part10 – LiquidXMLObjects – Advanced

2 More theory about XML and XSD rules

Here is some more theory about XML and XSD rules.

2.1 Optional Xml-Element and Xml-Attribute

Optional: Does not need to be present in the XML.

For XSD Schema elements:
Optional: minOccurs="0" attribute ->In order to set a schema element as optional, you include the minOccurs="0" attribute

2.2 The Difference Between Optional and Not Required for Xml-Element and Xml-Attribute

Note the difference:

  • Optional: Does not need to be present in the XML.
  • Not Required: Does not need to have a value.

You can have any combination:

  • Optional + Not Required
  • Optional + Required
  • Not Optional + Not Required
  • Not Optional + Required

For XSD Schema elements:

  • Optional: minOccurs="0" attribute ->In order to set a schema element as optional, you include the minOccurs="0" attribute
  • Required: nillable="true" attribute ->In order to set a schema element as not required, you include the nillable="true" attribute.

String data types are not required by default, though you can force them to be required.
Other data types, such as Boolean, Integer, Date, Time, etc. are all required by default. In order to make one of these data types not required, you must set the nillable attribute equal to true for the element in the schema.

3 Examples of XML and XSD

Here are some sample XML-s and XSD-s I created for test purposes.

3.1 Advanced case

Please note that this example XML/XSD has an Optional and Not-Required Xml-Element. Read the comments inside for more details.

<?xml version="1.0" encoding="utf-8"?>
<!--SmallCompany.xsd++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<xs:schema attributeFormDefault="unqualified" 
           elementFormDefault="qualified" 
           targetNamespace="https://markpelf.com/SmallCompany.xsd" 
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="SmallCompany">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="CompanyName" type="xs:string" />
                <xs:element maxOccurs="unbounded" name="Employee">
                    <xs:complexType>
                        <xs:sequence>
                            <!--Name_String_NO is String NotOptional-->
                            <xs:element name="Name_String_NO" type="xs:string" />
                            <!--City_String_O is String Optional-->
                            <xs:element minOccurs="0" name="City_String_O" type="xs:string" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
                <xs:element maxOccurs="unbounded" name="InfoData">
                    <xs:complexType>
                        <xs:sequence>
                            <!--Id_Int_NO is Int NotOptional-->
                            <xs:element name="Id_Int_NO" type="xs:int" />
                            <!--Quantity_Int_O is Int Optional-->
                            <xs:element minOccurs="0" name="Quantity_Int_O" type="xs:int" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>
<?xml version="1.0" encoding="utf-8"?>
<!--SmallCompanyAAA.xml+++++++++++++++++++++++++++++++++++++++++++++++-->
<SmallCompany xmlns="https://markpelf.com/SmallCompany.xsd">
    <CompanyName>SmallCompanyAAA</CompanyName>
    <Employee>
        <Name_String_NO>Mark</Name_String_NO>
        <City_String_O>Belgrade</City_String_O>
    </Employee>
    <Employee>
        <Name_String_NO>John</Name_String_NO>
    </Employee>
    <InfoData>
        <Id_Int_NO>11</Id_Int_NO>
        <Quantity_Int_O>123</Quantity_Int_O>
    </InfoData>
    <InfoData>
        <Id_Int_NO>22</Id_Int_NO>
    </InfoData>
</SmallCompany>

4 Using XSD.EXE tool to create C# class

We focus in this article on the usage of XSD.EXE tool to generate C# class from XSD file.
Here is the tool's basic info.


Tool name============================
XSD.exe

License============================ 
Microsoft, comming with Visual Studio

Where to get it=========================
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8.1 Tools\x64\xsd.exe

Version==========================
Windows PowerShell > ./xsd.exe
Microsoft (R) Xml Schemas/DataTypes support utility
[Microsoft (R) .NET Framework, Version 4.8.9037.0]
Copyright (C) Microsoft Corporation. All rights reserved.

Help============================
Windows PowerShell > ./xsd.exe
Microsoft (R) Xml Schemas/DataTypes support utility
[Microsoft (R) .NET Framework, Version 4.8.9037.0]
Copyright (C) Microsoft Corporation. All rights reserved.

xsd.exe -
    Utility to generate schema or class files from given source.

xsd.exe <schema>.xsd /classes|dataset [/e:] [/l:] [/n:] [/o:] [/s] [/uri:]
xsd.exe <assembly>.dll|.exe [/outputdir:] [/type: [...]]
xsd.exe <instance>.xml [/outputdir:]
xsd.exe <schema>.xdr [/outputdir:]

     - OPTIONS -

/classes
    Generate classes for this schema. Short form is '/c'.

/dataset
    Generate sub-classed DataSet for this schema. Short form is '/d'.

/enableLinqDataSet
    Generate LINQ-enabled sub-classed Dataset for the schemas provided.  Short form is '/eld'.

/element:<element>
    Element from schema to process. Short form is '/e:'.

/fields
    Generate fields instead of properties. Short form is '/f'.

/order
    Generate explicit order identifiers on all particle members.

/enableDataBinding
    Implement INotifyPropertyChanged interface on all generated types
    to enable data binding. Short form is '/edb'.

/language:<language>
    The language to use for the generated code. Choose from 'CS', 'VB', 'JS',
    'VJS', 'CPP' or provide a fully-qualified name for a class implementing
    System.CodeDom.Compiler.CodeDomProvider. The default language
    is 'CS' (CSharp). Short form is '/l:'.

/namespace:<namespace>
    The namespace for generated class files. The default namespace
    is the global namespace. Short form is '/n:'.

/nologo
    Suppresses the banner.

/out:<directoryName>
    The output directory to create files in. The default
    is the current directory. Short form is '/o:'.

/type:<type>
    Type from assembly to generate schema for. Multiple types may be provided.
    If no types are provided, then schemas for all types in an assembly
    are generated. Short form is '/t:'.

/uri:<uri>
    Uri of elements from schema to process. Short form is '/u:'.

     - ADVANCED -

/parameters:<file>
    Read command-line options from the specified xml file. Short form is '/p:'.

     - ARGUMENTS -
<schema>.xsd       Name of a schema containing elements to import.
<assembly>.dll|exe Name of an assembly containing types to generate schema for.
<instance>.xml     Name of an xml file to infer xsd schema from.
<schema>.xdr       Name of an xdr schema to convert to xsd.
Multiple file arguments of the same type may be provided.
========================================================
Usage Examples===================                          
Instructions to generate C# class 
Windows PowerShell> ./xsd.exe SmallCompany.xsd /classes /namespace:Example1
Windows PowerShell> ./xsd.exe BigCompany.xsd /classes /namespace:Example1
======================================

5 Generated C# class

Here is the C# generated by the above tool based on the above presented XSD BigCompany.xsd.
Here is the class's full code.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

// 
// This source code was auto-generated by xsd, Version=4.8.9037.0.
// 
namespace Example1 {
    using System.Xml.Serialization;


    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.9037.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="https://markpelf.com/BigCompany.xsd")]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="https://markpelf.com/BigCompany.xsd", IsNullable=false)]
    public partial class BigCompany {

        private string companyNameField;

        private BigCompanyEmployee[] employeeField;

        private BigCompanyInfoData[] infoDataField;

        /// <remarks/>
        public string CompanyName {
            get {
                return this.companyNameField;
            }
            set {
                this.companyNameField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("Employee")]
        public BigCompanyEmployee[] Employee {
            get {
                return this.employeeField;
            }
            set {
                this.employeeField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("InfoData")]
        public BigCompanyInfoData[] InfoData {
            get {
                return this.infoDataField;
            }
            set {
                this.infoDataField = value;
            }
        }
    }

    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.9037.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="https://markpelf.com/BigCompany.xsd")]
    public partial class BigCompanyEmployee {

        private string name_String_NOField;

        private string city_String_OField;

        /// <remarks/>
        public string Name_String_NO {
            get {
                return this.name_String_NOField;
            }
            set {
                this.name_String_NOField = value;
            }
        }

        /// <remarks/>
        public string City_String_O {
            get {
                return this.city_String_OField;
            }
            set {
                this.city_String_OField = value;
            }
        }
    }

    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.9037.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="https://markpelf.com/BigCompany.xsd")]
    public partial class BigCompanyInfoData {

        private int data1_Int_NO_RField;

        private System.Nullable<int> data2_Int_NO_NRField;

        private int data3_Int_O_RField;

        private bool data3_Int_O_RFieldSpecified;

        private System.Nullable<int> data4_Int_O_NRField;

        private bool data4_Int_O_NRFieldSpecified;

        /// <remarks/>
        public int Data1_Int_NO_R {
            get {
                return this.data1_Int_NO_RField;
            }
            set {
                this.data1_Int_NO_RField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
        public System.Nullable<int> Data2_Int_NO_NR {
            get {
                return this.data2_Int_NO_NRField;
            }
            set {
                this.data2_Int_NO_NRField = value;
            }
        }

        /// <remarks/>
        public int Data3_Int_O_R {
            get {
                return this.data3_Int_O_RField;
            }
            set {
                this.data3_Int_O_RField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool Data3_Int_O_RSpecified {
            get {
                return this.data3_Int_O_RFieldSpecified;
            }
            set {
                this.data3_Int_O_RFieldSpecified = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
        public System.Nullable<int> Data4_Int_O_NR {
            get {
                return this.data4_Int_O_NRField;
            }
            set {
                this.data4_Int_O_NRField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool Data4_Int_O_NRSpecified {
            get {
                return this.data4_Int_O_NRFieldSpecified;
            }
            set {
                this.data4_Int_O_NRFieldSpecified = value;
            }
        }
    }
}

Here is the class diagram.

6 Two C# API styles for Optional Xml-Elements

There are two approaches/styles for marking Optional Xml-Element presence in generated C# code:

  1. The first is bool_flag_style – using a bool flag to indicate the presence of optional Xml-Element, with flag=false to indicate the Xml-Element was not present.
    For example, for some Xml-Element ElemA that, if present, will have value integer, you will get in C# generated two variables “_bool ElemA_flag, int ElemAvalue”. You need to check if element ElemA was present by first checking the flag _ElemAflag; and then if it is true, you go for the value of _ElemAvalue. If you do not check flag _ElemAflag first, and just go for the value of _ElemAvalue you might pick the default int value of zero (0), and you can not know if that is just the default value for C# variable that is always present, but Xml-Element was not present, or that element was present and it actually had the value of zero (0).
  2. The second is nullable_type_style – using a nullable type to indicate the presence of Xml-Element, with value=null to indicate the Xml-Element was not present.
    For example, for some Xml-Element ElemA that, if present, will have value integer, you will get in C# generated variable “_int? ElemAnullableValue”. You need to check if element ElemA was present by first checking the _ElemAnullableValue not being null; and then if it is not meaning the element was present, you go for the int value of _ElemAnullableValue.

7 Sample C# app

Here is a sample C# code using the above generated C# class to load and process the above presented XML BigCompanyMMM.xml.

public static void ProcessVer1_Process2(
    string? filePath,
    Microsoft.Extensions.Logging.ILogger? logger)
{
    try
    {
        logger?.LogInformation(
            "+++ProcessVer1_Process2-Start++++++++++++++++++");
        logger?.LogInformation("filePath:" + filePath);

        XmlSerializer ser = new XmlSerializer(typeof(Example1.BigCompany));
        TextReader textReader = File.OpenText(filePath ?? String.Empty);
        Example1.BigCompany? xmlObject = ser.Deserialize(textReader) as Example1.BigCompany;

        if (xmlObject != null)
        {
            logger?.LogInformation("CompanyName:" + xmlObject.CompanyName);

            foreach (Example1.BigCompanyEmployee item in xmlObject.Employee)
            {
                logger?.LogInformation("------------");
                logger?.LogInformation("Name_String_NO:" + item.Name_String_NO);
                logger?.LogInformation("City_String_O:" + item.City_String_O);
            }

            foreach (Example1.BigCompanyInfoData item in xmlObject.InfoData)
            {
                logger?.LogInformation("------------");
                logger?.LogInformation("Data1_Int_NO_R:" + item.Data1_Int_NO_R);
                logger?.LogInformation("Data2_Int_NO_NR:" + (item.Data2_Int_NO_NR?.ToString() ?? "null"));
                logger?.LogInformation("Data3_Int_O_RSpecified:" + item.Data3_Int_O_RSpecified.ToString());
                logger?.LogInformation("Data3_Int_O_R:" + item.Data3_Int_O_R.ToString());
                logger?.LogInformation("Data4_Int_O_NRSpecified:" + item.Data4_Int_O_NRSpecified.ToString());
                logger?.LogInformation("Data4_Int_O_NR:" +( item.Data4_Int_O_NR?.ToString() ?? "null"));
            }
        }
        else
        {
            logger?.LogError("xmlObject == null");
        }

        logger?.LogInformation(
            "+++ProcessVer1_Process2-End++++++++++++++++++");
    }
    catch (Exception ex)
    {
        string methodName =
            $"Type: {System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.FullName}, " +
            $"Method: ProcessVer1_Process2; ";
        logger?.LogError(ex, methodName);
    }
}

And here is the log of execution.

+++ProcessVer1_Process2-Start++++++++++++++++++
 filePath:C:\TmpXSD\XsdExample_Ver1\Example01\bin\Debug\net8.0\XmlFiles\BigCompanyMMM.xml
 CompanyName:BigCompanyMMM
 ------------
 Name_String_NO:Mark
 City_String_O:Belgrade
 ------------
 Name_String_NO:John
 City_String_O:
 ------------
 Data1_Int_NO_R:555
 Data2_Int_NO_NR:16
 Data3_Int_O_RSpecified:True
 Data3_Int_O_R:333
 Data4_Int_O_NRSpecified:True
 Data4_Int_O_NR:17
 ------------
 Data1_Int_NO_R:123
 Data2_Int_NO_NR:null
 Data3_Int_O_RSpecified:True
 Data3_Int_O_R:15
 Data4_Int_O_NRSpecified:True
 Data4_Int_O_NR:null
 ------------
 Data1_Int_NO_R:777
 Data2_Int_NO_NR:null
 Data3_Int_O_RSpecified:False
 Data3_Int_O_R:0
 Data4_Int_O_NRSpecified:False
 Data4_Int_O_NR:null
 +++ProcessVer1_Process2-End++++++++++++++++++

8 Analysis

This tool uses “bool_flag_style” approach/style to mark Optional Xml-Element presence in generated C# code. It uses in addition nullable value to indicate a special case of “nill”.

  • Data1_Int_NO_R - is int type and always has value
  • Data2_Int_NO_NR - is int? type and the meaning is:
    a) null – present but “nill” (we have null here even if the element was present but the value was “nill”)
    b) int – present and had value
  • Xml-Element Data3_Int_O is presented by 2 C# variables:
    Data3_Int_O_RSpecified– is a bool type and
    Data3_Int_O_R – is int type and the meaning is
    a) Data3_Int_O_RSpecified – flag to indicate if the element was present or not
    b) Data3_Int_O_R – if the above flag is true, then this is the element int value
  • Xml-Element Data4_Int_O_NR is presented by 2 C# variables:
    Data4_Int_O_NRSpecified – is a bool type and
    Data4_Int_O_NR – is int? type and the meaning is
    a) Data4_Int_O_NRSpecified – flag to indicate if the element was present or not
    b) Data4_Int_O_NR – if the above flag is true, then this is the element value. If the value is null, that means that the element was “nill”, or if it is not null, that is the element int value. It can be confusing here that “null” means the value was “nill”, but that is how it is.

It is interesting to notice that this tool is using an old API style/approach “bool_flag_style”. But, it still has the ability in the case of Xml-Element Data4_Int_O_NR to indicate all three states: “not-present”, “present-nill”, “present-int”. Just look carefully, and you will see it can. Only API user needs to be careful about what and how is he or she retrieving from the data variables available. In the case above value (Data4_Int_O_NRSpecified, Data4_Int_O_NR)=(true, null) means “present-nill”.

9 Conclusion

Despite using old-style bool_flag_style API, the tool generates usable C# code for .NET8 environment, only the programmer needs to use the generated C# class skillfully. Code generated worked well in my tests.

The full example code project can be downloaded at GitHub [99].

10 References

[1] XML Schema
https://www.w3schools.com/xml/xml_schema.asp

[2] The Difference Between Optional and Not Required
https://www.infopathdev.com/blogs/greg/archive/2004/09/16/The-Difference-Between-Optional-and-Not-Required.aspx

[3] nillable and minOccurs XSD element attributes
https://stackoverflow.com/questions/1903062/nillable-and-minoccurs-xsd-element-attributes

[99] https://github.com/MarkPelf/XsdToolsInNet8

0
Subscribe to my newsletter

Read articles from Mark Pelf directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mark Pelf
Mark Pelf

A Software Engineer from Belgrade, Serbia.