Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

XSD Tools in .NET8 – Part6 – XmlSchemaClassGenerator - Advanced

5.00/5 (2 votes)
24 Sep 2024CPOL7 min read 2.4K   34  
A practical guide to XSD tools available in .NET8 environment.
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

 

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
<?xml version="1.0" encoding="utf-8"?>
<!--BigCompany.xsd+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           attributeFormDefault="unqualified" 
           elementFormDefault="qualified" 
           targetNamespace="https://markpelf.com/BigCompany.xsd" 
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="BigCompany">
        <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>
                            <!--Data1_Int_NO_R is Int NotOptional+Required-->
                            <xs:element name="Data1_Int_NO_R" type="xs:int" minOccurs="1" nillable="false" />
                            <!--Data2_Int_NO_NR is Int NotOptional+NotRequired-->
                            <xs:element name="Data2_Int_NO_NR" type="xs:int" minOccurs="1" nillable="true" />
                            <!--Data3_Int_O_R is Int Optional+Required-->
                            <xs:element name="Data3_Int_O_R" type="xs:int" minOccurs="0" nillable="false" />
                            <!--Data4_Int_O_NR is Int Optional+NotRequired-->
                            <xs:element name="Data4_Int_O_NR" type="xs:int" minOccurs="0" nillable="true" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

 

XML
<?xml version="1.0" encoding="utf-8"?>
<!--BigCompanyMMM.xml+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<BigCompany xmlns="https://markpelf.com/BigCompany.xsd">
    <CompanyName>BigCompanyMMM</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>
        <Data1_Int_NO_R>555</Data1_Int_NO_R>
        <Data2_Int_NO_NR>16</Data2_Int_NO_NR>
        <Data3_Int_O_R>333</Data3_Int_O_R>
        <Data4_Int_O_NR>17</Data4_Int_O_NR>
    </InfoData>
    <InfoData>
        <Data1_Int_NO_R>123</Data1_Int_NO_R>
        <Data2_Int_NO_NR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
        <Data3_Int_O_R>15</Data3_Int_O_R>
        <Data4_Int_O_NR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
    </InfoData>
    <InfoData>
        <Data1_Int_NO_R>777</Data1_Int_NO_R>
        <Data2_Int_NO_NR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
    </InfoData>
</BigCompany>

 

4 Using XmlSchemaClassGenerator tool to create C# class

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

Tool name============================
XmlSchemaClassGenerator 

License============================ 
Open Source/Freeware

Where to get it============================
https://github.com/mganss/XmlSchemaClassGenerator?tab=readme-ov-file

Version============================
Windows PowerShell > ./XmlSchemaClassGenerator.Console.exe
Usage: xscgen [OPTIONS]+ xsdFile...
Generate C# classes from XML Schema files.
Version 2.1.1162.0

Help============================
Windows PowerShell > ./XmlSchemaClassGenerator.Console.exe
Usage: xscgen [OPTIONS]+ xsdFile...
Generate C# classes from XML Schema files.
Version 2.1.1162.0
xsdFiles may contain globs, e.g. "content\{schema,xsd}\**\*.xsd", and URLs.
Append - to option to disable it, e.g. --interface-.

