CodeGym /Các khóa học /C# SELF /Cấu hình serialization XML

Cấu hình serialization XML

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Bạn đã biết rằng .NET có thể serialize và deserialize object sang XML mặc định, dựa trên public fields và properties. Nhưng khi muốn có XML đẹp, tương thích với phần mềm/tiêu chuẩn của người khác, hoặc khi không muốn đưa vào XML một vài chi tiết nội bộ, cần tinh chỉnh kỹ càng.

Trong C# có những thuộc tính serialization — các "tag" đặc biệt để đánh dấu class, property hoặc field. Nhờ mấy thuộc tính này bạn có thể kiểm soát gần như mọi thứ: field nào vào XML, field nào bị ignore, tên phần tử ra sao, biến thành attribute hay không, cấu trúc phức tạp như thế nào và thậm chí thứ tự phần tử!

Hãy tưởng tượng serialization mặc định giống như việc tự động điền mẫu đơn: họ tên vào các ô tương ứng, mọi thứ còn lại — đại khái. Còn với các thuộc tính bạn như một viên chức hành chính pro: quyết định dòng nào ghi chỗ nào, bỏ qua gì, đánh dấu đỏ gì, hay chui vào khu vực bí mật.

Mapping object và XML

Class/Property Attribute Kết quả trong XML
[XmlRoot("Persons")]
Class UserList
<Persons>...</Persons>
List<User> Users
[XmlElement("Person")]
<Person>...</Person>
int Id
[XmlAttribute("id")]
id="..." (attribute)
string Name
[XmlElement("FullName")]
<FullName>...</FullName>
string[] Emails
[XmlArray("EMails")][XmlArrayItem("Email")]
<EMails><Email>...</Email></EMails>
DateTime RegisteredAt
[XmlIgnore] (xem RegisteredAtString) -
string RegisteredAtString
[XmlAttribute("registered")]
registered="..."
Address[] Addresses
[XmlArray("UserAddresses")][XmlArrayItem("Address")]
<UserAddresses>...</UserAddresses>

2. Tổng quan các thuộc tính chính của XML-serialization

Giờ mình đi qua các thuộc tính phổ biến mà cộng đồng .NET đã dùng xuyên nhiều thế hệ!

Thuộc tính [XmlElement] — Tên phần tử

Thay đổi tên element trong XML hoặc đánh dấu property/field như XML-element.

public class Person
{
    [XmlElement("FullName")]
    public string Name { get; set; }
}

XML:

<Person>
  <FullName>Vasiliy Petrov</FullName>
</Person>

Nếu không chỉ định attribute — mặc định tên element sẽ trùng với tên property/field.

Thuộc tính [XmlAttribute] — Serialize thành XML-attribute

Cho phép serialize property/field như XML-attribute, không phải element.

public class Person
{
    [XmlAttribute("id")]
    public int Id { get; set; }
}

XML:

<Person id="123"></Person>

Trong các XML-API thực tế thường dùng attribute cho id, ngày, flag, v.v.

Thuộc tính [XmlIgnore] — Bỏ qua field/property

Ước mơ của mọi người hay lo ngại: loại hẳn property khỏi quá trình serialization và deserialization! Mọi thứ được đánh dấu bằng thuộc tính này sẽ không xuất hiện trong XML cuối cùng.

public class Person
{
    [XmlIgnore]
    public string InternalNote { get; set; }
}

XML: (property InternalNote sẽ không có ở đây và không bao giờ có)

Thuộc tính [XmlArray][XmlArrayItem] — kiểm soát collection

Thuộc tính đặc biệt cho mảng và collection. [XmlArray] đặt tên wrapper ngoài cho mảng, còn [XmlArrayItem] — tên cho từng phần tử.

public class Person
{
    [XmlArray("Phones")]
    [XmlArrayItem("Phone")]
    public string[] PhoneNumbers { get; set; }
}

XML:

<Person>
  <Phones>
    <Phone>+12951234567</Phone>
    <Phone>+12876543210</Phone>
  </Phones>
</Person>

Thuộc tính [XmlRoot] — Tên phần tử gốc

Đánh dấu class để thay đổi tên root element trong XML.

[XmlRoot("User")]
public class Person
{
    public string Name { get; set; }
}

XML:

<User>
  <Name>Anna</Name>
</User>

Thuộc tính [XmlText] — Serialize thành nội dung text của element

Đôi khi (hoặc theo spec) bạn muốn giá trị của property là text bên trong element, chứ không phải một child element hay attribute.

public class Note
{
    [XmlText]
    public string Content { get; set; }
}

XML:

<Note>Gọi cho bà!</Note>

