diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c409373 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,124 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +This changelog was reconstructed from the project's commit history and published +NuGet releases; entries for older versions are a best-effort summary. + +## [4.2.0] - 2026-06-16 + +### Added +- Option to pass a custom `ISerializationSurrogateProvider` to the serializers via the new + `DataSerializer.SerializationSurrogateProvider` property, allowing callers to customize + serialization and add support for otherwise unsupported types ([#178]). + +### Notes +- The surrogate provider is honored by the XML serializer only. The JSON serializer + (`DataContractJsonSerializer`) does not apply surrogate providers + ([dotnet/runtime#100553](https://github.com/dotnet/runtime/issues/100553)). +- The property is available on the `net6.0`–`net10.0` and `netstandard2.1` targets; + it is not exposed on `net48`/`net481`/`netstandard2.0`, which lack + `ISerializationSurrogateProvider`. + +## [4.1.0] - 2026-06-16 + +### Added +- Support for .NET 10 (`net10.0`). + +### Changed +- Replaced AppVeyor with GitHub Actions for CI; publishing now uses NuGet trusted + publishing (OIDC) instead of a stored API key. +- Added Dependabot configuration for NuGet updates. +- Documentation updates (README supported platforms, project guidance). + +## [4.0.0] - 2025-01-19 + +### Added +- Support for .NET 9 (`net9.0`). + +## [3.1.0] - 2023-10-21 + +### Added +- Support for .NET 8 (`net8.0`) and .NET Framework 4.8.1 (`net481`). +- `DateOnly` and `TimeOnly` added to the known types. + +### Fixed +- Serialization of unbound `ToString` expressions ([#169]). + +### Removed +- Dropped .NET 5 target (out of support). + +## [3.0.0] - 2023-06-10 + +### Added +- Support for .NET 7 (`net7.0`). +- `netstandard2.0` and `netstandard2.1` targets. +- README is now included in the NuGet package. + +### Changed +- Suppressed `SYSLIB0011` warnings during the transition away from `BinaryFormatter`. + +### Fixed +- Do not explode interface types as nullable types ([#163]). + +### Removed +- Removed `BinaryFormatSerializer` / `BinaryFormatter`-based serialization due to + security concerns. + +## [2.0.0] - 2020-12-12 + +### Added +- Support for .NET 5 (`net5.0`). +- Symbol package (`.snupkg`) support. + +### Changed +- License changed to MIT; removed legacy copyright/license headers. +- Added support for `DefaultExpression` ([#146]). +- `DateTimeOffset` added to the known types ([#107]). + +### Removed +- Removed deprecated target frameworks and example projects. + +## [1.8.1] - 2019-04-25 + +### Fixed +- Follow-up fix for finding the real constant value ([#113]). +- Support for `IndexExpression` serialization ([#119]). + +## [1.8.0] - 2019-04-25 + +### Fixed +- Try harder to find the real constant value ([#113]). + +## [1.7.3] - 2018-10-26 + +### Fixed +- Fix for member access on constant values ([#105]). +- Removed obsolete Silverlight/Windows Phone compiler switches. + +## [1.7.2] - 2018-10-26 + +### Added +- `DateTimeOffset` added as a known type ([#107]). + +## [1.7.1] - 2018-06-02 + +### Fixed +- Updated `Microsoft.Data.OData` reference (security alert). + +## [1.7.0] - 2018-03-20 + +- Baseline release. + +[Unreleased]: https://github.com/esskar/Serialize.Linq/compare/main...HEAD +[#178]: https://github.com/esskar/Serialize.Linq/issues/178 +[#169]: https://github.com/esskar/Serialize.Linq/issues/169 +[#163]: https://github.com/esskar/Serialize.Linq/pull/163 +[#146]: https://github.com/esskar/Serialize.Linq/issues/146 +[#119]: https://github.com/esskar/Serialize.Linq/issues/119 +[#113]: https://github.com/esskar/Serialize.Linq/issues/113 +[#107]: https://github.com/esskar/Serialize.Linq/issues/107 +[#105]: https://github.com/esskar/Serialize.Linq/issues/105 diff --git a/src/Serialize.Linq.Tests/Issues/Issue178.cs b/src/Serialize.Linq.Tests/Issues/Issue178.cs new file mode 100644 index 0000000..cb6f90a --- /dev/null +++ b/src/Serialize.Linq.Tests/Issues/Issue178.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq.Expressions; +using System.Runtime.Serialization; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Serialize.Linq.Serializers; + +namespace Serialize.Linq.Tests.Issues +{ + /// + /// https://github.com/esskar/Serialize.Linq/issues/178 + /// Adds the ability to pass an to the underlying + /// data contract serializer so callers can customize serialization / add support for unknown types. + /// + /// Note: the JSON serializer does not honor the surrogate provider on all runtimes + /// (see https://github.com/dotnet/runtime/issues/100553), so these tests exercise the XML serializer. + /// + [TestClass] + public class Issue178 + { + /// + /// A surrogate provider that does not substitute anything but records that it was queried, + /// proving the provider is actually wired into the data contract serializer. + /// + private sealed class RecordingSurrogateProvider : ISerializationSurrogateProvider + { + public int GetSurrogateTypeCalls { get; private set; } + + public Type GetSurrogateType(Type type) + { + GetSurrogateTypeCalls++; + return type; + } + + public object GetObjectToSerialize(object obj, Type targetType) => obj; + + public object GetDeserializedObject(object obj, Type targetType) => obj; + } + + [TestMethod] + public void SurrogateProviderIsInvokedDuringXmlRoundTrip() + { + var provider = new RecordingSurrogateProvider(); + var xmlSerializer = new XmlSerializer + { + SerializationSurrogateProvider = provider + }; + var serializer = new ExpressionSerializer(xmlSerializer); + + Expression> expression = x => x > 5; + + var text = serializer.SerializeText(expression); + var actual = (Expression>)serializer.DeserializeText(text); + + Assert.IsTrue(provider.GetSurrogateTypeCalls > 0, "surrogate provider was not invoked."); + + var func = actual.Compile(); + Assert.IsTrue(func(6)); + Assert.IsFalse(func(4)); + } + + [TestMethod] + public void NoSurrogateProviderStillRoundTrips() + { + var serializer = new ExpressionSerializer(new XmlSerializer()); + + Expression> expression = x => x > 5; + + var text = serializer.SerializeText(expression); + var actual = (Expression>)serializer.DeserializeText(text); + + var func = actual.Compile(); + Assert.IsTrue(func(6)); + Assert.IsFalse(func(4)); + } + } +} diff --git a/src/Serialize.Linq/Serialize.Linq.csproj b/src/Serialize.Linq/Serialize.Linq.csproj index 1d0f076..5361524 100644 --- a/src/Serialize.Linq/Serialize.Linq.csproj +++ b/src/Serialize.Linq/Serialize.Linq.csproj @@ -29,9 +29,9 @@ net48;net481;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1 - 4.1.0 - 4.1.0.0 - 4.1.0.0 + 4.2.0 + 4.2.0.0 + 4.2.0.0 LICENSE diff --git a/src/Serialize.Linq/Serializers/DataSerializer.cs b/src/Serialize.Linq/Serializers/DataSerializer.cs index e4d1de2..1766eda 100644 --- a/src/Serialize.Linq/Serializers/DataSerializer.cs +++ b/src/Serialize.Linq/Serializers/DataSerializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Serialize.Linq.Nodes; using System.Runtime.Serialization; @@ -8,12 +8,29 @@ namespace Serialize.Linq.Serializers { public abstract class DataSerializer : SerializerBase, ISerializer { +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + /// + /// Gets or sets a custom that is attached to the + /// underlying data contract serializer. A surrogate provider lets callers substitute types during + /// serialization and deserialization, for example to add support for types that the data contract + /// serializer cannot handle on its own. + /// + /// + /// This property is not available on the .NET Framework / netstandard2.0 targets, which do not + /// expose . + /// Note that the JSON serializer does not honor the surrogate provider on all runtimes + /// (see https://github.com/dotnet/runtime/issues/100553); prefer the XML serializer when a + /// surrogate provider is required. + /// + public ISerializationSurrogateProvider SerializationSurrogateProvider { get; set; } +#endif + public virtual void Serialize(Stream stream, T obj) where T : Node { if (stream == null) throw new ArgumentNullException(nameof(stream)); - var serializer = CreateXmlObjectSerializer(typeof(T)); + var serializer = CreateSerializer(typeof(T)); serializer.WriteObject(stream, obj); } @@ -22,10 +39,22 @@ public virtual T Deserialize(Stream stream) where T : Node if (stream == null) throw new ArgumentNullException(nameof(stream)); - var serializer = CreateXmlObjectSerializer(typeof(T)); + var serializer = CreateSerializer(typeof(T)); return (T)serializer.ReadObject(stream); } + private XmlObjectSerializer CreateSerializer(Type type) + { + var serializer = CreateXmlObjectSerializer(type); +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + // SetSerializationSurrogateProvider is only available on DataContractSerializer (XML). + // DataContractJsonSerializer does not honor a surrogate provider (dotnet/runtime#100553). + if (SerializationSurrogateProvider != null && serializer is DataContractSerializer dataContractSerializer) + dataContractSerializer.SetSerializationSurrogateProvider(SerializationSurrogateProvider); +#endif + return serializer; + } + protected abstract XmlObjectSerializer CreateXmlObjectSerializer(Type type); } }