DEV Community

Cover image for XSD Tools in .NET8 – Part8 – LinqToXsdCore - Advanced
Mark Pelf
Mark Pelf

Posted on

XSD Tools in .NET8 – Part8 – LinqToXsdCore - Advanced

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

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

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>
Enter fullscreen mode Exit fullscreen mode
<?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>
Enter fullscreen mode Exit fullscreen mode

Using LinqToXsdCore tool to create C# class

We focus in this article on the usage of LinqToXsdCore 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
====================================


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 BigCompany.xsd.

The class's full code produced by this tool is big (835 lines), so I will here show just the beginning of the file.

///////////////////////////////////////////////////////////////////////////
//           Liquid XML Objects GENERATED CODE - DO NOT MODIFY           //
//            https://www.liquid-technologies.com/xml-objects            //
//=======================================================================//
// Dependencies                                                          //
//     Nuget : LiquidTechnologies.XmlObjects.Runtime                     //
//           : MUST BE VERSION 20.7.14                                   //
//=======================================================================//
// Online Help                                                           //
//     https://www.liquid-technologies.com/xml-objects-quick-start-guide //
//=======================================================================//
// Licensing Information                                                 //
//     https://www.liquid-technologies.com/eula                          //
///////////////////////////////////////////////////////////////////////////
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;

// ------------------------------------------------------
// |                      Settings                      |
// ------------------------------------------------------
// GenerateCommonBaseClass                  = False
// GenerateUnprocessedNodeHandlers          = False
// RaiseChangeEvents                        = False
// CollectionNaming                         = Pluralize
// Language                                 = CS
// OutputNamespace                          = XsdExample_Ver4.XSD1
// WriteDefaultValuesForOptionalAttributes  = True
// WriteDefaultValuesForOptionalElements    = True
// MixedContentHandling                     = TreatAsAny
// GenerationModel                          = Simple
//                                            *WARNING* this simplified model that is very easy to work with
//                                            but may cause the XML to be produced without regard for element
//                                            cardinality or order. Where very high compliance with the XML Schema
//                                            standard is required use GenerationModelType.Conformant
// XSD Schema Files
//    C:\TmpXSD\XsdExample_Ver4\Example01\XSD1\SmallCompany.xsd


namespace XsdExample_Ver4.XSD1
{
    #region Global Settings
    /// <summary>Contains library level properties, and ensures the version of the runtime used matches the version used to generate it.</summary>
    [LxRuntimeRequirements("20.7.14.13112", "Free Community Edition", "M9WDQLWDQNEEVNLX", LiquidTechnologies.XmlObjects.LicenseTermsType.CommunityEdition)]
    public partial class LxRuntimeRequirementsWritten
    {
    }

    #endregion

}

namespace XsdExample_Ver4.XSD1.Xs
{
    #region Complex Types
    /// <summary>A class representing the root XSD complexType anyType@http://www.w3.org/2001/XMLSchema</summary>
    [LxSimpleComplexTypeDefinition("anyType", "http://www.w3.org/2001/XMLSchema")]
    public partial class AnyTypeCt : XElement
    {
        /// <summary>Constructor : create a <see cref="AnyTypeCt" /> element &lt;anyType xmlns='http://www.w3.org/2001/XMLSchema'&gt;</summary>
        public AnyTypeCt()  : base(XName.Get("anyType", "http://www.w3.org/2001/XMLSchema")) { }

    }

    #endregion

}

namespace XsdExample_Ver4.XSD1.Tns
{
    #region Elements
    /// <summary>A class representing the root XSD element SmallCompany@https://markpelf.com/SmallCompany.xsd</summary>
    [LxSimpleElementDefinition("SmallCompany", "https://markpelf.com/SmallCompany.xsd", ElementScopeType.GlobalElement)]
    public partial class SmallCompanyElm
    {
        /// <summary>A <see cref="System.String" />, Required : should not be set to null</summary>
        [LxElementValue(0, "CompanyName", "https://markpelf.com/SmallCompany.xsd", LxValueType.Value, XsdType.XsdString, MinOccurs = 1, MaxOccurs = 1)]
        public System.String CompanyName { get; set; } = "";

        /// <summary>A collection of <see cref="XsdExample_Ver4.XSD1.Tns.SmallCompanyElm.EmployeeElm" /></summary>
        [LxElementRef(1, MinOccurs = 0, MaxOccurs = LxConstants.Unbounded)]
        public List<XsdExample_Ver4.XSD1.Tns.SmallCompanyElm.EmployeeElm> Employees { get; } = new List<XsdExample_Ver4.XSD1.Tns.SmallCompanyElm.EmployeeElm>();

        /// <summary>A collection of <see cref="XsdExample_Ver4.XSD1.Tns.SmallCompanyElm.InfoDataElm" /></summary>
        [LxElementRef(2, MinOccurs = 0, MaxOccurs = LxConstants.Unbounded)]
        public List<XsdExample_Ver4.XSD1.Tns.SmallCompanyElm.InfoDataElm> InfoData { get; } = new List<XsdExample_Ver4.XSD1.Tns.SmallCompanyElm.InfoDataElm>();

        /// <summary>Represent the inline xs:element Employee@https://markpelf.com/SmallCompany.xsd.</summary>
        [LxSimpleElementDefinition("Employee", "https://markpelf.com/SmallCompany.xsd", ElementScopeType.InlineElement)]
        public partial class EmployeeElm
        {
            /// <summary>A <see cref="System.String" />, Required : should not be set to null</summary>
            [LxElementValue(0, "Name_String_NO", "https://markpelf.com/SmallCompany.xsd", LxValueType.Value, XsdType.XsdString, MinOccurs = 1, MaxOccurs = 1)]
            public System.String Name_String_NO { get; set; } = "";

            /// <summary>A <see cref="System.String" />, Optional : null when not set</summary>
            [LxElementValue(1, "City_String_O", "https://markpelf.com/SmallCompany.xsd", LxValueType.Value, XsdType.XsdString, MinOccurs = 0, MaxOccurs = 1)]
            public System.String? City_String_O { get; set; }

        }

        /// <summary>Represent the inline xs:element InfoData@https://markpelf.com/SmallCompany.xsd.</summary>
        [LxSimpleElementDefinition("InfoData", "https://markpelf.com/SmallCompany.xsd", ElementScopeType.InlineElement)]
        public partial class InfoDataElm
        {
            /// <summary>A <see cref="System.Int32" />, Required</summary>
            [LxElementValue(0, "Id_Int_NO", "https://markpelf.com/SmallCompany.xsd", LxValueType.Value, XsdType.XsdInt, MinOccurs = 1, MaxOccurs = 1)]
            public System.Int32 Id_Int_NO { get; set; }

            /// <summary>A nullable <see cref="System.Int32" />, Optional : null when not set</summary>
            [LxElementValue(1, "Quantity_Int_O", "https://markpelf.com/SmallCompany.xsd", LxValueType.Value, XsdType.XsdInt, MinOccurs = 0, MaxOccurs = 1)]
            public System.Int32? Quantity_Int_O { get; set; }

        }

    }

    #endregion

}


Enter fullscreen mode Exit fullscreen mode

Here is the class diagram.

The generated class requires very specific dependencies installed in support.

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.

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

        LxSerializer<XsdExample_Ver4.XSD1.Tns.SmallCompanyElm> serializer = 
            new LxSerializer<XsdExample_Ver4.XSD1.Tns.SmallCompanyElm>();
        TextReader textReader = File.OpenText(filePath ?? String.Empty);
        LxReaderSettings readerSettings = new LxReaderSettings();

        XsdExample_Ver4.XSD1.Tns.SmallCompanyElm? xmlObject =
            serializer.Deserialize(textReader, readerSettings);

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

            foreach(XsdExample_Ver4.XSD1.Tns.SmallCompanyElm.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.XSD1.Tns.SmallCompanyElm.InfoDataElm item in xmlObject.InfoData)
            {
                logger?.LogInformation("------------");
                logger?.LogInformation("Id_Int_NO:" + item.Id_Int_NO.ToString());

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

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

Enter fullscreen mode Exit fullscreen mode

And here is the log of execution.

 +++ProcessVer4_Process1-Start++++++++++++++++++
 filePath:C:\TmpXSD\XsdExample_Ver4\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:null
 ------------
 Id_Int_NO:11
 Quantity_Int_O:123
 ------------
 Id_Int_NO:22
 Quantity_Int_O:null
 +++ProcessVer4_Process1-End++++++++++++++++++

Enter fullscreen mode Exit fullscreen mode

8 Analysis

This tool uses “nullable_type_style” approach/style to mark Optional Xml-Element presence in generated C# code. It does not try to indicate a special case of “nill”.

  • Data1_Int_NO_R - is int type and always has value
  • Data2_Int_NO_NR - is int? type and the meaning is: a) null – present but “nill” (we have null here even if the element was present but the value was “nill”) b) int – present and had value
  • Data3_Int_O_R – is int? type and the meaning is a) null - means it was not present b) int – present and had value
  • 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/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 regarding the ability in the case of Xml-Element Data4_Int_O_NR to indicate all three states: “not-present”, “present-nill”, “present-int”. I do not see that using the modern API style/approach “nullable_type_style” the that 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 LinqToXsdCore is very interesting and available as freeware. Code C# class generated was very big (it includes some extra methods like Load, Save, Clone), but worked solid in my test. The issue is only that “nillable” option is not handled well at all. Theoretically, that is a problem and for some users, it can be important, if they want a tool that follows all XML specifications.

It can be of interest to users who want to use nullable_type_style API, which is generally a more modern approach to handling the Optional Xml-Elements.

Some things make me a bit reserved about using this tool, like why the generated class is so big. Even more than that is the question of those very specific dependencies libraries, are they maintained, and reliable, and will they cause any conflicts with other XML libraries? Answers to these questions would require more studying of this tool before deciding whether to use it in production.

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

Top comments (0)