# Làm quen với .NET Serialization

Trong .net hỗ trợ nhiều cách khác nhau để serialize dữ liệu, tất cả đều có chung mục đích là serialize object, nhưng điểm khác biệt là format output sẽ khác nhau. Các hàm dùng để serialize object trong .net có thể được gọi là formatter hoặc serializer và có thể phân loại theo 3 prefix là:

* Serializer như: XmlSerializer, DataContractSerializer, JavaScriptSerializer
* Formatter như: BinaryFormatter, SoapFormatter
* Reader như: XamlReader
* Hoặc thậm chí là từ thư viện thứ ba như JSON.NET&#x20;

Ta sẽ bắt đầu với gia phả nhà Formatter để làm quen với quá trình Serialize trong .net

## Formatter

Đối với formatter thì có 3 loại phổ biến:

* BinaryFormatter：cho ra kết quả là binary
* SoapFormatter：cho ra kết quả dưới dạng soap format
* ObjectStateFormatter：dùng với ViewState&#x20;

Để một object có thể serialize, ta chỉ cần thêm attribute `[Serializable]` (không giống như Java phải implement lại Serializable interface)

Ví dụ một object có thể serialize

```csharp
using System;

namespace demo
{
    [Serializable]
    public class SerializeObject
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public SerializeObject(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }
    }
}
```

Mình sẽ khai báo `BinaryFormater` để serialize object thành dạng binary như sau:

```csharp
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace demo
{
    internal class Program
    {
        public static void BinaryFormatterSerialize(string file, object o)
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
            bf.Serialize(fs,o);
            fs.Close();
        }

        public static object BinaryFormatterDerialize(string file)
        {
            BinaryFormatter bf = new BinaryFormatter();
            Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
            object o = bf.Deserialize(stream);
            stream.Close();
            return o;
        }
        public static void Main(string[] args)
        {
            SerializeObject a = new SerializeObject("endy", 8);
            string file = "data.bin";
            
            BinaryFormatterSerialize(file, a);
            SerializeObject deser = (SerializeObject)BinaryFormatterDerialize(file);
            Console.WriteLine(deser.Name + deser.Age);
        }
    }
}
```

Khi deser ta sẽ lưu kết quả vào file `data.bin` , ta có thể thấy dữ liệu được lưu vào file data.bin là dạng binary

<figure><img src="/files/VogcKtNwyfi2oAvsEdsx" alt=""><figcaption></figcaption></figure>

Còn khi deser ta sẽ có kết quả

<figure><img src="/files/RDQos1G3aJ59y9RJiVxK" alt="" width="563"><figcaption></figcaption></figure>

## Magic Methods

Trong quá trình serialize và deserialze đương nhiên sẽ có những magic method được gọi, đối với .net thì những magic method sẽ được gọi là callback method trong quá trình seri/deser