Options:
  -h, --help                 show this message and exit
  -n, --namespace=VALUE      map an XML namespace to a C# namespace
                               Separate XML namespace and C# namespace by '='.
                               A single value (no '=') is taken as the C#
                               namespace the empty XML namespace is mapped to.
                               One option must be given for each namespace to
                               be mapped.
                               A file name may be given by appending a pipe
                               sign (|) followed by a file name (like schema.
                               xsd) to the XML namespace.
                               If no mapping is found for an XML namespace, a
                               name is generated automatically (may fail).
      --nf, --namespaceFile=VALUE
                             file containing mappings from XML namespaces to C#
                               namespaces
                               The line format is one mapping per line: XML
                               namespace = C# namespace [optional file name].
                               Lines starting with # and empty lines are
                               ignored.
      --tns, --typeNameSubstitute=VALUE
                             substitute a generated type/member name
                               Separate type/member name and substitute name by
                               '='.
                               Prefix type/member name with an appropriate kind
                               ID as documented at: https://t.ly/HHEI.
                               Prefix with 'A:' to substitute any type/member.
      --tnsf, --typeNameSubstituteFile=VALUE
                             file containing generated type/member name
                               substitute mappings
                               The line format is one mapping per line:
                               prefixed type/member name = substitute name.
                               Lines starting with # and empty lines are
                               ignored.
  -o, --output=FOLDER        the FOLDER to write the resulting .cs files to
  -d, --datetime-offset      map xs:datetime and derived types to System.
                               DateTimeOffset instead of System.DateTime
  -i, --integer=TYPE         map xs:integer and derived types to TYPE instead
                               of automatic approximation
                               TYPE can be i[nt], l[ong], or d[ecimal]
      --fb, --fallback, --use-integer-type-as-fallback
                             use integer type specified via -i only if no type
                               can be deduced
  -e, --edb, --enable-data-binding
                             enable INotifyPropertyChanged data binding
  -r, --order                emit order for all class members stored as XML
                               element
  -c, --pcl                  PCL compatible output
  -p, --prefix=PREFIX        the PREFIX to prepend to auto-generated namespace
                               names
  -v, --verbose              print generated file names on stdout
  -0, --nullable             generate nullable adapter properties for optional
                               elements/attributes w/o default values
  -f, --ef                   generate Entity Framework Code First compatible
                               classes
  -t, --interface            generate interfaces for groups and attribute
                               groups (default is enabled)
  -a, --pascal               use Pascal case for class and property names (
                               default is enabled)
      --av, --assemblyVisible
                             use the internal visibility modifier (default is
                               false)
  -u, --enableUpaCheck       should XmlSchemaSet check for Unique Particle
                               Attribution (UPA) (default is enabled)
      --ct, --collectionType=VALUE
                             collection type to use (default is System.
                               Collections.ObjectModel.Collection`1)
      --cit, --collectionImplementationType=VALUE
                             the default collection type implementation to use (
                               default is null)
      --csm, --collectionSettersMode=Private, Public, PublicWithoutConstructorInitialization, Init, InitWithoutConstructorInitialization
                             generate a private, public, or init-only setter
                               with or without backing field initialization for
                               collections
                               (default is Private; can be: Private, Public,
                               PublicWithoutConstructorInitialization, Init,
                               InitWithoutConstructorInitialization)
      --ctro, --codeTypeReferenceOptions=GlobalReference, GenericTypeParameter
                             the default CodeTypeReferenceOptions Flags to use (
                               default is unset; can be: GlobalReference,
                               GenericTypeParameter)
      --tvpn, --textValuePropertyName=VALUE
                             the name of the property that holds the text value
                               of an element (default is Value)
      --dst, --debuggerStepThrough
                             generate DebuggerStepThroughAttribute (default is
                               enabled)
      --dc, --disableComments
                             do not include comments from xsd
      --nu, --noUnderscore   do not generate underscore in private member name (
                               default is false)
      --da, --description    generate DescriptionAttribute (default is true)
      --cc, --complexTypesForCollections
                             generate complex types for collections (default is
                               true)
  -s, --useShouldSerialize   use ShouldSerialize pattern instead of Specified
                               pattern (default is false)
      --sf, --separateFiles  generate a separate file for each class (default
                               is false)
      --nh, --namespaceHierarchy
                             generate a separate folder for namespace hierarchy.
                                Implies "separateFiles" if true (default is
                               false)
      --sg, --separateSubstitutes
                             generate a separate property for each element of a
                               substitution group (default is false)
      --dnfin, --doNotForceIsNullable
                             do not force generator to emit IsNullable = true
                               in XmlElement annotation for nillable elements
                               when element is nullable (minOccurs < 1 or
                               parent element is choice) (default is false)
      --cn, --compactTypeNames
                             use type names without namespace qualifier for
                               types in the using list (default is false)
      --cl, --commentLanguages=VALUE
                             comment languages to use (default is en; supported
                               are en, de)
      --un, --uniqueTypeNames
                             generate type names that are unique across
                               namespaces (default is false)
      --gc, --generatedCodeAttribute
                             add version information to GeneratedCodeAttribute (
                               default is true)
      --nc, --netCore        generate .NET Core specific code that might not
                               work with .NET Framework (default is false)
      --nr, --nullableReferenceAttributes
                             generate attributes for nullable reference types (
                               default is false)
      --ar, --useArrayItemAttribute
                             use ArrayItemAttribute for sequences with single
                               elements (default is true)
      --es, --enumAsString   Use string instead of enum for enumeration
      --dmb, --disableMergeRestrictionsWithBase
                             Disable merging of simple type restrictions with
                               base type restrictions
      --ca, --commandArgs    generate a comment with the exact command line
                               arguments that were used to generate the source
                               code (default is true)
      --uc, --unionCommonType
                             generate a common type for unions if possible (
                               default is false)
      --ec, --serializeEmptyCollections
                             serialize empty collections (default is false)
      --dtd, --allowDtdParse allows dtd parse (default is false)
      --ns, --namingScheme=VALUE
                             use the specified naming scheme for class and
                               property names (default is Pascal; can be:
                               Direct, Pascal, Legacy)

=================================================    
Usage Examples===================                          
Instructions to generate C# class 
Windows PowerShell> ./XmlSchemaClassGenerator.Console.exe --namespace=Example2SmallCompany --nullable --netCore --nullableReferenceAttributes --namingScheme=Direct SmallCompany.xsd
Windows PowerShell>  ./XmlSchemaClassGenerator.Console.exe --namespace=Example2BigCompany --nullable --netCore --nullableReferenceAttributes --namingScheme=Direct BigCompany.xsd

============================

 

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.

C#
//BigCompany_ver2_2.cs
//------------------------------------------------------------------------------
// <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 code was generated by XmlSchemaClassGenerator version 2.1.1162.0 using the following command:
// XmlSchemaClassGenerator.Console --namespace=Example2BigCompany --nullable --netCore --nullableReferenceAttributes --namingScheme=Direct BigCompany.xsd
namespace Example2BigCompany
{
    
    
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XmlSchemaClassGenerator", "2.1.1162.0")]
    [System.SerializableAttribute()]
    [System.Xml.Serialization.XmlTypeAttribute("BigCompany", Namespace="https://markpelf.com/BigCompany.xsd", AnonymousType=true)]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlRootAttribute("BigCompany", Namespace="https://markpelf.com/BigCompany.xsd")]
    public partial class BigCompany
    {
        
        [System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
        [System.Xml.Serialization.XmlElementAttribute("CompanyName")]
        public string CompanyName { get; set; }
        
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        private System.Collections.ObjectModel.Collection<BigCompanyEmployee> _employee;
        
        [System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
        [System.Xml.Serialization.XmlElementAttribute("Employee")]
        public System.Collections.ObjectModel.Collection<BigCompanyEmployee> Employee
        {
            get
            {
                return _employee;
            }
            private set
            {
                _employee = value;
            }
        }
        
        /// <summary>
        /// <para xml:lang="en">Initializes a new instance of the <see cref="BigCompany" /> class.</para>
        /// </summary>
        public BigCompany()
        {
            this._employee = new System.Collections.ObjectModel.Collection<BigCompanyEmployee>();
            this._infoData = new System.Collections.ObjectModel.Collection<BigCompanyInfoData>();
        }
        
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        private System.Collections.ObjectModel.Collection<BigCompanyInfoData> _infoData;
        
        [System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
        [System.Xml.Serialization.XmlElementAttribute("InfoData")]
        public System.Collections.ObjectModel.Collection<BigCompanyInfoData> InfoData
        {
            get
            {
                return _infoData;
            }
            private set
            {
                _infoData = value;
            }
        }
    }
    
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XmlSchemaClassGenerator", "2.1.1162.0")]
    [System.SerializableAttribute()]
    [System.Xml.Serialization.XmlTypeAttribute("BigCompanyEmployee", Namespace="https://markpelf.com/BigCompany.xsd", AnonymousType=true)]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    public partial class BigCompanyEmployee
    {
        
        [System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
        [System.Xml.Serialization.XmlElementAttribute("Name_String_NO")]
        public string Name_String_NO { get; set; }
        
        [System.Diagnostics.CodeAnalysis.AllowNullAttribute()]
        [System.Diagnostics.CodeAnalysis.MaybeNullAttribute()]
        [System.Xml.Serialization.XmlElementAttribute("City_String_O")]
        public string City_String_O { get; set; }
    }
    
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XmlSchemaClassGenerator", "2.1.1162.0")]
    [System.SerializableAttribute()]
    [System.Xml.Serialization.XmlTypeAttribute("BigCompanyInfoData", Namespace="https://markpelf.com/BigCompany.xsd", AnonymousType=true)]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    public partial class BigCompanyInfoData
    {
        
        [System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
        [System.Xml.Serialization.XmlElementAttribute("Data1_Int_NO_R")]
        public int Data1_Int_NO_R { get; set; }
        
        [System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
        [System.Xml.Serialization.XmlElementAttribute("Data2_Int_NO_NR", IsNullable=true)]
        public System.Nullable<int> Data2_Int_NO_NR { get; set; }
        
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
        [System.Xml.Serialization.XmlElementAttribute("Data3_Int_O_R")]
        public int Data3_Int_O_RValue { get; set; }
        
        /// <summary>
        /// <para xml:lang="en">Gets or sets a value indicating whether the Data3_Int_O_R property is specified.</para>
        /// </summary>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
        public bool Data3_Int_O_RValueSpecified { get; set; }
        
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public System.Nullable<int> Data3_Int_O_R
        {
            get
            {
                if (this.Data3_Int_O_RValueSpecified)
                {
                    return this.Data3_Int_O_RValue;
                }
                else
                {
                    return null;
                }
            }
            set
            {
                this.Data3_Int_O_RValue = value.GetValueOrDefault();
                this.Data3_Int_O_RValueSpecified = value.HasValue;
            }
        }
        
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
        [System.Xml.Serialization.XmlElementAttribute("Data4_Int_O_NR", IsNullable=true)]
        public System.Nullable<int> Data4_Int_O_NRValue { get; set; }
        
        /// <summary>
        /// <para xml:lang="en">Gets or sets a value indicating whether the Data4_Int_O_NR property is specified.</para>
        /// </summary>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
        public bool Data4_Int_O_NRValueSpecified { get; set; }
        
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public System.Nullable<int> Data4_Int_O_NR
        {
            get
            {
                if (this.Data4_Int_O_NRValueSpecified)
                {
                    return this.Data4_Int_O_NRValue;
                }
                else
                {
                    return null;
                }
            }
            set
            {
                this.Data4_Int_O_NRValue = value.GetValueOrDefault();
                this.Data4_Int_O_NRValueSpecified = value.HasValue;
            }
        }
    }
}

 

Here is the class diagram.

Image 1

 

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 BigCompanyMMM.xml.

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

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

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

            foreach (Example2BigCompany.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 ?? "null"));
            }

            foreach (Example2BigCompany.BigCompanyInfoData item in xmlObject.InfoData)
            {
                logger?.LogInformation("------------");
                logger?.LogInformation("Data1_Int_NO_R:" + item.Data1_Int_NO_R.ToString());
                logger?.LogInformation("Data2_Int_NO_NR:" + (item.Data2_Int_NO_NR?.ToString() ?? "null"));

                logger?.LogInformation("Data3_Int_O_RValue:" + item.Data3_Int_O_RValue.ToString());
                logger?.LogInformation("Data3_Int_O_RValueSpecified:" + item.Data3_Int_O_RValueSpecified.ToString());
                logger?.LogInformation("Data3_Int_O_R:" + (item.Data3_Int_O_R?.ToString() ?? "null"));

                logger?.LogInformation("Data4_Int_O_NRValue:" + (item.Data4_Int_O_NRValue?.ToString() ?? "null"));
                logger?.LogInformation("Data4_Int_O_NRValueSpecified:" + item.Data4_Int_O_NRValueSpecified.ToString());
                logger?.LogInformation("Data4_Int_O_NR:" + (item.Data4_Int_O_NR?.ToString() ?? "null"));
            }
        }
        else
        {
            logger?.LogError("xmlObject == null");
        }

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

 

And here is the log of execution.

 +++ProcessVer2_Process2-Start++++++++++++++++++
 filePath:C:\TmpXSD\XsdExample_Ver2\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:null
 ------------
 Data1_Int_NO_R:555
 Data2_Int_NO_NR:16
 Data3_Int_O_RValue:333
 Data3_Int_O_RValueSpecified:True
 Data3_Int_O_R:333
 Data4_Int_O_NRValue:17
 Data4_Int_O_NRValueSpecified:True
 Data4_Int_O_NR:17
 ------------
 Data1_Int_NO_R:123
 Data2_Int_NO_NR:null
 Data3_Int_O_RValue:15
 Data3_Int_O_RValueSpecified:True
 Data3_Int_O_R:15
 Data4_Int_O_NRValue:null
 Data4_Int_O_NRValueSpecified:True
 Data4_Int_O_NR:null
 ------------
 Data1_Int_NO_R:777
 Data2_Int_NO_NR:null
 Data3_Int_O_RValue:0
 Data3_Int_O_RValueSpecified:False
 Data3_Int_O_R:null
 Data4_Int_O_NRValue:null
 Data4_Int_O_NRValueSpecified:False
 Data4_Int_O_NR:null
 +++ProcessVer2_Process2-End++++++++++++++++++

 

8 Analysis

It is not easy to understand what is happening here, but it looks like this tool is trying to use both “bool_flag_style” and “nullable_type_style” approaches/styles to mark Optional Xml-Element presence in generated C# code. The user can choose which API approach/style he or she likes. If using API style/aproach “bool_flag_style” in addition it uses a nullable method to indicate “nill” value. If using API style/aproach “nullable_type_style” it has no way to indicate “nill” value.

  • Data1_Int_NO_R - is int type and always has value
  • Data2_Int_NO_NR - is int? type and the meaning is:
    1) null – present but “nill” (we have null here even if the element was present but the value was “nill”)
    2) int – present and had value
  • Xml-Element Data3_Int_O_R is presented by 3 C# variables:
    Data3_Int_O_RValueSpecified – is a bool type (“bool_flag_style” API)
    Data3_Int_O_RValue – is int type (“bool_flag_style” API)
    Data3_Int_O_R – is int? type (“nullable_type_style” API)
    • Using “bool_flag_style” API:
      1) Data3_Int_O_RValueSpecified – flag to indicate if the element was present or not
      2) Data3_Int_O_RValue – if the above flag is true, then this is the element int value
    • Using “nullable_type_style” API:
      3) Data3_Int_O_R - – is int? type and the meaning is
      a) null - means it was not present
      b) int – present and had value
  • Xml-Element Data4_Int_O_NR is presented by 3 C# variables:
    Data4_Int_O_NRValueSpecified – is a bool type (“bool_flag_style” API)
    Data4_Int_O_NRValue – is int? type (“bool_flag_style” API)
    Data4_Int_O_NR – is int? type (“nullable_type_style” API)
    • Using “bool_flag_style” API:
      1) Data4_Int_O_NRValueSpecified – flag to indicate if the element was present or not
      2) Data4_Int_O_NRValue – 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.
    • Using “nullable_type_style” API:
      3) Data4_Int_O_NR - is int? type and the meaning is:
      a) null – means it either was not present or present but “nill”. We can not know which of these 2 cases happened. Strictly speaking, that is a deficiency of this approach/style for code generated. In some cases, such a distinction might be needed.
      b) int – present and had value

 

It is interesting to look at this tool when using an old API style/approach “bool_flag_style”. It 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. In the case above value (Data4_Int_O_NRValueSpecified, Data4_Int_O_NRValue)=(true, null) means “present-nill”.

I do not see that using the modern API style/approach “nullable_type_style” the same can be achieved. If Data4_Int_O_NR is null, we can not know if it was “not-present” or “present-nill”.

 

9 Conclusion

This tool XmlSchemaClassGenerator is very interesting and available as a freeware. Code generated worked solid in my test. It can be of great interest to users that want to use nullable_type_style API, which is generally more modern approach to handling the Optional Xml-Elements.

 

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

[21] XSD Tools in .NET8 – Part1 – VS2022
https://www.codeproject.com/Articles/5388391/XSD-Tools-in-NET8-Part1-VS2022

[22] XSD Tools in .NET8 – Part2 – C# validation
https://www.codeproject.com/Articles/5388393/XSD-Tools-in-NET8-Part2-Csharp-validation

[23] XSD Tools in .NET8 – Part3 – XsdExe – Simple
https://www.codeproject.com/Articles/5388396/XSD-Tools-in-NET8-Part3-XsdExe-Simple

[24] XSD Tools in .NET8 – Part4 – XsdExe - Advanced
https://www.codeproject.com/Articles/5388483/XSD-Tools-in-NET8-Part4-XsdExe-Advanced

[25] XSD Tools in .NET8 – Part5 – XmlSchemaClassGenerator – Simple
https://www.codeproject.com/Articles/5388548/XSD-Tools-in-NET8-Part5-XmlSchemaClassGenerator-Si

[26] XSD Tools in .NET8 – Part6 – XmlSchemaClassGenerator – Advanced
https://www.codeproject.com/Articles/5388549/XSD-Tools-in-NET8-Part6-XmlSchemaClassGenerator-Ad

[27] XSD Tools in .NET8 – Part7 – LinqToXsdCore – Simple
https://www.codeproject.com/Articles/5388628/XSD-Tools-in-NET8-Part7-LinqToXsdCore-Simple

[28] XSD Tools in .NET8 – Part8 – LinqToXsdCore – Advanced
https://www.codeproject.com/Articles/5388629/XSD-Tools-in-NET8-Part8-LinqToXsdCore-Advanced

[29] XSD Tools in .NET8 – Part9 – LiquidXMLObjects – Simple
https://www.codeproject.com/Articles/5388683/XSD-Tools-in-NET8-Part9-LiquidXMLObjects-Simple

[30] XSD Tools in .NET8 – Part10 – LiquidXMLObjects – Advanced
https://www.codeproject.com/Articles/5388684/XSD-Tools-in-NET8-Part10-LiquidXMLObjects-Advanced

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)