Skip to content

Commit

Permalink
BinaryFormatter Migration Guide (#41564)
Browse files Browse the repository at this point in the history
Add BinaryFormatter migration guide

Co-authored-by: Jeff Handley <[email protected]>
Co-authored-by: Jeremy Barton <[email protected]>
Co-authored-by: Genevieve Warren <[email protected]>
Co-authored-by: Loni Tra <[email protected]>
Co-authored-by: Andrew Arnott <[email protected]>
Co-authored-by: Marc Gravell <[email protected]>
Co-authored-by: Rishabh Chauhan <[email protected]>
Co-authored-by: Immo Landwerth <[email protected]>
Co-authored-by: Eirik Tsarpalis <[email protected]>
Co-authored-by: Tanya Solyanik <[email protected]>
  • Loading branch information
11 people authored Aug 8, 2024
1 parent 559b0d5 commit 3f63aab
Show file tree
Hide file tree
Showing 15 changed files with 931 additions and 3 deletions.
26 changes: 26 additions & 0 deletions docs/fundamentals/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,32 @@ items:
href: ../standard/serialization/xml-schema-definition-tool-xsd-exe.md
- name: Binary serialization
items:
- name: BinaryFormatter migration guide
items:
- name: Overview
href: ../standard/serialization/binaryformatter-migration-guide/index.md
- name: Choose a serializer
href: ../standard/serialization/binaryformatter-migration-guide/choose-a-serializer.md
- name: Migrate to System.Text.Json (JSON)
href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-system-text-json.md
- name: Migrate to DataContractSerializer (XML)
href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-datacontractserializer.md
- name: Migrate to MessagePack (binary)
href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-messagepack.md
- name: Migrate to protobuf-net (binary)
href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-protobuf-net.md
- name: Read BinaryFormatter (NRBF) payloads
href: ../standard/serialization/binaryformatter-migration-guide/read-nrbf-payloads.md
- name: Compatibility package
href: ../standard/serialization/binaryformatter-migration-guide/compatibility-package.md
- name: BinaryFormatter functionality reference
href: ../standard/serialization/binaryformatter-migration-guide/functionality-reference.md
- name: Windows Forms apps
href: ../standard/serialization/binaryformatter-migration-guide/winforms-applications.md
- name: WPF apps
href: ../standard/serialization/binaryformatter-migration-guide/wpf-applications.md
- name: Clipboard and drag-and-drop guidance
href: ../standard/serialization/binaryformatter-migration-guide/winforms-wpf-ole-guidance.md
- name: BinaryFormatter security guide
href: ../standard/serialization/binaryformatter-security-guide.md
- name: BinaryFormatter event source
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
title: "BinaryFormatter migration guide: Choose a serializer"
description: "Compare the capabilities and trade-offs of serializers to choose a replacement for BinaryFormatter."
ms.date: 5/31/2024
no-loc: [BinaryFormatter, Serialization]
helpviewer_keywords:
- "BinaryFormatter"
- "serializing objects"
- "serialization"
- "objects, serializing"
---

# Choose a serializer

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.

If a binary serialization format is not a requirement, you can consider using JSON or XML serialization formats. These serializers are included in .NET and are officially supported.

1. JSON using [System.Text.Json](./migrate-to-system-text-json.md)
2. XML using [`System.Runtime.Serialization.DataContractSerializer`](./migrate-to-datacontractserializer.md)

If a compact binary representation is important for your scenarios, the following serialization formats and open-source serializers are recommended:

1. [MessagePack](https://msgpack.org/) using [MessagePack for C#](./migrate-to-messagepack.md)
2. [Protocol Buffers](https://protobuf.dev/) using [protobuf-net](./migrate-to-protobuf-net.md)

Whether you have control to change the API shape of the serialized type will influence your direction and approach to serialization. Migration to these serializers may be more straightforward with the ability to annotate types with new attributes, add new constructors, make types/members public, and change fields to properties. Without that ability, using modern serializers might require implementation of custom converters or resolvers.

| Feature | BinaryFormatter | System.Text.Json | DataContractSerializer | MessagePack for C# | protobuf-net |
|------------------------------------------------|------------------|-------------------------|------------------------|--------------------------|-----------------------------------|
| Serialization format | binary (NRBF) | JSON | XML | binary (MessagePack) | binary (Protocol Buffers) |
| Compact representation | ✔️ ||| ✔️ | ✔️ |
| Human-readable | ❌️ | ✔️ | ✔️ | ❌️ | ❌️ |
| Performance | ❌️ | ✔️ || ✔️ | ✔️ |
| `[Serializable]` attribute support | ✔️ || ✔️ |||
| Serializing public types | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Serializing non-public types | ✔️ | ✔️ | ✔️ | ✔️ (resolver required) | ✔️ |
| Serializing fields | ✔️ | ✔️ (opt in) | ✔️ | ✔️ (attribute required) | ✔️ (attribute required) |
| Serializing non-public fields | ✔️ | ✔️ (resolver required) | ✔️ | ✔️ (resolver required) | ✔️ (attribute required) |
| Serializing properties | ✔️<sup>*</sup> | ✔️ | ✔️ | ✔️ (attribute required) | ✔️ (attribute required) |
| Deserializing readonly members | ✔️ | ✔️ (attribute required) | ✔️ | ✔️ | ✔️ (parameterless ctor required) |
| Polymorphic type hierarchy | ✔️ | ✔️ (attribute required) | ✔️ | ✔️ (attribute required) | ✔️ (attribute required) |
| AOT support | ❌️ | ✔️ || ✔️ | ❌ (planned) |

## JSON using System.Text.Json

The [`System.Text.Json`](../system-text-json/overview.md) library is a modern serializer that emphasizes security, high performance, and low memory allocation for the JavaScript Object Notation (JSON) format. JSON is human-readable and has broad cross-platform support. While text-based format is not as compact as binary formats, it can be significantly reduced in size through compression.

Serialization excludes non-public and readonly members unless specifically handled through attributes and constructors. System.Text.Json also supports [custom serialization and deserialization](../system-text-json/custom-contracts.md) for more control over how types are converted into JSON and vice versa. System.Text.Json does not support the `[Serializable]` attribute.

[Migrate to System.Text.Json (JSON)](./migrate-to-system-text-json.md).

## XML using DataContractSerializer

[`System.Runtime.Serialization.DataContractSerializer`](/dotnet/api/system.runtime.serialization.datacontractserializer) was introduced in .NET Framework 3.0 and is used to serialize and deserialize data sent in Windows Communication Foundation (WCF) messages. `DataContractSerializer` is an XML serializer that **fully supports the serialization programming model that was used by the `BinaryFormatter`**, which means it honors the `[Serializable]` attribute and implementation of `ISerializable`. Hence, it's the serializer that requires the least amount of effort to migrate to. It does, however, require the known types to be specified up-front (but most .NET collections and primitive types are on a default allow-list and don't need to be specified).

While `DataContractSerializer` carries those functional benefits when migrating from BinaryFormatter, it is not as modern or performant as the other choices.

[Migrate to DataContractSerializer (XML)](./migrate-to-datacontractserializer.md).

> [!NOTE]
> Do not confuse `DataContractSerializer` with [`NetDataContractSerializer`](/dotnet/api/system.runtime.serialization.netdatacontractserializer). `NetDataContractSerializer` is also identified as a [dangerous serializer](../binaryformatter-security-guide.md#dangerous-alternatives).
## Binary using MessagePack

[MessagePack](https://msgpack.org/) is a compact binary serialization format, resulting in smaller message sizes compared to JSON and XML. The open source [MessagePack for C#](https://github.com/MessagePack-CSharp/MessagePack-CSharp) library is highly performant and offers built-in super-fast LZ4 compression for an even smaller data size. It works best when data types are annotated with either `DataContractSerializer` or the library's own attributes. It can be configured to support AOT environments, non-public types and members, and read-only types and members.

[Migrate to MessagePack (binary)](./migrate-to-messagepack.md).

## Binary using protobuf-net

The [protobuf-net](https://github.com/protobuf-net/protobuf-net) library is a contract-based serializer for .NET that uses the binary [Protocol Buffers](https://protobuf.dev) serialization format. The API follows typical .NET patterns and is broadly comparable to `XmlSerializer` and `DataContractSerializer`. This popular library is also feature-rich and can handle non-public types and fields, but many scenarios do require applying attributes to members.

[Migrate to protobuf-net (binary)](./migrate-to-protobuf-net.md).
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: "BinaryFormatter migration guide: Compatibility Package"
description: "Using the BinaryFormatter compatibility package."
ms.date: 5/31/2024
no-loc: [BinaryFormatter, Serialization]
helpviewer_keywords:
- "BinaryFormatter"
- "serializing objects"
- "serialization"
- "objects, serializing"
---

# BinaryFormatter compatibility package

> [!CAUTION]
> The compatibility package is not supported and unsafe. We strongly recommend against taking a dependency on this package and instead [migrate away from BinaryFormatter](./index.md#migration-topics).
.NET 9+ users who can't migrate away from `BinaryFormatter` can install the unsupported [System.Runtime.Serialization.Formatters](https://www.nuget.org/packages/System.Runtime.Serialization.Formatters) NuGet package and set the `System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization` AppContext switch to `true`.

> [!NOTE]
> Please note that this package doesn't change the type identity of BinaryFormatter. Existing libraries don't need to be updated to depend on this package in order to use it. The only place that needs to depend on this package is the application project.
The package replaces the in-box implementation of BinaryFormatter with a functioning one, including its vulnerabilities and risks. It's meant as a stop gap if you can't wait with migrating to .NET 9 and later while not having replaced the usages of BinaryFormatter yet. We still strongly recommend you [migrate away from BinaryFormatter](./index.md#migration-topics).

```xml
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="9.0.0-*" />
</ItemGroup>
```

> [!CAUTION]
> The compatibility package is not supported and unsafe. We strongly recommend against taking a dependency on this package and instead [migrate away from BinaryFormatter](./index.md#migration-topics).
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title: "BinaryFormatter migration guide: Functionality reference"
description: "A reference for BinaryFormatter's functionality that may need to be considered during migrations."
ms.date: 5/31/2024
no-loc: [BinaryFormatter, Serialization, WinForms]
dev_langs:
- CSharp
helpviewer_keywords:
- "BinaryFormatter"
- "serializing objects"
- "serialization"
- "objects, serializing"
---

# BinaryFormatter functionality reference

The `BinaryFormatter` was first introduced with the initial release of .NET Framework in 2002. To understand how to replace usage of BinaryFormatter, it helps to know how BinaryFormatter works.

`BinaryFormatter` can serialize any instance of any type that's annotated with `[Serializable]` or implements the `ISerializable` interface.

## Member names

In most common scenario, the type is annotated with `[Serializable]` and the serializer uses reflection to serialize **all fields** (both public and non-public) except those that are annotated with `[NonSerialized]`. By default, the serialized member names will match the type's field names. This historically led to incompatibilities when even private fields are renamed on `[Serializable]` types. During migrations away from BinaryFormatter, it becomes necessary to understand how serialized field names were handled and overridden.

### C# auto properties

For C# [auto-implemented properties](/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties) (`{ get; set; }`), BinaryFormatter will serialize the backing fields that are generated by the C# compiler, not the properties. The names of those serialized backing fields contain illegal C# characters and cannot be controlled. A C# decompiler (such as [https://sharplab.io/](https://sharplab.io/) or [ILSpy](https://github.com/icsharpcode/ILSpy)) can demonstrate how C# auto properties are presented to the runtime.

```csharp
[Serializable]
internal class PropertySample
{
public string Name { get; set; }
}
```

The previous class is translated by the C# compiler to:

```csharp
[Serializable]
internal class PropertySample
{
private string <Name>k__BackingField;

public string Name
{
get
{
return <Name>k__BackingField;
}
set
{
<Name>k__BackingField = value;
}
}
}
```

In this case, `<Name>k__BackingField` is **the name of the member that `BinaryFormatter` uses in the serialized payload**. It's not possible to use `nameof` or any other C# operator to get this name.

The [ISerializable](/dotnet/api/system.runtime.serialization.iserializable) interface comes with [GetObjectData(SerializationInfo info, StreamingContext context)](/dotnet/api/system.runtime.serialization.iserializable.getobjectdata) method that allows the users to control the names, by using one of the [SerializationInfo.AddValue](/dotnet/api/system.runtime.serialization.serializationinfo.addvalue) methods.

```csharp
// Note lack of any special attribute.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Name", this.Name);
}
```

If such customization has been applied, the information needs to be provided during deserialization as well. That's possible by using the **serialization constructor**, where all values are read from `SerializationInfo` with one of the `Get` methods it provides.

```csharp
private PropertySample(SerializationInfo info, StreamingContext context)
{
this.Name = info.GetString("Name");
}
```

> [!NOTE]
> The `nameof` operator was purposely not used here, as the payload can be persisted and the property can get renamed at a later time. So even if it gets renamed (say to `FirstName` because you decide to also introduce a `LastName` property), to remain backward compatible, the serialization should still use the old name that could have been persisted somewhere.
## Serialization binder

It's recommended to use [SerializationBinder](/dotnet/api/system.runtime.serialization.serializationbinder) to control class loading and mandate what class to load. That minimizes security vulnerabilities (so only allowed types get loaded, even if the attacker modifies the payload to deserialize and load something else).

Using this type requires inheriting from it and overriding the [Type BindToType(string assemblyName, string typeName)](/dotnet/api/system.runtime.serialization.serializationbinder.bindtotype#system-runtime-serialization-serializationbinder-bindtotype(system-string-system-string)) method.

Ideally the list of serializable types is closed set because it means you know which types can be instantiated which will help reduce security vulnerabilities.
Loading

0 comments on commit 3f63aab

Please sign in to comment.