Cụ thể quá trình seri/deser diễn ra như thế nào thì có thể tham khảo: [dotnet-serialize-101.mdY4er/dotnet-deserialization](https://github.com/Y4er/dotnet-deserialization/blob/main/dotnet-serialize-101.md)​

Trong .NET một Object Serialize sẽ có 4 magic method cơ bản như sau:

* **OnSerializing**: được gọi trước khi quá trình Serialize diễn ra
* **OnSerialized**: được gọi khi hoàn thành quá trình Serialize
* **OnDeserializing**: được gọi trước khi quá trình Deserialize diễn ra
* **OnDeserialized**: được gọi khi hoàn thành quá trình Deserialize

Code ví dụ:

```csharp
    using System;
    using System.Runtime.Serialization;

    namespace demo
    {
        [Serializable]
        public class SerializeObject
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public SerializeObject(string name, int age)
            {
                this.Name = name;
                this.Age = age;
            }
            [OnDeserializing]
            private void TestOnDeserializing(StreamingContext sc) { Console.WriteLine("OnDeserializing"); }
            
            [OnDeserialized]
            private void TestOnDeserialized(StreamingContext sc) { Console.WriteLine("OnDeserialized"); }
            
            [OnSerializing]
            private void TestOnSerializing(StreamingContext sc) { Console.WriteLine("OnSerializing"); }
            
            [OnSerialized]
            private void TestOnSerialized(StreamingContext sc) { Console.WriteLine("OnSerialized"); }
        }
    }
```

Khi seri và deser Object trên ta được kết quả

<figure><img src="/files/KTrecdg2lrDYMgEIEwzR" alt="" width="375"><figcaption></figcaption></figure>

Ngoài 4 methods trên thì nếu khi ta implement lại một số interface có hỗ trợ serialize ta sẽ có thêm nhiều magic methods khác, ví dụ như khi implement **ISerializable** interfaces thì ta phải implement lại constructor và method `GetObjectData` , và method này sẽ được gọi trong quá trình serialize

Code ví dụ:

```csharp
    using System;
    using System.Runtime.Serialization;

    namespace demo
    {
        [Serializable]
        public class SerializeObject : ISerializable
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public SerializeObject(string name, int age)
            {
                this.Name = name;
                this.Age = age;
            }
            [OnDeserializing]
            private void TestOnDeserializing(StreamingContext sc) { Console.WriteLine("OnDeserializing"); }
            
            [OnDeserialized]
            private void TestOnDeserialized(StreamingContext sc) { Console.WriteLine("OnDeserialized"); }
            
            [OnSerializing]
            private void TestOnSerializing(StreamingContext sc) { Console.WriteLine("OnSerializing"); }
            
            [OnSerialized]
            private void TestOnSerialized(StreamingContext sc) { Console.WriteLine("OnSerialized"); }

            public void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                Console.WriteLine("GetObjectData is called");
            }
            protected SerializeObject(SerializationInfo info, StreamingContext context)
            {
                Console.WriteLine("CustomObject2 constructor is called");
            }

        }
    }
```

Kết quả:

<figure><img src="/files/jGSFmL4EQoMbeFhE173l" alt="" width="375"><figcaption></figcaption></figure>

{% hint style="info" %}
Lưu ý: ta có thể thấy khi implement 2 methods trên ta cần phải khai báo kèm theo `SerializationInfo` class, nguyên nhân của việc này là .net cho phép ta có thể can thiệp vào quá trình seri hoặc deser thông qua SerializationInfo. SerializationInfo giống như một biến đại diện cho thông tin của seri stream, mọi thông tin như type, giá trị của object trong quá trình seri/deser đều được lưu trong này
{% endhint %}

<figure><img src="/files/Ln1nrHg8AcIUBs690Dzn" alt="" width="563"><figcaption></figcaption></figure>

Tóm tắt:

<figure><img src="/files/B7sYxkf7Um9Y3SnbDIOT" alt="" width="563"><figcaption><p>Cre: Lụm dâu không nhớ <span data-gb-custom-inline data-tag="emoji" data-code="1f604">😄</span></p></figcaption></figure>

Ngoài ra thằng Formarter còn có tính năng cho phép ta serialize object có type không hỗ trợ. Ta có thể làm điều đó với interface `ISerializationSurrogate` . Ta có thể hình dung Interface này giống như một “người đại diện” giúp ta có thể serialize được object type mà Formarter không hỗ trợ. Ví dụ mình có object `Person`, là một object bình thường không sử dụng attribute `[Serialize]`

```csharp
namespace demo
{
    class Person
    {
        public string Name { get; set; }
        public Person(string name)
        {
            Name = name;
        }
        public override string ToString()
        {
            return Name;
        }
        
    }
}
```

Đây hoàn toàn là object type mà Formatter không thể serialize, do đó ta sẽ khai báo thêm một class để làm “đại diện” cho Class Person này khi thực hiện serialize

```csharp
using System;
using System.Runtime.Serialization;

namespace demo
{
    class PersonSerializeSurrogate : ISerializationSurrogate
    {
        public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
        {
            Console.WriteLine("ISerializationSurrogate.GetObjectData is calling");
            var p = (Person)obj;
            info.AddValue("Name", p.Name);
        }

        public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            Console.WriteLine("ISerializationSurrogate.SetObjectData is calling");
            var p = (Person)obj;
            p.Name = info.GetString("Name");
            return p;
        }
    }
}
```

Khi implement `ISerializationSurrogate` ta sẽ phải khai báo 2 method là:

* **GetObjectData**: được gọi khi serialize
* **SetObjectData**: được gọi khi deser

Để Formarter có thể sử dụng được `ISerializationSurrogate` ta tiến hành khai báo tại hàm main như sau:

```csharp
var formatter = new BinaryFormatter(); 
SurrogateSelector ss = new SurrogateSelector();
ss.AddSurrogate(typeof(Person), formatter.Context, new PersonSerializeSurrogate());
formatter.SurrogateSelector = ss; 
FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(fs, new Person("endy"));
fs.Close();
```

Mình sẽ khai báo 2 instance của `BinaryFormatter` và `SurrogateSelector`. Sau đó dùng method `AddSurrogate` để khai báo object type nào sẽ được `PersonSerializeSurrogate` làm “đại diện”, ở đây mình sẽ có type của `Person`

Chi tiết các agrument của method AddSurrogate

<figure><img src="/files/5ab1N7xbjhveeWqoBm4l" alt="" width="563"><figcaption></figcaption></figure>

Tiếp theo mình add “đại diện” vào Formatter và tiến hành serialize như bình thường, mình ghi kết quả ra file để so sánh kết quả khi dùng `ISerializationSurrogate` và khi không dùng

Các bạn có thể thấy object được serialize thông qua `ISerializationSurrogate` không khác gì một object được serialize trực tiếp như bình thường

<figure><img src="/files/in2T6LDrOHrbf0cvVWpB" alt=""><figcaption></figcaption></figure>

Về thứ thự gọi callback methods trong việc serialize thông qua “đại diện” thì `GetObjectData` sẽ nằm giữa OnSerializing và OnSerialized, còn `SetObjectData` thì nằm giữa OnDeserializing và OnDeserialized

<figure><img src="/files/WW2AxzV32flmK6Jo5kSU" alt="" width="563"><figcaption></figcaption></figure>

Tóm tắt quá trình seri của Formatter sẽ như sau

<figure><img src="/files/trQC5uMzA8FyO77oTiEI" alt=""><figcaption><p>Cre: <a href="https://github.com/Y4er/dotnet-deserialization/blob/main/dotnet-serialize-101.assets/image-20210420105228965.png">https://github.com/Y4er/dotnet-deserialization/blob/main/dotnet-serialize-101.assets/image-20210420105228965.png</a></p></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://endy.gitbook.io/endys-notes/web-security/.net-sec-learn/.net-deserialize/lam-quen-voi-.net-serialization.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