Thuộc tính [XmlNamespaceDeclarations], [XmlElement(Type = ...)]

Cho các scenario nâng cao hơn (ví dụ serialize với namespace, hỗ trợ inheritance, v.v.) còn có những thuộc tính đặc thù hơn. Nhớ xem tài liệu chính thức về XML-serialization để biết danh sách đầy đủ.

3. Thực hành: cải thiện serialization trên ví dụ thực tế

Bước từng bước, ta sẽ hoàn thiện ứng dụng học tập để có "XML-serialization hạng sang", khiến cả inspector XML khó tính nhất cũng hài lòng.

Class cơ bản không có thuộc tính

Giả sử chúng ta có class user như sau:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string[] Emails { get; set; }
    public DateTime RegisteredAt { get; set; }
    public string Password { get; set; }
}

Serialization mặc định sẽ cho XML kiểu như sau (chú ý ngày sẽ được ghi theo kiểu riêng của nó):

<User>
  <Id>42</Id>
  <Name>Ivan Ivanov</Name>
  <Emails>
    <string>user@mail.com</string>
    <string>admin@site.org</string>
  </Emails>
  <RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
  <Password>qwerty</Password>
</User>

Các vấn đề:

  • Mật khẩu tốt nhất là không serialize.
  • Muốn ngày như attribute và ở định dạng dễ đọc hơn.
  • Muốn Emails ở dạng đẹp hơn.
  • Muốn root element là <Person>.

Thêm cấu hình bằng thuộc tính

[XmlRoot("Person")]
public class User
{
    [XmlAttribute("id")]
    public int Id { get; set; }

    [XmlElement("FullName")]
    public string Name { get; set; }

    [XmlArray("EMails")]
    [XmlArrayItem("Email")]
    public string[] Emails { get; set; }

    [XmlAttribute("registered")]
    public string RegisteredAtString
    {
        get => RegisteredAt.ToString("yyyy-MM-dd");
        set => RegisteredAt = DateTime.Parse(value);
    }

    [XmlIgnore]
    public string Password { get; set; }

    [XmlIgnore]
    public DateTime RegisteredAt { get; set; }
}

Giải thích các điểm tinh tế:

  • Dùng [XmlAttribute("registered")] để serialize ngày thành attribute.
  • Để serialize không phải DateTime mà là chuỗi của nó, thêm property riêng RegisteredAtString, còn field thực tế thì ẩn bằng [XmlIgnore].
  • Ẩn password bằng [XmlIgnore].
  • Mảng email được cấu hình để wrapper ngoài là <EMails>, mỗi email là <Email>.

Kết quả:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
    <Email>admin@site.org</Email>
  </EMails>
</Person>

4. Làm việc với object lồng nhau

XML thể hiện tốt nhất khi có các object lồng nhau (ví dụ user và addresses của họ).

public class Address
{
    [XmlAttribute]
    public string City { get; set; }

    [XmlText]
    public string Details { get; set; }
}

public class User
{
    // ...các property khác, xem ở trên...

    public Address[] Addresses { get; set; }
}

Và thêm cấu hình cho mảng addresses:

[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }

Thì XML sẽ trông như sau:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
  </EMails>
  <UserAddresses>
    <Address City="Neon city">ul. Zamkovaya, d.1</Address>
    <Address City="North Cave">ul. Kraynyaya d.12</Address>
  </UserAddresses>
</Person>

Bạn thấy rằng mỗi <Address> có city — là attribute, còn nội dung địa chỉ được ghi như text bên trong.

5. Lỗi thường gặp và lưu ý

Nếu bạn serialize object phức tạp mà không thấy thay đổi trong XML như mong muốn — có thể là quên chỉ định đúng thuộc tính. Đôi khi properties/fields vẫn không được đánh dấu, lúc đó .NET dùng hành vi mặc định.

Một bẫy nữa là tên collection. Nếu collection (ví dụ mảng string) không được đánh dấu cả [XmlArray] lẫn [XmlArrayItem], các phần tử sẽ được ghi với tag string (như ví dụ ban đầu). Để tránh, luôn chỉ định rõ tên tag bằng thuộc tính.

Cẩn thận với các property chỉ có getter (getter-only) — những property này không deserialize được theo mặc định. Cố gắng để có public set.

Một số kiểu (ví dụ từ điển hoặc interface) không serialize được bằng XmlSerializer tiêu chuẩn, hoặc cần xử lý riêng.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tuần tự hóa một collection sử dụng XmlArray và XmlArrayItem
Tuần tự hóa một collection sử dụng XmlArray và XmlArrayItem
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION