XSD Tools in .NET8 – Part3 – XsdExe- Simple

Mark Pelf - Nov 8 - - Dev Community

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).

1 Doing XML and XSD related work in .NET8

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

3 Examples of XML and XSD

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

3.1 Simple case

Please note that this example XML/XSD has an Optional 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>
Enter fullscreen mode Exit fullscreen mode
<?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>
Enter fullscreen mode Exit fullscreen mode

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
======================================


Enter fullscreen mode Exit fullscreen mode

5 Generated C# class

Here is the C# generated by the above tool based on the above presented XSD SmallCompany.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/SmallCompany.xsd")]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="https://markpelf.com/SmallCompany.xsd", IsNullable=false)]
    public partial class SmallCompany {

        private string companyNameField;

        private SmallCompanyEmployee[] employeeField;

        private SmallCompanyInfoData[] infoDataField;

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

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

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("InfoData")]
        public SmallCompanyInfoData[] 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/SmallCompany.xsd")]
    public partial class SmallCompanyEmployee {

        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/SmallCompany.xsd")]
    public partial class SmallCompanyInfoData {

        private int id_Int_NOField;

        private int quantity_Int_OField;

        private bool quantity_Int_OFieldSpecified;

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

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

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

Enter fullscreen mode Exit fullscreen mode

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 ElemA_value”. You need to check if element ElemA was present by first checking the flag ElemA_flag; and then if it is true, you go for the value of ElemA_value. If you do not check flag ElemA_flag first, and just go for the value of ElemA_value 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? ElemA_nullableValue”. You need to check if element ElemA was present by first checking the ElemA_nullableValue not being null; and then if it is not meaning the element was present, you go for the int value of ElemA_nullableValue.

7 Sample C# app

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

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

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

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

            foreach(Example1.SmallCompanyEmployee 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.SmallCompanyInfoData item in xmlObject.InfoData)
            {
                logger?.LogInformation("------------");
                logger?.LogInformation("Id_Int_NO:" + item.Id_Int_NO.ToString());
                logger?.LogInformation("Quantity_Int_OSpecified:" + item.Quantity_Int_OSpecified.ToString());
                logger?.LogInformation("Quantity_Int_O:" + item.Quantity_Int_O.ToString());
            }
        }
        else
        {
            logger?.LogError("xmlObject == null");
        }

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

Enter fullscreen mode Exit fullscreen mode

And here is the log of execution.

 +++ProcessVer1_Process1-Start++++++++++++++++++
 filePath:C:\TmpXSD\XsdExample_Ver1\Example01\bin\Debug\net8.0\XmlFiles\SmallCompanyAAA.xml
 CompanyName:SmallCompanyAAA
 ------------
 Name_String_NO:Mark
 City_String_O:Belgrade
 ------------
 Name_String_NO:John
 City_String_O:
 ------------
 Id_Int_NO:11
 Quantity_Int_OSpecified:True
 Quantity_Int_O:123
 ------------
 Id_Int_NO:22
 Quantity_Int_OSpecified:False
 Quantity_Int_O:0
 +++ProcessVer1_Process1-End++++++++++++++++++
Enter fullscreen mode Exit fullscreen mode

8 Analysis

This tool uses “bool_flag_style” approach/style to mark Optional Xml-Element presence in generated C# code.

  • Xml-Element Quantity_Int_O is presented by 2 C# variables: Quantity_Int_OSpecified – is a bool type and Quantity_Int_O – is int type and the meaning is
    • a) Quantity_Int_OSpecified– flag to indicate if the element was present or not
    • b) Quantity_Int_O– if the above flag is true, then this is the element int value

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.

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .