A fájlkezelés részben röviden bemutatásra került az XML szerializáció. Az ott bemutatottak kiválóan alkalmasak abban az esetben, ha a saját programunk ment ebbe a formátumba és nem kell mással kompatibilisnek lennünk. Azonban ha más által gyártott XML dokumentumot kell feldolgoznunk, deszerializálnunk, akkor első körben nem ártana azt validálnunk.
Az XML elÅ‘nye, hogy van hozzá egy validációs nyelv, ami szintén XML alapú. Ez a validációs nyelv az XML Schema. SegÃtségével ellenÅ‘rizni tudjuk, hogy egy dokumentum szintaktikailag megfelel-e az elvárásainknak, illetve annak a tartalma is megfelelÅ‘-e. Például XML Schema segÃtségével ellenÅ‘rizhetjük, hogy egy adott mezÅ‘ értéke 0 és 10 között van-e anélkül, hogy ténylegesen sor kerülne a deszerializációra.
A validáció működését a legegyszerűbb egy példán keresztül bemutatni. Tételezzük fel, hogy adott az alábbi dokumentum, ami könyveket Ãr le.
<?xml version="1.0" encoding="UTF-8"?>
<books
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="books.xsd">
<book title="Book 1">
<author>Author 1</author>
<publicationYear>2020</publicationYear>
</book>
<book title="Book 2">
<author>Author 2</author>
<publicationYear>2019</publicationYear>
</book>
</books>
Ezt kellene validálnunk. Ideális esetben, ha egy XML fájlt kapunk, akkor az XSD (séma) definÃciót is kapjuk mellé, ha nem mi állÃtjuk elÅ‘ a dokumentumot. Ha nem kapnánk sémát, akkor mindenképpen ragaszkodjunk hozzá, hogy kapjunk, mert csak az XML készÃtÅ‘je tudja pontosan, hogy mi lehet a dokumentumban, egy minta alapján pedig csak olyan sémát lehet Ãrni, ami arra az egy dokumentumra illeszkedik.
Sémát Ãrni kézzel nem a legjobb élmény, de szerencsére a Visual Studio rendelkezik séma szerkesztÅ‘vel, ami alapján mi magunk is össze tudjuk állÃtani a sémát a leendÅ‘ dokumentumainkhoz. Csupán egy új XML Schema (XSD) fájlt kell hozzáadnunk a projektünkhöz és a megnyÃló XML Schema Explorer-ben tudunk navigálni és módosÃtani a sémán is.
Ezt a folyamatot lehet automatizálni is, méghozzá az XSD.exe eszközzel, ami a .NET Framework SDK része, de valamilyen oknál fogva a Visual Studio Tools menüjébe alapértelmezetten nincs kivezetve. Az én gépemen Windows 10 és .NET Framework 4.8 esetén az c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\ mappában található a program.
Az xsd.exe books.xml parancs segÃtségével tudunk egy sémát generáltatni, ami illik a bemeneti xml fájlra. Ez több mint valószÃnű nem lesz tökéletes és kézzel 1-2 elem tÃpusát módosÃtanunk kell majd, de már ez is nagy segÃtség.
AlternatÃvaként a https://www.liquid-technologies.com/online-xml-to-xsd-converter oldalon található eszköz is képes sémát generálni.
A generált xsd fájlt némileg átformáztam, illetve az author és publicationYear attribútumoknál a minOccurs és maxOccurs tulajdonság beállÃtásával kikötöttem, hogy egy book elem pontosan egy szerzÅ‘ és pontosan egy kiadás éve tulajdonsággal rendelkezhet. A validációhoz használt séma végül a következÅ‘ lett:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="books">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="book" minOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="author"
type="xs:string"
minOccurs="1"
maxOccurs="1" />
<xs:element name="publicationYear"
type="xs:gYear"
minOccurs="1"
maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="title"
type="xs:string"
use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Az XSD dokumentum alapján az xsd.exe /classes books.xsd parancs kiadásával kigeneráltathatjuk a deszerializációhoz szükséges osztályokat. Mivel az eszköz .NET Framework alapú, ezért az általa generált kód nem tudja igénybe venni a .NET Core és késÅ‘bbi keretrendszerek újdonságait. A generált kódokat érdemes egyébként egy külön assembly-be szervezni, hogy még véletlenül se keveredjenek az általunk Ãrt kóddal. Ennek két oka van. Egyrészt strukturáltabb lesz a kód, másrészt pedig a generálás könnyebben automatizálható shell scriptekkel.
A példa kedvéért a deszerializációs osztályokat kézzel készÃtettem el. Az alábbi kód a validációt és a deszerializációt valósÃtja meg:
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace XMLXsd
{
public class Book
{
[XmlElement("author")]
public string Author { get; set; }
[XmlElement("publicationYear")]
public int PublicationYear { get; set; }
[XmlAttribute("title")]
public string Title { get; set; }
}
public class Books
{
[XmlElement("book")]
public Book[] BookArray { get; set; }
}
public static class Program
{
private static bool TryValidate(string xmlPath, string xsdPath, out string issues)
{
StringBuilder issuesBuilder = new StringBuilder();
bool valid = true;
var schemaSet = new XmlSchemaSet();
schemaSet.Add(null, xsdPath);
var xmlReaderSettings = new XmlReaderSettings
{
ValidationType = ValidationType.Schema,
Schemas = schemaSet
};
xmlReaderSettings.ValidationEventHandler += (s, e) =>
{
if (e.Severity == XmlSeverityType.Warning)
{
issuesBuilder.AppendLine($"Warning: {e.Message}");
}
else if (e.Severity == XmlSeverityType.Error)
{
issuesBuilder.AppendLine($"Error: {e.Message}");
valid = false;
}
};
using (var xmlSteam = File.OpenRead(xmlPath))
{
var reader = XmlReader.Create(xmlSteam, xmlReaderSettings);
while (reader.Read());
}
issues = issuesBuilder.ToString();
return valid;
}
public static void Main(string[] args)
{
string issues;
if (TryValidate("books.xml", "books.xsd", out issues))
{
using (var xmlStream = File.OpenRead("books.xml"))
{
XmlSerializer xs = new XmlSerializer(typeof(Books), new XmlRootAttribute("books"));
var books = (Books)xs.Deserialize(xmlStream);
Console.WriteLine("Count of Books: {0}", books.BookArray.Length);
}
}
Console.WriteLine("XML issues:");
Console.WriteLine(issues);
}
}
}
A program kimenete:
Count of Books: 2
XML issues:
Az osztályokban feltűnhetnek az XmlElement és az XmlAttribute attribútumok. Az XmlElement attribútumok xml elemeket Ãrnak le, mint például az <author></author>, mÃg az XmlAttribute azt jelzi, hogy a tulajdonság egy attribútuma a tartalmazó elemnek. Például a <book> elem esetén ilyen a title.
A validációt a TryValidate metódus végzi el, ami igaz értéket ad vissza, ha a dokumentum megfelel a séma szabályoknak és az esetleges hibák és figyelmeztetések az issues kimeneti változóba kerülnek. A tényleges validációt az XmlReader végzi, ami konfigurálható az XmlReaderSettings osztály segÃtségével. Ennek átadva egy vagy több sémát (schemaSet a kódban ) történik majd a validáció. A validáció eredményérÅ‘l a ValidationEventHandler eseménykezelÅ‘ben tájékozódhatunk. Ez jelenleg a kódban egy StringBuilder példányhoz fűzi az információkat, amit a validáció végén szöveggé alakÃtunk. A validációnk jelenleg megengedÅ‘, mivel ténylegesen hibának csak a hibákat tekintjük, de a figyelmeztetéseket sem szabad figyelmen kÃvül hagyni és biztonsági szempontból jobb, ha a figyelmeztetéseket is hibáknak tekintjük.
A deszerializáció érdekessége, hogy a XmlSerializer példányosÃtásakor egy második paraméternek át van adva egy új XmlRootAttribute példány, ami az XML dokumentum root elemét határozza meg.
.NET és a sebesség optimalizáció
Az XML szerializáció nem a leggyorsabb .NET alatt sem és sajnos a korábban bemutatott sgen sem használható, de ez nem azt jelenti, hogy nem tudunk gyorsÃtani a dolgokon. A .NET 5 óta lehetÅ‘ség van extra kódot generáltatni fordÃtás közben a megÃrt kód kiegészÃtéseként. Ezt használja ki a Microsoft.XmlSerializer.Generator NuGet csomag, amit ha telepÃtünk az XML szerializációt tartalmazó C# projektünkbe, akkor a fordÃtás során egy optimalizált XML szerializációs kód keletkezik. Lényegében ennek a csomagnak a hozzáadása az sgen modern megfelelÅ‘je.