Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add netstandard2.0 target #148

Merged
merged 2 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
os:
- ubuntu-22.04
#- macos-14
#- windows-2022
- windows-2022

steps:
- uses: actions/checkout@v4
Expand Down
8 changes: 8 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)'!='.NETCoreApp'">
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(IsAnalyzerProject)'=='true'">
<PackageVersion Update="System.Collections.Immutable" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<GlobalPackageReference Include="CSharpIsNullAnalyzer" Version="0.1.593" />
<GlobalPackageReference Include="DotNetAnalyzers.DocumentationAnalyzers" Version="1.0.0-beta.59" />
Expand Down
29 changes: 26 additions & 3 deletions docfx/docs/custom-converters.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,43 @@ Applications that have a legitimate need to exceed the default stack depth limit

The @Nerdbank.MessagePack.SerializationContext.GetConverter*?displayProperty=nameWithType method may be used to obtain a converter to use for members of the type your converter is serializing or deserializing.

[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValues)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValuesNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValuesNETFX)]

---

The above assumes that `SomeOtherType` is a type that you declare and can have @PolyType.GenerateShapeAttribute`1 applied to it.
If this is not the case, you may provide your own type shape and reference that.
For convenience, you may want to apply it directly to your custom converter:

[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatter)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatterNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatterNETFX)]

---

The @PolyType.GenerateShapeAttribute`1 is what enables `FooConverter` to be a "provider" for the shape of `SomeOtherType`.

Arrays of a type require a shape of their own.
So even if you define your type `MyType` with @PolyType.GenerateShapeAttribute`1, serializing `MyType[]` would require a witness type and attribute. For example:

[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatter)]

# [.NET](#tab/net)

[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatterNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatterNETFX)]

### Version compatibility

Expand Down
10 changes: 9 additions & 1 deletion docfx/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ Given a type annotated with [`GenerateShapeAttribute`](xref:PolyType.GenerateSha

You can serialize and deserialize it like this:

[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtrip)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtripNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtripNETFX)]

---

Only the top-level types that you serialize need the attribute.
All types that they reference will automatically have their 'shape' source generated as well so the whole object graph can be serialized.
Expand Down
2 changes: 1 addition & 1 deletion docfx/docs/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Reference preservation | [✅](xref:Nerdbank.MessagePack.MessagePackSerialize
JSON schema export | [✅](xref:Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema*) | ❌ |
Secure defaults | ✅ | ❌ |
Automatic hash collection deserialization in secure mode | ❌ | ✅ |
Automatic collision-resistant hash function for arbitrary types | [✅](xref:Nerdbank.MessagePack.ByValueEqualityComparer`1) | ❌ |
Automatic collision-resistant hash function for arbitrary types | [✅](xref:Nerdbank.MessagePack.ByValueEqualityComparer) | ❌ |
Free of mutable statics | ✅ | ❌ |

Security is a complex subject, and an area where Nerdbank.MessagePack is actively evolving.
Expand Down
14 changes: 11 additions & 3 deletions docfx/docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,20 @@ Instead, you can provide your own defense by initializing your collections with

Here is an example of a defense against hash collisions:

[!code-csharp[](../../samples/Security.cs#SecureEqualityComparers)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/Security.cs#SecureEqualityComparersNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/Security.cs#SecureEqualityComparersNETFX)]

---

Note how the collection properties do *not* define a property setter.
This is crucial to the threat mitigation, since it activates the deserializer behavior of not recreating the collection using the default (insecure) equality comparer.

In this example, we use @Nerdbank.MessagePack.ByValueEqualityComparer`1.HashResistant?displayProperty=nameWithType, which provides a collision resistant implementation of @System.Collections.Generic.IEqualityComparer`1.
In this example, we use @Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant*?displayProperty=nameWithType, which provides a collision resistant implementation of @System.Collections.Generic.IEqualityComparer`1.
This implementation uses the SIP hash algorithm, which is known for its high performance and collision resistance.
While it will function for virtually any data type, its behavior is not correct in all cases and you may need to implement your own secure hash function.
Please review the documentation for @Nerdbank.MessagePack.ByValueEqualityComparer`1.HashResistant for more information.
Please review the documentation for @Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant* for more information.
10 changes: 9 additions & 1 deletion docfx/docs/type-shapes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ Doing so leads to default serialization rules being applied to the type (e.g. on
For this example, suppose you consume a `FamilyTree` type from a library that you don't control and did not annotate their type for serialization.
In your own project, you can define this witness type and use it to serialize an external type.

[!code-csharp[](../../samples/TypeShapePatterns.cs#Witness)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/TypeShapePatterns.cs#WitnessNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/TypeShapePatterns.cs#WitnessNETFX)]

---

Note the only special bit is providing the `Witness` class as a type argument to the `Serialize` method.
The _name_ of the witness class is completely inconsequential.
Expand Down
30 changes: 27 additions & 3 deletions docfx/docs/unions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ For instance, suppose you have this type to serialize:
But there are many kinds of animals.
You can get them to serialize and deserialize correctly like this:

[!code-csharp[](../../samples/Unions.cs#FarmAnimals)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/Unions.cs#FarmAnimalsNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/Unions.cs#FarmAnimalsNETFX)]

---

This changes the schema of the serialized data to include a tag that indicates the type of the object.

Expand Down Expand Up @@ -50,7 +58,15 @@ This is because the `Horse` type is statically known as the generic type argumen

Now suppose you have different breeds of horses that each had their own subtype:

[!code-csharp[](../../samples/Unions.cs#HorseBreeds)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/Unions.cs#HorseBreedsNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/Unions.cs#HorseBreedsNETFX)]

---

At this point your `HorsePen` *would* serialize with the union schema around each horse:
```json
Expand All @@ -73,4 +89,12 @@ This witness type must be specified as a second type argument to @Nerdbank.Messa

For example:

[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypes)]
# [.NET](#tab/net)

[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypesNET)]

# [.NET Standard](#tab/netfx)

[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypesNETFX)]

---
2 changes: 2 additions & 0 deletions samples/.editorconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[*.cs]

indent_style = space

# SA1123: Do not place regions within elements
dotnet_diagnostic.SA1123.severity = none

Expand Down
28 changes: 12 additions & 16 deletions samples/ApplyingSerializationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@

partial class ApplyingSerializationContext
{
void ApplyingContext()
{
#region ApplyingStartingContext
MessagePackSerializer serializer = new()
{
StartingContext = new SerializationContext
{
MaxDepth = 128,
},
};
serializer.Serialize(new SomeType());
#endregion
}

[GenerateShape]
internal partial record SomeType;
void ApplyingContext()
{
#region ApplyingStartingContext
MessagePackSerializer serializer = new()
{
StartingContext = new SerializationContext
{
MaxDepth = 128,
},
};
#endregion
}
}
Loading