نحوه تبدیل XML در NET Core.

سه شنبه 11 شهریور 1399

قبلا در مورد نحوه تبدیل JSON در NET Core. صحبت کردیم. بیشتر آن را به این دلیل نوشتیم که فکر می‌کردیم افراد برای کار با JSON خود را به سختی می‌اندازند.

نحوه تبدیل XML در NET Core.

ما فکر می‌کنیم کار با XML کمی متفاوت است. هنوز کتابخانه استاندارد خوبی برای کار با XML وجود ندارد.

برخلاف JSON که دارای کتابخانه JSON.NET که برای مدیریت هر چیزی است، بیشتر موارد وقتی با XML کار می‌کنید، از یکی از Parserهای داخلی XML در فریم‌ورک NET Core. استفاده خواهید کرد. این‌ها ممکن است کمی ناامیدکننده باشند. برخی از این موارد این است که آن‌ها در اوایل ساخت NET. ایجاد شده‌اند و به همین دلیل، همیشه باید با موارد قبلی سازگار باشند بنابراین شما در چیزهایی مثل Generics شکست می‌خورید. مورد دیگر این است که مشخصات واقعی XML که شامل مواردی مانند فضای نام‌ها (namespace) و DTDها می‌شود، گرچه در ابتدا ساده به نظر می‌رسد، اما می‌تواند فوق‌العاده خشن باشد. منظور ما از خشن این است که اگر فقط یک قطعه از پازل را از دست بدهید، همه چیز به سادگی کار نخواهد کرد و ممکن است ساعت‌ها طول بکشد تا ببینید چه چیزی اشتباه است.

به هر حال، بیاید سریع برویم و گزینه‌های مربوط به کار با XML در NET. را بررسی کنیم.

مثالی برای فایل XML

ما می‌خواهیم از یک فایل XML خیلی ساده استفاده کنیم که دارای المنت، پراپرتی اتربیوت و یک لیست است. ما هنگام بررسی گزینه‌ها از این موارد استفاده می‌کنیم، بنابراین همیشه سعی می‌کنیم فایل مشابه‌ای را بخوانیم.

<?xml version="1.0" encoding="utf-8" ?>
<MyDocument xmlns="http://www.dotnetcoretutorials.com/namespace">
  <MyProperty>Abc</MyProperty>
  <MyAttributeProperty value="123" />
  <MyList>
    <MyListItem>1</MyListItem>
    <MyListItem>2</MyListItem>
    <MyListItem>3</MyListItem>
  </MyList>
</MyDocument>

استفاده از XMLReader

خوب اولین گزینه‌ای که داریم استفاده از کلاس " XMLReader" است. این فقط یک XML Parser رو به جلو است (منظور ما این است که فایل را تقریبا خط به خط می‌خواند). این گزینه خیلی ابتدایی است. مثلا کد شما ممکن است خیلی شبیه به این باشد:

XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;

using (var fileStream = File.OpenText("test.xml"))
using(XmlReader reader = XmlReader.Create(fileStream, settings))
{
    while(reader.Read())
    {
        switch(reader.NodeType)
        {
            case XmlNodeType.Element:
                Console.WriteLine($"Start Element: {reader.Name}. Has Attributes? : {reader.HasAttributes}");
                break;
            case XmlNodeType.Text:
                Console.WriteLine($"Inner Text: {reader.Value}");
                break;
            case XmlNodeType.EndElement:
                Console.WriteLine($"End Element: {reader.Name}");
                break;
            default:
                Console.WriteLine($"Unknown: {reader.NodeType}");
                break;
        }
    }
}

با خروجی مانند این:

Unknown: XmlDeclaration
Start Element: MyDocument. Has Attributes? : True
Start Element: MyProperty. Has Attributes? : False
Inner Text: Abc
End Element: MyProperty
Start Element: MyAttributePropety. Has Attributes? : True
Start Element: MyList. Has Attributes? : False
Start Element: MyListItem. Has Attributes? : False
Inner Text: 1
End Element: MyListItem
Start Element: MyListItem. Has Attributes? : False
Inner Text: 2
End Element: MyListItem
Start Element: MyListItem. Has Attributes? : False
Inner Text: 3
End Element: MyListItem
End Element: MyList
End Element: MyDocument

این مورد یادآور استفاده از ADO.NET و خواندن سطر به سطر داده‌ها و تلاش برای ذخیره کردن آن در یک آبجکت است. ایده کلی به این دلیل است که به صورت خط به خط تبدیل را انجام می‌دهید، و حافظه کمتری دارد. اما شما همچنین باید هر خط را به صورت جداگانه با هر تعداد جایگشت از عناصر، اتربیوت‌ها، لیست‌ها و غیره مدیریت کنید. ما فکر می‌کنیم تنها دلیل استفاده از این متد می‌تواند در صورتی باشد که شما فایل‌های خیلی بزرگ XML را دارید (بیشتر از 100 مگابایت) یا به دنبال موارد بسیار خاص هستید. مثلا فقط می‌خواهید یک عنصر را از فایل بخوانید، و وقتی به دنبال آن یک عنصر هستید، نمی‌خواهید همه چیز را لود کنید.

نکته دیگری که می‌خواهیم به آن اشاره کنم این است که فضای نام‌های XML و مشکل پیرامون آن‌ها همراه با XMLReader نیستند.

استفاده از XPathDocument/XPathNavigator

خوب روش دیگر دریافت گره‌های (Node) منحصربه‌فرد XML که می‌تواند یک سند را جستجو کند، استفاده از آبجکت XPathNavigator است.

ابتدا، کد را ببینیم:

