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.
="1.0"="utf-8"
<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>
<xs:element name="Name_String_NO" type="xs:string" />
<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>
<xs:element name="Data1_Int_NO_R" type="xs:int" minOccurs="1" nillable="false" />
<xs:element name="Data2_Int_NO_NR" type="xs:int" minOccurs="1" nillable="true" />
<xs:element name="Data3_Int_O_R" type="xs:int" minOccurs="0" nillable="false" />
<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>
="1.0"="utf-8"
<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 LiquidXMLObjects tool to create C# class
We focus in this article on the usage of LiquidXMLObjects tool to generate C# class from XSD file.
Here is the tool's basic info.
Tool name: ============================
Liquid XML Objects
License============================
2 licenses:
1) Free Community Edition: Home Users and Students, XML Schema (size limited)
2) Commercial Product, Developer Bundle (Installed) $770.94 (perpetual license)
Where to get it============================
https://www.liquid-technologies.com/xml-objects
Install Instructions====================================
-a free trial which will expire 15 days after activation, after this time the product will continue to operate as the Free Community Edition.
Version============================
Liquid Studio - Community Edition 20.7.14.13112
Help============================
https://www.liquid-technologies.com/Reference/XmlDataBinding/xml-objects-introduction.html
====================================================
Usage Examples===================
Instructions to generate C# class
Using GUI - context right click on .xsd file
====================================
4.1 GUI
This commercial product has an impressive GUI to issue commands. Here is how C# classes are generated.
You use the right-click menu to invoke C# class generation
And here are the options I set for C# code generation.
5 Generated C# class
Here is the C# generated by the above tool based on the above presented XSD BigCompany.xsd.
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Numerics;
using LiquidTechnologies.XmlObjects;
using LiquidTechnologies.XmlObjects.Attribution;
namespace XsdExample_Ver4.XSD2
{
#region Global Settings
[LxRuntimeRequirements("20.7.14.13112", "Free Community Edition", "M9WDQLWDQNEEVNLX", LiquidTechnologies.XmlObjects.LicenseTermsType.CommunityEdition)]
public partial class LxRuntimeRequirementsWritten
{
}
#endregion
}
namespace XsdExample_Ver4.XSD2.Xs
{
#region Complex Types
[LxSimpleComplexTypeDefinition("anyType", "http://www.w3.org/2001/XMLSchema")]
public partial class AnyTypeCt : XElement
{
public AnyTypeCt() : base(XName.Get("anyType", "http://www.w3.org/2001/XMLSchema")) { }
}
#endregion
}
namespace XsdExample_Ver4.XSD2.Tns
{
#region Elements
[LxSimpleElementDefinition("BigCompany", "https://markpelf.com/BigCompany.xsd", ElementScopeType.GlobalElement)]
public partial class BigCompanyElm
{
[LxElementValue(0, "CompanyName", "https://markpelf.com/BigCompany.xsd", LxValueType.Value, XsdType.XsdString, MinOccurs = 1, MaxOccurs = 1)]
public System.String CompanyName { get; set; } = "";
[LxElementRef(1, MinOccurs = 0, MaxOccurs = LxConstants.Unbounded)]
public List<XsdExample_Ver4.XSD2.Tns.BigCompanyElm.EmployeeElm> Employees { get; } = new List<XsdExample_Ver4.XSD2.Tns.BigCompanyElm.EmployeeElm>();
[LxElementRef(2, MinOccurs = 0, MaxOccurs = LxConstants.Unbounded)]
public List<XsdExample_Ver4.XSD2.Tns.BigCompanyElm.InfoDataElm> InfoData { get; } = new List<XsdExample_Ver4.XSD2.Tns.BigCompanyElm.InfoDataElm>();
[LxSimpleElementDefinition("Employee", "https://markpelf.com/BigCompany.xsd", ElementScopeType.InlineElement)]
public partial class EmployeeElm
{
[LxElementValue(0, "Name_String_NO", "https://markpelf.com/BigCompany.xsd", LxValueType.Value, XsdType.XsdString, MinOccurs = 1, MaxOccurs = 1)]
public System.String Name_String_NO { get; set; } = "";
[LxElementValue(1, "City_String_O", "https://markpelf.com/BigCompany.xsd", LxValueType.Value, XsdType.XsdString, MinOccurs = 0, MaxOccurs = 1)]
public System.String? City_String_O { get; set; }
}
[LxSimpleElementDefinition("InfoData", "https://markpelf.com/BigCompany.xsd", ElementScopeType.InlineElement)]
public partial class InfoDataElm
{
[LxElementValue(0, "Data1_Int_NO_R", "https://markpelf.com/BigCompany.xsd", LxValueType.Value, XsdType.XsdInt, MinOccurs = 1, MaxOccurs = 1)]
public System.Int32 Data1_Int_NO_R { get; set; }
[LxElementValue(1, "Data2_Int_NO_NR", "https://markpelf.com/BigCompany.xsd", LxValueType.Value, XsdType.XsdInt, MinOccurs = 1, MaxOccurs = 1)]
public LxNillable<System.Int32> Data2_Int_NO_NR { get; set; } = LxNillable<System.Int32>.Create(default(System.Int32), false);
[LxElementValue(2, "Data3_Int_O_R", "https://markpelf.com/BigCompany.xsd", LxValueType.Value, XsdType.XsdInt, MinOccurs = 0, MaxOccurs = 1)]
public System.Int32? Data3_Int_O_R { get; set; }
[LxElementValue(3, "Data4_Int_O_NR", "https://markpelf.com/BigCompany.xsd", LxValueType.Value, XsdType.XsdInt, MinOccurs = 0, MaxOccurs = 1)]
public LxNillable<System.Int32> Data4_Int_O_NR { get; set; }
}
}
#endregion
}
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 ProcessVer4_Process2(
string? filePath,
Microsoft.Extensions.Logging.ILogger? logger)
{
try
{
logger?.LogInformation(
"+++ProcessVer4_Process2-Start++++++++++++++++++");
logger?.LogInformation("filePath:" + filePath);
LxSerializer<XsdExample_Ver4.XSD2.Tns.BigCompanyElm> serializer =
new LxSerializer<XsdExample_Ver4.XSD2.Tns.BigCompanyElm>();
TextReader textReader = File.OpenText(filePath ?? String.Empty);
LxReaderSettings readerSettings = new LxReaderSettings();
XsdExample_Ver4.XSD2.Tns.BigCompanyElm? xmlObject =
serializer.Deserialize(textReader, readerSettings);
if (xmlObject != null)
{
logger?.LogInformation("CompanyName:" + xmlObject.CompanyName);
foreach (XsdExample_Ver4.XSD2.Tns.BigCompanyElm.EmployeeElm item in xmlObject.Employees)
{
logger?.LogInformation("------------");
logger?.LogInformation("Name_String_NO:" + item.Name_String_NO);
logger?.LogInformation("City_String_O:" + (item.City_String_O ?? "null"));
}
foreach (XsdExample_Ver4.XSD2.Tns.BigCompanyElm.InfoDataElm 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.IsNil ? "nill" : item.Data2_Int_NO_NR.Value));
logger?.LogInformation("Data3_Int_O_R:" + (item.Data3_Int_O_R?.ToString() ?? "null"));
logger?.LogInformation("Data4_Int_O_NR:" +
(item.Data4_Int_O_NR==null ? "null" :
(item.Data4_Int_O_NR.IsNil ? "nil" : item.Data4_Int_O_NR.Value)));
}
}
else
{
logger?.LogError("xmlObject == null");
}
logger?.LogInformation(
"+++ProcessVer4_Process2-End++++++++++++++++++");
}
catch (Exception ex)
{
string methodName =
$"Type: {System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.FullName}, " +
$"Method: ProcessVer4_Process2; ";
logger?.LogError(ex, methodName);
}
}
And here is the log of execution.
+++ProcessVer4_Process2-Start++++++++++++++++++
filePath:C:\TmpXSD\XsdExample_Ver4\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_R:333
Data4_Int_O_NR:17
------------
Data1_Int_NO_R:123
Data2_Int_NO_NR:nill
Data3_Int_O_R:15
Data4_Int_O_NR:nil
------------
Data1_Int_NO_R:777
Data2_Int_NO_NR:nill
Data3_Int_O_R:null
Data4_Int_O_NR:null
+++ProcessVer4_Process2-End++++++++++++++++++
8 Analysis
This tool uses “nullable_type_style” approach/style to mark Optional Xml-Element presence in generated C# code. To enable the indication of “nill” it uses a special object class as a result.
- Data1_Int_NO_R - is int type and always has value
- Data2_Int_NO_NR - is an object of a certain class and can have 2 values:, 1) “nill”, 2)int.
a) “nill”– present but “nill”
b) int – present and had value - Data3_Int_O_R – is int? type and the meaning is
1) null - means it was not present
2) int – present and had value - Data4_Int_O_NR - is an object of a certain class and can have 3 values: 1)null, 2) “nill”, 3)int.
1) null – means the element was not present
2) “nill”– element was present but “nill”
3) int – element was present and had value
It is interesting to notice that this tool is using the modern API style/approach “nullable_type_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”. The way they achieve it is by using a special class/object to represent ternary value.
9 Conclusion
This tool LiquidXMLObjects is very interesting but requires a commercial license. The code generated looked solid in my test. It can be of great interest to users who want to use nullable_type_style API, which is generally a more modern approach for handling the Optional Xml-Elements.
10 References
[1] XML Schema
https://www.w3schools.com/xml/xml_schema.asp
[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