-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BinaryFormatter Migration Guide (#41564)
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
1 parent
559b0d5
commit 3f63aab
Showing
15 changed files
with
931 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
docs/standard/serialization/binaryformatter-migration-guide/choose-a-serializer.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
37 changes: 37 additions & 0 deletions
37
...standard/serialization/binaryformatter-migration-guide/compatibility-package.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
89 changes: 89 additions & 0 deletions
89
...andard/serialization/binaryformatter-migration-guide/functionality-reference.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.