using (var fileStream = File.Open("test.xml", FileMode.Open))
{
    //Load the file and create a navigator object. 
    XPathDocument xPath = new XPathDocument(fileStream);
    var navigator = xPath.CreateNavigator();

    //Compile the query with a namespace prefix. 
    XPathExpression query = navigator.Compile("ns:MyDocument/ns:MyProperty");

    //Do some BS to get the default namespace to actually be called ns. 
    var nameSpace = new XmlNamespaceManager(navigator.NameTable);
    nameSpace.AddNamespace("ns", "http://www.dotnetcoretutorials.com/namespace");
    query.SetContext(nameSpace);

    Console.WriteLine("My Property Value : " + navigator.SelectSingleNode(query).Value);
}

اگر بخواهیم صادقانه بگوییم این کد خوبی نیست، و به یک دلیل می‌توانیم بگوییم که بد است. فضای نام‌ها در اینجا واقعا باعث زحمت می‌شوند. در مورد خاص ما، از آنجایی که ما یک فضای نام پیش‌فرض (default namespace) داریم، این تنها راهی بود که می‌توانستیم با XPath کار کنیم. بدون فضای نام کارها واقعا به سهولت انجام می‌شوند. می‌توانیم بگوییم در هنگام کار با XML هر دردسری که تا به حال داشته‌ایم به خاطر فضای نام‌ها بوده است.

بنابراین اجازه دهید موردی را به شما بگویم. اگر سندی که با آن کار می‌کنید از فضای نام‌ها استفاده نمی‌کند (یا مایل به حذف آن‌ها هستید)، و باید از اکسپشن XPath برای دریافت یک گره استفاده کنید، پس استفاده از XMLNavigator  در واقع گزینه بدی نیست.

استفاده از XMLDocument

XMLDocument می‌تواند مانند به روز رسانی XPathNavigator رفتار کند. این چند متد ساده‌تر برای لود کردن اسناد دارد، و به شما امکان می‌دهد XMLDocuments را در حافظه نیز اصلاح کنید.

XmlDocument document = new XmlDocument();
document.Load("test.xml");

XmlNamespaceManager m = new XmlNamespaceManager(document.NameTable);
m.AddNamespace("ns", "http://www.dotnetcoretutorials.com/namespace");
Console.WriteLine(document.SelectSingleNode("ns:MyDocument/ns:MyProperty", m).InnerText);

به طور کلی شما هنوز هم باید با برخی فضای نام‌ها سر و کار داشته باشید (مثلا Default Namespaces به خوبی مدیریت نمی‌شود)، و شما هنوز هم باید هر عنصر را طبق نیاز خود یک به یک دریافت کنید. اما اگر می‌خواهید فقط یک زیر مجموعه کوچک از XML را لود کنید، این گزینه خوبی است. این واقعیت که می‌توانید XML را تغییر دهید و آن را در فایل ذخیره کنید نیز یک مورد خوب است.

استفاده از XMLSerializer

به نظر ما XMLSerializer بهترین گزینه برای تبدیل XML در NET Core. است. اگر قبلا از JSONDocument از JSON.NET استفاده کرده‌اید، راه‌اندازی آن خیلی شبیه است.

ابتدا به سادگی یک کلاس ایجاد می‌کنیم که از فایل XML شکل گرفته است. ما از مجموعه‌ای از اتربیوت‌ها برای مشخص کردن نحوه خواندن سند، اینکه از کدام فضای نام استفاده می‌کنیم، حتی اینکه کدام نوع عنصر را برای deserialize امتحان می‌کنیم (اتربیوت، عنصر یا آرایه)، استفاده می‌کنیم.

[XmlRoot("MyDocument", Namespace = "http://www.dotnetcoretutorials.com/namespace")]
public class MyDocument
{
    public string MyProperty { get; set; }

    public MyAttributeProperty MyAttributeProperty { get; set; }

    [XmlArray]
    [XmlArrayItem(ElementName = "MyListItem")]
    public List MyList { get; set; }
}

public class MyAttributeProperty
{
    [XmlAttribute("value")]
    public int Value { get; set; }
}

واقعا ساده است. و سپس XML را خوانده و آن را به این کلاس تبدیل می‌کنیم:

using (var fileStream = File.Open("test.xml", FileMode.Open))
{
    XmlSerializer serializer = new XmlSerializer(typeof(MyDocument));
    var myDocument = (MyDocument)serializer.Deserialize(fileStream);

    Console.WriteLine($"My Property : {myDocument.MyProperty}");
    Console.WriteLine($"My Attribute : {myDocument.MyAttributeProperty.Value}");

    foreach(var item in myDocument.MyList)
    {
        Console.WriteLine(item);
    }
}

این مورد بدون تلاش برای دریافت فضای نام درست، بدون تلاش برای درست کار کردن Xpath، کار می‌کند. احتمالا وقتی شروع به استفاده از XMLSerializer می‌کنید، تعجب خواهید کرد که چرا تا به حال خود را به زحمت انداخته و سعی کرده‌اید تا دوباره به صورت دستی مستندات XML را بخوانید.

اکنون یک اخطار بزرگ وجود دارد. اگر واقعا به بخش بزرگی از مستندات توجه نمی‌کنید و فقط سعی می‌کنید تا یک عنصر عمیق را دریافت کنید، ایجاد این کلاس‌ها و مدل‌های بزرگ فقط برای دریافت یک عنصر می‌تواند شما را به زحمت زیادی بیاندازد.

با این حال، در 99% موارد، از XMLSerializer برای تبدیل XML استفاده کنید. نسبت به گزینه‌های دیگر کمتر شکننده است و الگوی بسیار مشابه‌ای را برای JSON serialization دنبال می‌کند که به این معناست که هر کسی که با یکی از این‌ها کار کرده باشد، می‌تواند با دیگری نیز کار کند.

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید