NET 9 BinaryFormatter migration paths

karenpayneoregon

Karen Payne

Posted on November 30, 2024

NET 9 BinaryFormatter migration paths

Introduction

With the release of .NET 9 Core Framework, BinaryFormatter Class is now obsolete and should not be used because of security risk. BinaryFormatter is now not included in the .NET 9 runtime.

There is no drop-in replacement for BinaryFormatter, but there are several serializers recommended for serializing .NET types. Regardless of which serializer you choose, changes will be needed for integration with the new serializer. During these migrations, it's important to consider the trade-offs between coercing the new serializer to handle existing types with as few changes as possible vs. refactoring types to enable idiomatic serialization with the chosen serializer. Once a serializer is chosen, its documentation should be studied for best practices.

(Above is from Microsoft documentation)

It is recommended that before performing a migration to ensure there is a working copy of a project in source control repository such as GitHub. Also, in the working copy have a test project that during the migration new test methods will be needed.

Learn about migration options from the list of serializer by way of code samples.

  • System.Text.Json
  • XML using DataContractSerializer
  • Binary using MessagePack
  • Binary using protobuf-net

Important code sample notes

Each code sample are void of exception handling as all work in done in the project’s debug folder which a developer has permissions. For an actual application use assertion, try-catch blocks and optionally logging runtime errors to a physical log file using a provider such as SeriLog.

For all code samples except one there is no encryption used. The one sample using encryption uses Inferno NuGet package working with json data.

Samples presented are trimmed down from what is in the following.

Source code

Samples

All code samples use mocked up data from here.

In Program.cs, all code samples can be executed via being presented with a question.

Program.cs code

JSON using System.Text.Json

Using System.Text.Json is straight forward, call the Serialize method to serialize data and Deserialize with the type.

In the following examples the following private variable represents the file to read and write too.

private static string fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "person1.json");
Enter fullscreen mode Exit fullscreen mode

The following models are used.

public class Person1 : IPerson
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public string SSN { get; set; }
    public Address Address { get; set; }
}

[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class Address
{
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Note
ProtoContract on Address is ignored for this sample.

public static JsonSerializerOptions Indented => new() { WriteIndented = true };


public static void SerializePeople()
{

    List<Person1> people = MockedData.GetPersons1();

    var json = JsonSerializer.Serialize(people, Indented);

    File.WriteAllText(fileName, json);

}


public static void DeserializePeople()
{

    var people = JsonSerializer.Deserialize<List<Person1>>(
        File.ReadAllText(fileName));

}
Enter fullscreen mode Exit fullscreen mode

XML using DataContractSerializer

DataContractSerializer Class Serializes and deserializes an instance of a type into an XML stream or document using a supplied data contract.

File to read and write too.

private static string fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "person1.data");
Enter fullscreen mode Exit fullscreen mode

The following models are used. In the json sample, BirthDate is DateOnly where in this sample a DateTime as DataContractSerializer does not handle DateOnly out of the box.

public class Person2
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public string SSN { get; set; }
    public Address Address { get; set; }
}

[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class Address
{
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Code

This code sample is a condensed version of what is found in the GitHub repository which has a separate class for Serialize and Deserialize found in DataContractSerializerHelpers class. So if this is your option copy DataContractSerializerHelpers into a project or if there are several projects that need migration, place the class in a new class project and reference the class project.

internal class DataContractSerializerOperations
{
    private static string fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "person1.data");

    public static void SerializePeople()
    {

        List<Person2> people = MockedData.GetPersons2();

        var text = Serialize(people);
        File.WriteAllText(fileName,text);

        var list = Deserialize(text, typeof(List<Person2>)) as List<Person2>;

    }

    public static string Serialize(object obj)
    {
        using MemoryStream memoryStream = new();
        DataContractSerializer serializer = new(obj.GetType());
        serializer.WriteObject(memoryStream, obj);
        return Encoding.UTF8.GetString(memoryStream.ToArray());
    }

    public static object Deserialize(string xml, Type toType)
    {
        using MemoryStream memoryStream = new(Encoding.UTF8.GetBytes(xml));

        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(memoryStream, Encoding.UTF8, 
            new XmlDictionaryReaderQuotas(), null);

        DataContractSerializer serializer = new DataContractSerializer(toType);
        return serializer.ReadObject(reader);
    }
}
Enter fullscreen mode Exit fullscreen mode

MessagePack

The extremely fast MessagePack serializer for C#.

MessagePack is the most configurable, performant and capable of all available migration paths. In the code samples provided are basic.

One nice feature is the ability to configure a class to use MessagePack when a developer does not have access to the class source code but there is performance penalties as described in their documentation.

MessagePack documentation

MessagePackAnalyzer NuGet package contains analyzers and source generator for MessagePack for C#. Verify rules for [MessagePackObject] and code fix for [Key].

Security concerns.

Code

internal class MessagePackOperations
{
    public static void SingleFriend()
    {

        var fileName = "SingleFriend.bin";

        var list = MockedData.Friends();
        var friend = list[0];

        var bytes = MessagePackSerializer.Serialize(friend, 
            MessagePack.Resolvers.ContractlessStandardResolver.Options);

        File.WriteAllBytes(fileName, bytes);

        var bytes1 = File.ReadAllBytes(fileName);
        var deserialized = MessagePackSerializer.Deserialize<Friend>(bytes1, 
            MessagePack.Resolvers.ContractlessStandardResolver.Options);

        AnsiConsole.MarkupLine(BeautifyFriendDump(deserialized.Dump()));
    }

    public static void SingleBinaryFriend1()
    {

        PrintCyan();

        var fileName = "SingleFriend1.bin";

        Friend1 friend = new()
        {
            Id = 1,
            FirstName = "John",
            LastName = "Doe",
            BirthDate = new DateOnly(1980, 1, 1),
            CellPhone = "555-555-5555"
        };

        // Serialize Friend to byte[]
        var bytes = MessagePackSerializer.Serialize(friend);

        var json = MessagePackSerializer.ConvertToJson(bytes);

        File.WriteAllBytes(fileName, bytes);
        // Deserialize byte[] to Friend
        var bytes1 = File.ReadAllBytes(fileName);
        var deserialized = MessagePackSerializer.Deserialize<Friend1>(bytes1);

        AnsiConsole.MarkupLine(BeautifyFriendDump(deserialized.Dump()));
    }

    public static void SingleJsonFriend1()
    {

        PrintCyan();

        var fileName = "SingleFriend1.json";

        Friend1 friend = new()
        {
            Id = 1,
            FirstName = "John",
            LastName = "Doe",
            BirthDate = new DateOnly(1980, 1, 1),
            CellPhone = "555-555-5555"
        };

        var bytes = MessagePackSerializer.Serialize(friend);
        var json = MessagePackSerializer.ConvertToJson(bytes);

        File.WriteAllText(fileName, json);

        var json1 = File.ReadAllText(fileName);
        var bytes1 = MessagePackSerializer.ConvertFromJson(json1);

        var deserialized = MessagePackSerializer.Deserialize<Friend1>(bytes1);

        AnsiConsole.MarkupLine(BeautifyFriendDump(deserialized.Dump()));
    }
    public static void ListFriends()
    {

        PrintCyan();

        var fileName = "Friends.bin";

        var list = MockedData.Friends();


        // Serialize Friend to byte[]
        var bytes = MessagePackSerializer.Serialize(list, MessagePack.Resolvers.ContractlessStandardResolver.Options);
        File.WriteAllBytes(fileName, bytes);

        var bytes1 = File.ReadAllBytes(fileName);
        var deserialized = MessagePackSerializer.Deserialize<List<Friend>>(bytes1, MessagePack.Resolvers.ContractlessStandardResolver.Options);

        AnsiConsole.MarkupLine(BeautifyFriendDump(deserialized.Dump()));
    }
}
Enter fullscreen mode Exit fullscreen mode

Binary using protobuf-net

protobuf-net is a contract based serializer for .NET code, that happens to write data in the "protocol buffers" serialization format engineered by Google.

The protobuf-net library is easy to use, decorate a class and properties with attributes followed by using Serializer.Serialize to serialize data and Serializer.Deserialize to deserialize data.

Class decelerated to include all properties for serializing data,

[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class Person : IPerson
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public string SSN { get; set; }
    public Address Address { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Sample operations

using BinaryFormatterAlternate.Models;
using static BinaryFormatterAlternate.Classes.SpectreConsoleHelpers;

namespace BinaryFormatterAlternate.Classes;

internal class ProtobufOperations
{
    private static string fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "people.bin");
    public static void SerializePeople()
    {

        List<Person> people = MockedData.GetPersons();

        using var file = File.Create(fileName);
        Serializer.Serialize(file, people);
    }

    public static void DeserializePeople()
    {

        using var file = File.OpenRead(fileName);
        var people = Serializer.Deserialize<List<Person>>(file);

        AnsiConsole.MarkupLine($"{BeautifyPersonDump(people.Dump())}");
    }
}
Enter fullscreen mode Exit fullscreen mode

See also

Summary

Several external and internal libraries have been presented to migrate away from using BinaryFormatter class using basic syntax to allow developers to select a path that fits their needs.

💖 💪 🙅 🚩
karenpayneoregon
Karen Payne

Posted on November 30, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

NET 9 BinaryFormatter migration paths
csharp NET 9 BinaryFormatter migration paths

November 30, 2024