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
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 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.
//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.
6 Two C# API styles for Optional Xml-Elements
There are two approaches/styles for marking Optional Xml-Element presence in generated C# code:
- 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).
- 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.
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.
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