From e792625d9490927d13a88a9a5ee6360bdc680d75 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Tue, 3 Mar 2026 11:06:09 +0000 Subject: [PATCH 01/20] Add support for optional NATS headers in serialization/deserialization --- examples/Example.NativeAot/Program.cs | 4 +- src/NATS.Client.Abstractions/INatsHeaders.cs | 10 ++++ .../INatsSerialize.cs | 6 +- .../NATS.Client.Abstractions.csproj | 1 + .../Commands/CommandWriter.cs | 6 +- src/NATS.Client.Core/NatsHeaders.cs | 2 +- src/NATS.Client.Core/NatsMsg.cs | 4 +- src/NATS.Client.Core/NatsSerialize.cs | 24 ++++---- .../Internal/NatsJSJsonDocumentSerializer.cs | 2 +- src/NATS.Client.KeyValueStore/NatsKVStore.cs | 2 +- src/NATS.Client.ObjectStore/NatsObjStore.cs | 2 +- ...atsJsonContextOptionsSerializerRegistry.cs | 4 +- .../NatsJsonSerializer.cs | 4 +- tests/NATS.Client.CheckAbi/Program.cs | 4 +- .../MySerializer.cs | 4 +- tests/NATS.Client.Core.Tests/NatsMsgTests.cs | 2 +- .../NATS.Client.Core2.Tests/SerializerTest.cs | 24 ++++---- .../NatsJsonSerializerTests.cs | 10 ++-- .../CustomSerializerTest.cs | 4 +- .../EnumJsonTests.cs | 60 +++++++++---------- .../JetStreamApiSerializerTest.cs | 6 +- .../ParseJsonTests.cs | 6 +- .../TimeSpanJsonTests.cs | 56 ++++++++--------- .../Advanced/SerializationPage.cs | 4 +- 24 files changed, 132 insertions(+), 119 deletions(-) create mode 100644 src/NATS.Client.Abstractions/INatsHeaders.cs diff --git a/examples/Example.NativeAot/Program.cs b/examples/Example.NativeAot/Program.cs index 0bb978429..c069f8e91 100644 --- a/examples/Example.NativeAot/Program.cs +++ b/examples/Example.NativeAot/Program.cs @@ -201,7 +201,7 @@ public class MyProtoBufSerializer : INatsSerializer { public static readonly INatsSerializer Default = new MyProtoBufSerializer(); - public void Serialize(IBufferWriter bufferWriter, T value) + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { if (value is IMessage message) { @@ -213,7 +213,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) } } - public T? Deserialize(in ReadOnlySequence buffer) + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (typeof(T) == typeof(Greeting)) { diff --git a/src/NATS.Client.Abstractions/INatsHeaders.cs b/src/NATS.Client.Abstractions/INatsHeaders.cs new file mode 100644 index 000000000..4c1cd6c53 --- /dev/null +++ b/src/NATS.Client.Abstractions/INatsHeaders.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Primitives; + +namespace NATS.Client.Core; + +/// +/// Represents NATS message headers as a dictionary of string keys and values. +/// +public interface INatsHeaders : IDictionary +{ +} diff --git a/src/NATS.Client.Abstractions/INatsSerialize.cs b/src/NATS.Client.Abstractions/INatsSerialize.cs index 343f50880..c5b8657f9 100644 --- a/src/NATS.Client.Abstractions/INatsSerialize.cs +++ b/src/NATS.Client.Abstractions/INatsSerialize.cs @@ -27,7 +27,8 @@ public interface INatsSerialize /// /// Buffer to write the serialized data. /// Object to be serialized. - void Serialize(IBufferWriter bufferWriter, T value); + /// Optional NATS headers associated with the message. + void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers); } /// @@ -40,8 +41,9 @@ public interface INatsDeserialize /// Deserialize value from buffer. /// /// Buffer with the serialized data. + /// Optional NATS headers associated with the message. /// Deserialized object - T? Deserialize(in ReadOnlySequence buffer); + T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers); } public interface INatsSerializerRegistry diff --git a/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj b/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj index fa1f67471..a019099cf 100644 --- a/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj +++ b/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj @@ -8,6 +8,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 98b65979c..dcf196c29 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -333,12 +333,12 @@ public ValueTask PublishAsync(string subject, T? value, NatsHeaders? headers, try { + if (value != null) + serializer.Serialize(payloadBuffer, value, headers); + if (headers != null) _headerWriter.Write(headersBuffer!, headers); - if (value != null) - serializer.Serialize(payloadBuffer, value); - var size = payloadBuffer.WrittenMemory.Length + (headersBuffer?.WrittenMemory.Length ?? 0); if (_connection.ServerInfo is { } info && size > info.MaxPayload) { diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 15d09be45..50cbadd66 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -15,7 +15,7 @@ namespace NATS.Client.Core; [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1516:Elements should be separated by blank line", Justification = "Keep class format as is for reference")] [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Keep class format as is for reference")] [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1214:Readonly fields should appear before non-readonly fields", Justification = "Keep class format as is for reference")] -public class NatsHeaders : IDictionary +public class NatsHeaders : INatsHeaders, IDictionary { public enum Messages { diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index 8d7112831..208b97708 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -401,7 +401,7 @@ public static NatsMsg Build( { try { - data = serializer.Deserialize(payloadBuffer); + data = serializer.Deserialize(payloadBuffer, headers); } catch (Exception e) { @@ -519,7 +519,7 @@ public NatsMsg Msg if (Serializer != null && Data != null) { var bufferWriter = new NatsPooledBufferWriter(SerializationBufferSize); - Serializer.Serialize(bufferWriter, Data); + Serializer.Serialize(bufferWriter, Data, null); size = bufferWriter.WrittenMemory.Length; } diff --git a/src/NATS.Client.Core/NatsSerialize.cs b/src/NATS.Client.Core/NatsSerialize.cs index 3f889935b..f0b59be12 100644 --- a/src/NATS.Client.Core/NatsSerialize.cs +++ b/src/NATS.Client.Core/NatsSerialize.cs @@ -53,7 +53,7 @@ public class NatsUtf8PrimitivesSerializer : INatsSerializer public INatsSerializer CombineWith(INatsSerializer? next) => new NatsUtf8PrimitivesSerializer(next); /// - public void Serialize(IBufferWriter bufferWriter, T value) + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { if (value is string str) { @@ -335,11 +335,11 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}"); } - _next.Serialize(bufferWriter, value); + _next.Serialize(bufferWriter, value, headers); } /// - public T? Deserialize(in ReadOnlySequence buffer) + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (typeof(T) == typeof(string)) { @@ -550,7 +550,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't deserialize {typeof(T)}"); } - return _next.Deserialize(buffer); + return _next.Deserialize(buffer, headers); } } @@ -573,7 +573,7 @@ public class NatsRawSerializer : INatsSerializer public INatsSerializer CombineWith(INatsSerializer? next) => new NatsRawSerializer(next); /// - public void Serialize(IBufferWriter bufferWriter, T value) + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { if (value is byte[] bytes) { @@ -630,11 +630,11 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}"); } - _next.Serialize(bufferWriter, value); + _next.Serialize(bufferWriter, value, headers); } /// - public T? Deserialize(in ReadOnlySequence buffer) + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (typeof(T) == typeof(byte[])) { @@ -678,7 +678,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't deserialize {typeof(T)}"); } - return _next.Deserialize(buffer); + return _next.Deserialize(buffer, headers); } } @@ -728,7 +728,7 @@ public NatsJsonContextSerializer(JsonSerializerContext context, INatsSerializer< public INatsSerializer CombineWith(INatsSerializer next) => new NatsJsonContextSerializer(_contexts, next); /// - public void Serialize(IBufferWriter bufferWriter, T value) + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { foreach (var context in _contexts) { @@ -757,11 +757,11 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}"); } - _next.Serialize(bufferWriter, value); + _next.Serialize(bufferWriter, value, headers); } /// - public T? Deserialize(in ReadOnlySequence buffer) + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (buffer.Length == 0) { @@ -778,7 +778,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) } if (_next != null) - return _next.Deserialize(buffer); + return _next.Deserialize(buffer, headers); throw new NatsException($"Can't deserialize {typeof(T)}"); } diff --git a/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs b/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs index c022b47a0..49a66c2c3 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs @@ -8,7 +8,7 @@ internal sealed class NatsJSJsonDocumentSerializer : INatsDeserialize Default = new(); - public NatsJSApiResult Deserialize(in ReadOnlySequence buffer) + public NatsJSApiResult Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (buffer.Length == 0) { diff --git a/src/NATS.Client.KeyValueStore/NatsKVStore.cs b/src/NATS.Client.KeyValueStore/NatsKVStore.cs index 260f9914c..96a1485ad 100644 --- a/src/NATS.Client.KeyValueStore/NatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/NatsKVStore.cs @@ -400,7 +400,7 @@ public async ValueTask>> TryGetEntryAsync(string ke try { - data = serializer.Deserialize(buffer); + data = serializer.Deserialize(buffer, null); } catch (Exception e) { diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index befdca53c..783be6eea 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -473,7 +473,7 @@ public async ValueTask GetInfoAsync(string key, bool showDeleted } var buffer = new ReadOnlySequence(response.Message.Data); - var data = NatsObjJsonSerializer.Default.Deserialize(buffer) ?? throw new NatsObjException("Can't deserialize object metadata"); + var data = NatsObjJsonSerializer.Default.Deserialize(buffer, null) ?? throw new NatsObjException("Can't deserialize object metadata"); data.MTime = response.Message.Time; diff --git a/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs b/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs index c205a9213..c43263a6e 100644 --- a/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs +++ b/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs @@ -20,7 +20,7 @@ public class NatsJsonOptionsSerializer(JsonSerializerOptions options) : INats [ThreadStatic] private static Utf8JsonWriter? jsonWriter; - public void Serialize(IBufferWriter bufferWriter, T value) + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { Utf8JsonWriter writer; if (jsonWriter == null) @@ -38,7 +38,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) writer.Reset(NullBufferWriter.Instance); } - public T? Deserialize(in ReadOnlySequence buffer) + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (buffer.Length == 0) { diff --git a/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs b/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs index 376318993..f2d1fa1c7 100644 --- a/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs +++ b/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs @@ -52,7 +52,7 @@ public NatsJsonSerializer() public INatsSerializer CombineWith(INatsSerializer next) => throw new NotSupportedException(); /// - public void Serialize(IBufferWriter bufferWriter, T? value) + public void Serialize(IBufferWriter bufferWriter, T? value, INatsHeaders? headers) { Utf8JsonWriter writer; if (_jsonWriter == null) @@ -71,7 +71,7 @@ public void Serialize(IBufferWriter bufferWriter, T? value) } /// - public T? Deserialize(in ReadOnlySequence buffer) + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (buffer.Length == 0) { diff --git a/tests/NATS.Client.CheckAbi/Program.cs b/tests/NATS.Client.CheckAbi/Program.cs index 054a1c184..bdd615633 100644 --- a/tests/NATS.Client.CheckAbi/Program.cs +++ b/tests/NATS.Client.CheckAbi/Program.cs @@ -27,10 +27,10 @@ Console.WriteLine("Testing TransientLib.MySerializer (compiled against 2.6.0):"); var serializer = new MySerializer(); var buffer = new ArrayBufferWriter(); -serializer.Serialize(buffer, "hello from transient dependency"); +serializer.Serialize(buffer, "hello from transient dependency", null); Console.WriteLine($" Serialized to {buffer.WrittenCount} bytes"); -var deserialized = serializer.Deserialize(new ReadOnlySequence(buffer.WrittenSpan.ToArray())); +var deserialized = serializer.Deserialize(new ReadOnlySequence(buffer.WrittenSpan.ToArray()), null); Console.WriteLine($" Deserialized: '{deserialized}'"); Console.WriteLine(); diff --git a/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs b/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs index 0d1e60648..3f699c02b 100644 --- a/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs +++ b/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs @@ -14,13 +14,13 @@ public static string GetSerializerInterfaceAssembly() return typeof(INatsSerialize<>).Assembly.GetName().Name!; } - public void Serialize(IBufferWriter bufferWriter, string value) + public void Serialize(IBufferWriter bufferWriter, string value, INatsHeaders? headers) { var bytes = System.Text.Encoding.UTF8.GetBytes(value); bufferWriter.Write(bytes); } - public string? Deserialize(in ReadOnlySequence buffer) + public string? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { return System.Text.Encoding.UTF8.GetString(buffer.ToArray()); } diff --git a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs index 0e9d4905e..92fcb38a5 100644 --- a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs +++ b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs @@ -76,7 +76,7 @@ public void Create_nats_msg_by_object() var msg = builder.Msg; var bufferWriter = new NatsPooledBufferWriter(256); - serializer.Serialize(bufferWriter, data); + serializer.Serialize(bufferWriter, data, null); var serializedSize = bufferWriter.WrittenCount; var expectedSize = subject.Length + (replyTo?.Length ?? 0) + headers.GetBytesLength() + serializedSize; diff --git a/tests/NATS.Client.Core2.Tests/SerializerTest.cs b/tests/NATS.Client.Core2.Tests/SerializerTest.cs index fbd987e79..4a1e362e6 100644 --- a/tests/NATS.Client.Core2.Tests/SerializerTest.cs +++ b/tests/NATS.Client.Core2.Tests/SerializerTest.cs @@ -107,7 +107,7 @@ void SerializeDeserialize(T actual, string expected) void Serialize(INatsSerialize serializer, T value, string expected) { var buffer = new NatsBufferWriter(); - serializer.Serialize(buffer, value); + serializer.Serialize(buffer, value, null); var actual = Encoding.UTF8.GetString(buffer.WrittenMemory.Span.ToArray()); Assert.Equal(expected, actual); } @@ -115,7 +115,7 @@ void Serialize(INatsSerialize serializer, T value, string expected) void Deserialize(INatsDeserialize serializer, string input, T expected) { var buffer = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); - var actual = serializer.Deserialize(buffer); + var actual = serializer.Deserialize(buffer, null); Assert.Equal(expected, actual); } } @@ -145,14 +145,14 @@ public void Utf8_deserialize_empty() // Test chaining var testDataSerializer = new NatsUtf8PrimitivesSerializer(new TestSerializer()); - Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer)); + Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer, null)); return; void Deserialize() { var serializer = new NatsUtf8PrimitivesSerializer(new TestSerializer()); - var actual = serializer.Deserialize(emptyBuffer); + var actual = serializer.Deserialize(emptyBuffer, null); Assert.Equal(actual, default); } } @@ -195,7 +195,7 @@ void SerializeDeserialize(byte[] inputBuffer, Func input, Func(INatsSerialize serializer, byte[] inputBuffer, Func input) { var buffer = new NatsBufferWriter(); - serializer.Serialize(buffer, input(inputBuffer)); + serializer.Serialize(buffer, input(inputBuffer), null); var actual = buffer.WrittenMemory.ToArray(); AssertByteArray(inputBuffer, actual); } @@ -203,7 +203,7 @@ void Serialize(INatsSerialize serializer, byte[] inputBuffer, Func(INatsDeserialize serializer, byte[] inputBuffer, Func output) { var buffer = new ReadOnlySequence(inputBuffer); - var actual = serializer.Deserialize(buffer); + var actual = serializer.Deserialize(buffer, null); Assert.True(actual is { }); AssertByteArray(inputBuffer, output(actual)); } @@ -222,14 +222,14 @@ public void Raw_deserialize_empty() // Test chaining var testDataSerializer = new NatsRawSerializer(new TestSerializer()); - Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer)); + Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer, null)); return; void Deserialize() { var serializer = new NatsRawSerializer(new TestSerializer()); - var actual = serializer.Deserialize(emptyBuffer); + var actual = serializer.Deserialize(emptyBuffer, null); Assert.Equal(actual, default); } } @@ -343,9 +343,9 @@ public class TestSerializerRegistry : INatsSerializerRegistry public class TestSerializer : INatsSerializer { - public void Serialize(IBufferWriter bufferWriter, T? value) => throw new TestSerializerException(); + public void Serialize(IBufferWriter bufferWriter, T? value, INatsHeaders? headers) => throw new TestSerializerException(); - public T? Deserialize(in ReadOnlySequence buffer) => throw new TestSerializerException(); + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => throw new TestSerializerException(); public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); } @@ -354,11 +354,11 @@ public class TestSerializerException : Exception; public class TestSerializerWithEmpty : INatsSerializer { - public T? Deserialize(in ReadOnlySequence buffer) => (T)(object)(buffer.IsEmpty + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => (T)(object)(buffer.IsEmpty ? new TestData("__EMPTY__") : new TestData(Encoding.ASCII.GetString(buffer.ToArray()))); - public void Serialize(IBufferWriter bufferWriter, T value) => throw new Exception("not used"); + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) => throw new Exception("not used"); public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); } diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs b/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs index b1d6d9454..4aa9291b5 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs @@ -24,11 +24,11 @@ public void RoundTrip_RequiredNullableProperty_ShouldSucceed() var bufferWriter = new ArrayBufferWriter(); // Act - Serialize - serializer.Serialize(bufferWriter, obj); + serializer.Serialize(bufferWriter, obj, null); // Deserialize back var buffer = new ReadOnlySequence(bufferWriter.WrittenMemory); - var result = serializer.Deserialize(buffer); + var result = serializer.Deserialize(buffer, null); // Assert - Round trip should succeed Assert.NotNull(result); @@ -44,7 +44,7 @@ public void Deserialize_RequiredNullableProperty_WithJsonSerializerOptionsDefaul var buffer = new ReadOnlySequence(json); // Act - var result = serializer.Deserialize(buffer); + var result = serializer.Deserialize(buffer, null); // Assert - This should work fine Assert.NotNull(result); @@ -60,7 +60,7 @@ public void RoundTrip_RequiredNullableProperty_WithJsonSerializerOptionsDefault_ var bufferWriter = new ArrayBufferWriter(); // Act - Serialize - serializer.Serialize(bufferWriter, obj); + serializer.Serialize(bufferWriter, obj, null); var json = Encoding.UTF8.GetString(bufferWriter.WrittenSpan); // With default options, null is included in JSON @@ -68,7 +68,7 @@ public void RoundTrip_RequiredNullableProperty_WithJsonSerializerOptionsDefault_ // Deserialize back var buffer = new ReadOnlySequence(bufferWriter.WrittenMemory); - var result = serializer.Deserialize(buffer); + var result = serializer.Deserialize(buffer, null); // Assert - Round trip succeeds Assert.NotNull(result); diff --git a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs index a865e4867..4ccd6e115 100644 --- a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs +++ b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs @@ -75,13 +75,13 @@ public async Task When_consuming_ack_should_be_serialized_normally_if_custom_ser private class Level42Serializer : INatsSerializer { - public void Serialize(IBufferWriter bufferWriter, T value) + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { bufferWriter.Write(new byte[] { 42 }); bufferWriter.Advance(1); } - public T Deserialize(in ReadOnlySequence buffer) => (T)(object)new byte[] { 42 }; + public T Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => (T)(object)new byte[] { 42 }; public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); } diff --git a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs index 4eef1a5d4..ccd1baea0 100644 --- a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs @@ -20,12 +20,12 @@ public void ConsumerConfigAckPolicy_test(ConsumerConfigAckPolicy value, string e var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { AckPolicy = value }); + serializer.Serialize(bw, new ConsumerConfig { AckPolicy = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.AckPolicy); } @@ -42,12 +42,12 @@ public void ConsumerConfigDeliverPolicy_test(ConsumerConfigDeliverPolicy value, var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { DeliverPolicy = value }); + serializer.Serialize(bw, new ConsumerConfig { DeliverPolicy = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.DeliverPolicy); } @@ -60,12 +60,12 @@ public void ConsumerConfigReplayPolicy_test(ConsumerConfigReplayPolicy value, st var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { ReplayPolicy = value }); + serializer.Serialize(bw, new ConsumerConfig { ReplayPolicy = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.ReplayPolicy); } @@ -78,7 +78,7 @@ public void StreamConfigCompression_test(StreamConfigCompression value, string e var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Compression = value }); + serializer.Serialize(bw, new StreamConfig { Compression = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); if (value == default) @@ -86,7 +86,7 @@ public void StreamConfigCompression_test(StreamConfigCompression value, string e else Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.Compression); } @@ -99,7 +99,7 @@ public void StreamConfigDiscard_test(StreamConfigDiscard value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Discard = value }); + serializer.Serialize(bw, new StreamConfig { Discard = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); if (value == default) @@ -107,7 +107,7 @@ public void StreamConfigDiscard_test(StreamConfigDiscard value, string expected) else Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.Discard); } @@ -121,12 +121,12 @@ public void StreamConfigRetention_test(StreamConfigRetention value, string expec var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Retention = value }); + serializer.Serialize(bw, new StreamConfig { Retention = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.Retention); } @@ -139,12 +139,12 @@ public void StreamConfigStorage_test(StreamConfigStorage value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Storage = value }); + serializer.Serialize(bw, new StreamConfig { Storage = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.Storage); } @@ -158,12 +158,12 @@ public void ConsumerCreateRequestAction_Test(ConsumerCreateAction value, string var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerCreateRequest { Action = value, StreamName = string.Empty }); + serializer.Serialize(bw, new ConsumerCreateRequest { Action = value, StreamName = string.Empty }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.Action); } @@ -176,13 +176,13 @@ public void StreamConfigPersistMode_null_not_serialized() // When PersistMode is null (not explicitly set), it should not be included in JSON var bw = new NatsBufferWriter(); var config = new StreamConfig { PersistMode = null }; - serializer.Serialize(bw, config); + serializer.Serialize(bw, config, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.DoesNotContain("persist_mode", json); // Deserialize and verify it remains null - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Null(result.PersistMode); } @@ -197,13 +197,13 @@ public void StreamConfigPersistMode_explicit_value_serialized(StreamConfigPersis // When PersistMode is explicitly set (even to Default), it should be included in JSON var bw = new NatsBufferWriter(); var config = new StreamConfig { PersistMode = value }; - serializer.Serialize(bw, config); + serializer.Serialize(bw, config, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); // Deserialize and verify the value is preserved - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.PersistMode); } @@ -216,39 +216,39 @@ public void StreamConfigPersistMode_roundtrip_preserves_server_values() // Test case 1: Server returns "default" - should preserve it var jsonWithDefault = "{\"persist_mode\":\"default\",\"retention\":\"limits\",\"storage\":\"file\"}"; var bytes = Encoding.UTF8.GetBytes(jsonWithDefault); - var configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes)); + var configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), null); Assert.NotNull(configFromServer); Assert.Equal(StreamConfigPersistMode.Default, configFromServer.PersistMode); // When we serialize it again, it should include persist_mode var bw1 = new NatsBufferWriter(); - serializer.Serialize(bw1, configFromServer); + serializer.Serialize(bw1, configFromServer, null); var jsonOut1 = Encoding.UTF8.GetString(bw1.WrittenSpan.ToArray()); Assert.Contains("\"persist_mode\":\"default\"", jsonOut1); // Test case 2: Server returns "async" - should preserve it var jsonWithAsync = "{\"persist_mode\":\"async\",\"retention\":\"limits\",\"storage\":\"file\"}"; bytes = Encoding.UTF8.GetBytes(jsonWithAsync); - configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes)); + configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), null); Assert.NotNull(configFromServer); Assert.Equal(StreamConfigPersistMode.Async, configFromServer.PersistMode); // When we serialize it again, it should include persist_mode var bw2 = new NatsBufferWriter(); - serializer.Serialize(bw2, configFromServer); + serializer.Serialize(bw2, configFromServer, null); var jsonOut2 = Encoding.UTF8.GetString(bw2.WrittenSpan.ToArray()); Assert.Contains("\"persist_mode\":\"async\"", jsonOut2); // Test case 3: Server doesn't return persist_mode - should remain null var jsonWithoutPersistMode = "{\"retention\":\"limits\",\"storage\":\"file\"}"; bytes = Encoding.UTF8.GetBytes(jsonWithoutPersistMode); - configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes)); + configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), null); Assert.NotNull(configFromServer); Assert.Null(configFromServer.PersistMode); // When we serialize it again, it should NOT include persist_mode var bw3 = new NatsBufferWriter(); - serializer.Serialize(bw3, configFromServer); + serializer.Serialize(bw3, configFromServer, null); var jsonOut3 = Encoding.UTF8.GetString(bw3.WrittenSpan.ToArray()); Assert.DoesNotContain("persist_mode", jsonOut3); } @@ -262,12 +262,12 @@ public void ConsumerConfigPriorityPolicy_test(ConsumerConfigPriorityPolicy value var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = value }); + serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = value }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(value, result.PriorityPolicy); } @@ -278,12 +278,12 @@ public void ConsumerConfigPriorityPolicy_null_test() var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = null }); + serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = null }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.DoesNotContain("priority_policy", json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Null(result.PriorityPolicy); } diff --git a/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs b/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs index 2bce33d27..971381a32 100644 --- a/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs +++ b/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs @@ -101,7 +101,7 @@ await Retry.Until( public void Deserialize_value() { var serializer = NatsJSJsonDocumentSerializer.Default; - var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"memory":1}"""))); + var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"memory":1}""")), null); result.Value.Memory.Should().Be(1); } @@ -109,7 +109,7 @@ public void Deserialize_value() public void Deserialize_empty_buffer() { var serializer = NatsJSJsonDocumentSerializer.Default; - var result = serializer.Deserialize(ReadOnlySequence.Empty); + var result = serializer.Deserialize(ReadOnlySequence.Empty, null); result.Exception.Message.Should().Be("Buffer is empty"); } @@ -117,7 +117,7 @@ public void Deserialize_empty_buffer() public void Deserialize_error() { var serializer = NatsJSJsonDocumentSerializer.Default; - var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"error":{"code":2}}"""))); + var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"error":{"code":2}}""")), null); result.Error.Code.Should().Be(2); } } diff --git a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs index 6144ca979..4566f8464 100644 --- a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs @@ -15,12 +15,12 @@ public void Placement_properties_should_be_optional() var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new Placement()); + serializer.Serialize(bw, new Placement(), null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Equal("{}", json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Null(result.Cluster); Assert.Null(result.Tags); @@ -32,7 +32,7 @@ public void Default_consumer_ack_policy_should_be_explicit() var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig()); + serializer.Serialize(bw, new ConsumerConfig(), null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches("\"ack_policy\":\"explicit\"", json); diff --git a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs index c382f5f67..4e4673798 100644 --- a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs @@ -48,12 +48,12 @@ public void ConsumerConfigAckWait_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { AckWait = time }); + serializer.Serialize(bw, new ConsumerConfig { AckWait = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.AckWait); } @@ -68,12 +68,12 @@ public void ConsumerConfigIdleHeartbeat_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { IdleHeartbeat = time }); + serializer.Serialize(bw, new ConsumerConfig { IdleHeartbeat = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.IdleHeartbeat); } @@ -88,12 +88,12 @@ public void ConsumerConfigInactiveThreshold_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { InactiveThreshold = time }); + serializer.Serialize(bw, new ConsumerConfig { InactiveThreshold = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.InactiveThreshold); } @@ -108,12 +108,12 @@ public void ConsumerConfigMaxExpires_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { MaxExpires = time }); + serializer.Serialize(bw, new ConsumerConfig { MaxExpires = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.MaxExpires); } @@ -128,12 +128,12 @@ public void ConsumerGetnextRequestExpires_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerGetnextRequest { Expires = time }); + serializer.Serialize(bw, new ConsumerGetnextRequest { Expires = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.Expires); } @@ -148,12 +148,12 @@ public void ConsumerGetnextRequestIdleHeartbeat_test(string value, string expect var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerGetnextRequest { IdleHeartbeat = time }); + serializer.Serialize(bw, new ConsumerGetnextRequest { IdleHeartbeat = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.IdleHeartbeat); } @@ -168,12 +168,12 @@ public void PeerInfoActive_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new PeerInfo { Name = "test", Active = time }); + serializer.Serialize(bw, new PeerInfo { Name = "test", Active = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.Active); } @@ -188,12 +188,12 @@ public void StreamConfigMaxAge_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { MaxAge = time }); + serializer.Serialize(bw, new StreamConfig { MaxAge = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.MaxAge); } @@ -208,12 +208,12 @@ public void StreamConfigDuplicateWindow_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { DuplicateWindow = time }); + serializer.Serialize(bw, new StreamConfig { DuplicateWindow = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.DuplicateWindow); } @@ -228,12 +228,12 @@ public void StreamSourceInfoActive_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = time }); + serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.Active); } @@ -246,12 +246,12 @@ public void StreamSourceInfoActive_minus_one_indicates_no_activity() var serializer = NatsJSJsonSerializer.Default; var jsonWithMinusOne = """{"name":"test","lag":0,"active":-1}"""u8; - var resultMinusOne = serializer.Deserialize(new ReadOnlySequence(jsonWithMinusOne.ToArray())); + var resultMinusOne = serializer.Deserialize(new ReadOnlySequence(jsonWithMinusOne.ToArray()), null); Assert.NotNull(resultMinusOne); Assert.Null(resultMinusOne.Active); var jsonWithZero = """{"name":"test","lag":0,"active":0}"""u8; - var resultZero = serializer.Deserialize(new ReadOnlySequence(jsonWithZero.ToArray())); + var resultZero = serializer.Deserialize(new ReadOnlySequence(jsonWithZero.ToArray()), null); Assert.NotNull(resultZero); Assert.Equal(TimeSpan.Zero, resultZero.Active); @@ -267,12 +267,12 @@ public void StreamSourceInfoActive_null_roundtrip() // Serialize null -> should produce -1 var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = null }); + serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = null }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains("\"active\":-1", json); // Deserialize -1 -> should produce null - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Null(result.Active); } @@ -288,7 +288,7 @@ public void ConsumerInfoPauseRemaining_test(string? value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerInfo { StreamName = "test", Name = "test", PauseRemaining = time }); + serializer.Serialize(bw, new ConsumerInfo { StreamName = "test", Name = "test", PauseRemaining = time }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); if (value != null) @@ -301,7 +301,7 @@ public void ConsumerInfoPauseRemaining_test(string? value, string expected) Assert.DoesNotMatch(expected, "pause_remaining"); } - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(time, result.PauseRemaining); } @@ -316,12 +316,12 @@ public void ConsumerConfigBackoff_test(int minimumFrameworkVersion, List.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { Backoff = timeSpans }); + serializer.Serialize(bw, new ConsumerConfig { Backoff = timeSpans }, null); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); Assert.NotNull(result); Assert.Equal(timeSpans, result.Backoff); } diff --git a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index a8550387d..5fd339a8d 100644 --- a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -236,7 +236,7 @@ public class MyProtoBufSerializer : INatsSerializer { public static readonly INatsSerializer Default = new MyProtoBufSerializer(); - public void Serialize(IBufferWriter bufferWriter, T value) + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { if (value is IMessage message) { @@ -248,7 +248,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) } } - public T? Deserialize(in ReadOnlySequence buffer) + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (typeof(T) == typeof(Greeting)) { From a82b08e94fb1932dd8c2831500015fcbdec5694c Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 4 Mar 2026 11:35:05 +0000 Subject: [PATCH 02/20] Make `Serialize` method header-aware and add unit tests for header serialization logic --- src/NATS.Client.Core/NatsMsg.cs | 2 +- tests/NATS.Client.Core.Tests/NatsMsgTests.cs | 59 +++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index 208b97708..4a3b32e28 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -519,7 +519,7 @@ public NatsMsg Msg if (Serializer != null && Data != null) { var bufferWriter = new NatsPooledBufferWriter(SerializationBufferSize); - Serializer.Serialize(bufferWriter, Data, null); + Serializer.Serialize(bufferWriter, Data, Headers); size = bufferWriter.WrittenMemory.Length; } diff --git a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs index 92fcb38a5..e3b480c3f 100644 --- a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs +++ b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Text; using NATS.Client.Serializers.Json; @@ -76,7 +77,39 @@ public void Create_nats_msg_by_object() var msg = builder.Msg; var bufferWriter = new NatsPooledBufferWriter(256); - serializer.Serialize(bufferWriter, data, null); + serializer.Serialize(bufferWriter, data, headers); + var serializedSize = bufferWriter.WrittenCount; + + var expectedSize = subject.Length + (replyTo?.Length ?? 0) + headers.GetBytesLength() + serializedSize; + + // Assert + msg.Size.Should().Be(expectedSize); + } + + [Fact] + public void Create_nats_msg_by_object_with_header_aware_serializer() + { + // Arrange + const string subject = "test"; + const string replyTo = "reply"; + var data = new TestData { Id = 1, Name = "example" }; + var headers = new NatsHeaders { { "X-Type", "test-data" } }; + + var serializer = new HeaderAwareSerializer(); + + // Act + var builder = new NatsMsgBuilder + { + Subject = subject, + Data = data, + Serializer = serializer, + Headers = headers, + ReplyTo = replyTo, + }; + var msg = builder.Msg; + + var bufferWriter = new NatsPooledBufferWriter(256); + serializer.Serialize(bufferWriter, data, headers); var serializedSize = bufferWriter.WrittenCount; var expectedSize = subject.Length + (replyTo?.Length ?? 0) + headers.GetBytesLength() + serializedSize; @@ -110,4 +143,28 @@ private class TestData public string Name { get; set; } = null!; } + + private class HeaderAwareSerializer : INatsSerializer + { + private readonly NatsJsonSerializer _inner = new(); + + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + { + // Write a header-based prefix before the JSON payload + if (headers != null && headers.TryGetValue("X-Type", out var values)) + { + var prefix = Encoding.UTF8.GetBytes(values.ToString() + ":"); + var span = bufferWriter.GetSpan(prefix.Length); + prefix.CopyTo(span); + bufferWriter.Advance(prefix.Length); + } + + _inner.Serialize(bufferWriter, value, headers); + } + + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => + _inner.Deserialize(buffer, headers); + + public INatsSerializer CombineWith(INatsSerializer next) => this; + } } From 1e3d64e025c3b6359e87a7b168022b9122e6b658 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 4 Mar 2026 12:36:02 +0000 Subject: [PATCH 03/20] Add recursion guard to prevent infinite loops in default interface methods for `Serialize`/`Deserialize`, update tests and examples accordingly --- .../INatsSerialize.cs | 96 ++++++++++++++++++- .../NatsSerializeGuard.cs | 56 +++++++++++ src/NATS.Client.Core/NatsSerialize.cs | 27 ++++++ .../Internal/NatsJSJsonDocumentSerializer.cs | 5 + ...atsJsonContextOptionsSerializerRegistry.cs | 7 ++ .../NatsJsonSerializer.cs | 9 ++ tests/NATS.Client.CheckAbi/Program.cs | 6 +- .../MySerializer.cs | 4 +- tests/NATS.Client.Core.Tests/NatsMsgTests.cs | 8 ++ .../NATS.Client.Core2.Tests/SerializerTest.cs | 16 ++++ .../CustomSerializerTest.cs | 8 ++ .../Advanced/SerializationPage.cs | 38 ++++++++ .../documentation/advanced/serialization.md | 14 +++ 13 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 src/NATS.Client.Abstractions/NatsSerializeGuard.cs diff --git a/src/NATS.Client.Abstractions/INatsSerialize.cs b/src/NATS.Client.Abstractions/INatsSerialize.cs index c5b8657f9..9ab248aeb 100644 --- a/src/NATS.Client.Abstractions/INatsSerialize.cs +++ b/src/NATS.Client.Abstractions/INatsSerialize.cs @@ -22,13 +22,61 @@ public interface INatsSerializer : INatsSerialize, INatsDeserialize /// Serialized object type public interface INatsSerialize { + /// + /// Serialize value to buffer. + /// + /// Buffer to write the serialized data. + /// Object to be serialized. + [Obsolete("Use Serialize(IBufferWriter, T, INatsHeaders?) overload instead.")] +#if NET8_0_OR_GREATER + void Serialize(IBufferWriter bufferWriter, T value) + { + if (!NatsSerializeGuard.TryEnter()) + { + throw new NotImplementedException("You must implement either Serialize(IBufferWriter, T) or Serialize(IBufferWriter, T, INatsHeaders?)."); + } + + try + { + Serialize(bufferWriter, value, null); + } + finally + { + NatsSerializeGuard.Exit(); + } + } +#else + void Serialize(IBufferWriter bufferWriter, T value); +#endif + /// /// Serialize value to buffer. /// /// Buffer to write the serialized data. /// Object to be serialized. /// Optional NATS headers associated with the message. +#if NET8_0_OR_GREATER + void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + { + if (!NatsSerializeGuard.TryEnter()) + { + throw new NotImplementedException("You must implement either Serialize(IBufferWriter, T) or Serialize(IBufferWriter, T, INatsHeaders?)."); + } + + try + { +#pragma warning disable CS0618 // Type or member is obsolete + Serialize(bufferWriter, value); +#pragma warning restore CS0618 + } + finally + { + NatsSerializeGuard.Exit(); + } + } +#else void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers); +#endif } /// @@ -37,13 +85,59 @@ public interface INatsSerialize /// Deserialized object type public interface INatsDeserialize { + /// + /// Deserialize value from buffer. + /// + /// Buffer with the serialized data. + [Obsolete("Use Deserialize(in ReadOnlySequence, INatsHeaders?) overload instead.")] +#if NET8_0_OR_GREATER + T? Deserialize(in ReadOnlySequence buffer) + { + if (!NatsDeserializeGuard.TryEnter()) + { + throw new NotImplementedException("You must implement either Deserialize(in ReadOnlySequence) or Deserialize(in ReadOnlySequence, INatsHeaders?)."); + } + + try + { + return Deserialize(buffer, null); + } + finally + { + NatsDeserializeGuard.Exit(); + } + } +#else + T? Deserialize(in ReadOnlySequence buffer); +#endif + /// /// Deserialize value from buffer. /// /// Buffer with the serialized data. /// Optional NATS headers associated with the message. - /// Deserialized object +#if NET8_0_OR_GREATER + T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + { + if (!NatsDeserializeGuard.TryEnter()) + { + throw new NotImplementedException("You must implement either Deserialize(in ReadOnlySequence) or Deserialize(in ReadOnlySequence, INatsHeaders?)."); + } + + try + { +#pragma warning disable CS0618 // Type or member is obsolete + return Deserialize(buffer); +#pragma warning restore CS0618 + } + finally + { + NatsDeserializeGuard.Exit(); + } + } +#else T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers); +#endif } public interface INatsSerializerRegistry diff --git a/src/NATS.Client.Abstractions/NatsSerializeGuard.cs b/src/NATS.Client.Abstractions/NatsSerializeGuard.cs new file mode 100644 index 000000000..16f1261cf --- /dev/null +++ b/src/NATS.Client.Abstractions/NatsSerializeGuard.cs @@ -0,0 +1,56 @@ +namespace NATS.Client.Core; + +/// +/// Recursion guard for default interface method (DIM) pairs on . +/// The interface has two Serialize overloads (with and without headers) whose DIMs call each other, +/// so implementors only need to provide one. If neither is implemented, the DIMs would infinitely +/// recurse; this guard uses a counter to detect re-entry on +/// the same thread and signal the DIM to throw instead. +/// Thread-safe: each thread has its own counter, so concurrent calls do not interfere. +/// +internal static class NatsSerializeGuard +{ + [ThreadStatic] + private static int _depth; + + public static bool TryEnter() + { + if (_depth > 0) + { + return false; + } + + _depth++; + return true; + } + + public static void Exit() => _depth--; +} + +/// +/// Recursion guard for default interface method (DIM) pairs on . +/// The interface has two Deserialize overloads (with and without headers) whose DIMs call each other, +/// so implementors only need to provide one. If neither is implemented, the DIMs would infinitely +/// recurse; this guard uses a counter to detect re-entry on +/// the same thread and signal the DIM to throw instead. +/// Separate from so that serialize and deserialize calls on the +/// same thread do not interfere with each other. +/// +internal static class NatsDeserializeGuard +{ + [ThreadStatic] + private static int _depth; + + public static bool TryEnter() + { + if (_depth > 0) + { + return false; + } + + _depth++; + return true; + } + + public static void Exit() => _depth--; +} diff --git a/src/NATS.Client.Core/NatsSerialize.cs b/src/NATS.Client.Core/NatsSerialize.cs index f0b59be12..8a04dbe31 100644 --- a/src/NATS.Client.Core/NatsSerialize.cs +++ b/src/NATS.Client.Core/NatsSerialize.cs @@ -49,6 +49,15 @@ public class NatsUtf8PrimitivesSerializer : INatsSerializer /// The next serializer in chain. public NatsUtf8PrimitivesSerializer(INatsSerializer? next = default) => _next = next; +#pragma warning disable CS0618 // Type or member is obsolete + + /// + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + /// + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + /// public INatsSerializer CombineWith(INatsSerializer? next) => new NatsUtf8PrimitivesSerializer(next); @@ -569,6 +578,15 @@ public class NatsRawSerializer : INatsSerializer /// Next serializer in chain. public NatsRawSerializer(INatsSerializer? next = default) => _next = next; +#pragma warning disable CS0618 // Type or member is obsolete + + /// + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + /// + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + /// public INatsSerializer CombineWith(INatsSerializer? next) => new NatsRawSerializer(next); @@ -724,6 +742,15 @@ public NatsJsonContextSerializer(JsonSerializerContext context, INatsSerializer< { } +#pragma warning disable CS0618 // Type or member is obsolete + + /// + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + /// + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + /// public INatsSerializer CombineWith(INatsSerializer next) => new NatsJsonContextSerializer(_contexts, next); diff --git a/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs b/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs index 49a66c2c3..0a0538c20 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs @@ -8,6 +8,11 @@ internal sealed class NatsJSJsonDocumentSerializer : INatsDeserialize Default = new(); +#pragma warning disable CS0618 // Type or member is obsolete + + public NatsJSApiResult Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + public NatsJSApiResult Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { if (buffer.Length == 0) diff --git a/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs b/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs index c43263a6e..9c3553b84 100644 --- a/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs +++ b/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs @@ -20,6 +20,13 @@ public class NatsJsonOptionsSerializer(JsonSerializerOptions options) : INats [ThreadStatic] private static Utf8JsonWriter? jsonWriter; +#pragma warning disable CS0618 // Type or member is obsolete + + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { Utf8JsonWriter writer; diff --git a/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs b/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs index f2d1fa1c7..0b62afab5 100644 --- a/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs +++ b/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs @@ -48,6 +48,15 @@ public NatsJsonSerializer() /// public static NatsJsonSerializer Default { get; } = new(); +#pragma warning disable CS0618 // Type or member is obsolete + + /// + public void Serialize(IBufferWriter bufferWriter, T? value) => Serialize(bufferWriter, value, null); + + /// + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + /// public INatsSerializer CombineWith(INatsSerializer next) => throw new NotSupportedException(); diff --git a/tests/NATS.Client.CheckAbi/Program.cs b/tests/NATS.Client.CheckAbi/Program.cs index bdd615633..9b2e68c29 100644 --- a/tests/NATS.Client.CheckAbi/Program.cs +++ b/tests/NATS.Client.CheckAbi/Program.cs @@ -27,10 +27,12 @@ Console.WriteLine("Testing TransientLib.MySerializer (compiled against 2.6.0):"); var serializer = new MySerializer(); var buffer = new ArrayBufferWriter(); -serializer.Serialize(buffer, "hello from transient dependency", null); +#pragma warning disable CS0618 // Type or member is obsolete +serializer.Serialize(buffer, "hello from transient dependency"); Console.WriteLine($" Serialized to {buffer.WrittenCount} bytes"); -var deserialized = serializer.Deserialize(new ReadOnlySequence(buffer.WrittenSpan.ToArray()), null); +var deserialized = serializer.Deserialize(new ReadOnlySequence(buffer.WrittenSpan.ToArray())); +#pragma warning restore CS0618 Console.WriteLine($" Deserialized: '{deserialized}'"); Console.WriteLine(); diff --git a/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs b/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs index 3f699c02b..0d1e60648 100644 --- a/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs +++ b/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs @@ -14,13 +14,13 @@ public static string GetSerializerInterfaceAssembly() return typeof(INatsSerialize<>).Assembly.GetName().Name!; } - public void Serialize(IBufferWriter bufferWriter, string value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, string value) { var bytes = System.Text.Encoding.UTF8.GetBytes(value); bufferWriter.Write(bytes); } - public string? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public string? Deserialize(in ReadOnlySequence buffer) { return System.Text.Encoding.UTF8.GetString(buffer.ToArray()); } diff --git a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs index e3b480c3f..912ad7980 100644 --- a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs +++ b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs @@ -148,6 +148,14 @@ private class HeaderAwareSerializer : INatsSerializer { private readonly NatsJsonSerializer _inner = new(); +#if !NET8_0_OR_GREATER +#pragma warning disable CS0618 // Type or member is obsolete + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 +#endif + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { // Write a header-based prefix before the JSON payload diff --git a/tests/NATS.Client.Core2.Tests/SerializerTest.cs b/tests/NATS.Client.Core2.Tests/SerializerTest.cs index 4a1e362e6..56ae99c57 100644 --- a/tests/NATS.Client.Core2.Tests/SerializerTest.cs +++ b/tests/NATS.Client.Core2.Tests/SerializerTest.cs @@ -343,6 +343,14 @@ public class TestSerializerRegistry : INatsSerializerRegistry public class TestSerializer : INatsSerializer { +#if !NET8_0_OR_GREATER +#pragma warning disable CS0618 // Type or member is obsolete + public void Serialize(IBufferWriter bufferWriter, T? value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 +#endif + public void Serialize(IBufferWriter bufferWriter, T? value, INatsHeaders? headers) => throw new TestSerializerException(); public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => throw new TestSerializerException(); @@ -354,6 +362,14 @@ public class TestSerializerException : Exception; public class TestSerializerWithEmpty : INatsSerializer { +#if !NET8_0_OR_GREATER +#pragma warning disable CS0618 // Type or member is obsolete + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 +#endif + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => (T)(object)(buffer.IsEmpty ? new TestData("__EMPTY__") : new TestData(Encoding.ASCII.GetString(buffer.ToArray()))); diff --git a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs index 4ccd6e115..6d02f4055 100644 --- a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs +++ b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs @@ -75,6 +75,14 @@ public async Task When_consuming_ack_should_be_serialized_normally_if_custom_ser private class Level42Serializer : INatsSerializer { +#if !NET8_0_OR_GREATER +#pragma warning disable CS0618 // Type or member is obsolete + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 +#endif + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { bufferWriter.Write(new byte[] { 42 }); diff --git a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index 5fd339a8d..58145737b 100644 --- a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -278,6 +278,44 @@ public class MixedSerializerRegistry : INatsSerializerRegistry } #endregion +#region header-aware-serializer +public class MyHeaderAwareSerializer : INatsSerializer +{ + private readonly NatsJsonContextSerializer _jsonSerializer; + + public MyHeaderAwareSerializer(JsonSerializerContext context) + { + _jsonSerializer = new NatsJsonContextSerializer(context); + } + + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + { + // Set a content-type header so the deserializer knows the format + if (headers is NatsHeaders mutableHeaders) + { + mutableHeaders["Content-Type"] = "application/json"; + } + + _jsonSerializer.Serialize(bufferWriter, value, headers); + } + + public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + { + // Read the content-type header to determine how to deserialize + if (headers != null + && headers.TryGetValue("Content-Type", out var contentType) + && contentType.ToString() == "application/json") + { + return _jsonSerializer.Deserialize(buffer, headers); + } + + throw new NatsException($"Unsupported content type for {typeof(T)}"); + } + + public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); +} +#endregion + // Fake protobuf message. // Normally, this would be generated using protobuf compiler. public class Greeting : IBufferMessage diff --git a/tools/site_src/documentation/advanced/serialization.md b/tools/site_src/documentation/advanced/serialization.md index a3dcf2c40..339335fe8 100644 --- a/tools/site_src/documentation/advanced/serialization.md +++ b/tools/site_src/documentation/advanced/serialization.md @@ -75,6 +75,10 @@ You can also set the serializer for a specific subscription or publish call: You can also provide your own serializer by implementing the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) interface. This is useful if you need to support a custom serialization format or if you need to support multiple serialization formats. +On .NET 8+, you only need to implement one overload of `Serialize` and `Deserialize` thanks to default interface methods (DIMs). +If you implement the headers overload, the old headerless overload is provided automatically (and vice versa). +On older targets (netstandard2.0/2.1), both overloads must be implemented explicitly. + Here is an example of a custom serializer that uses the Google ProtoBuf serializer to serialize and deserialize: [!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#custom-serializer)] @@ -83,6 +87,16 @@ You can then use the custom serializer as the default for the connection: [!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#custom)] +## Using Headers in Serializers + +The `Serialize` and `Deserialize` methods receive an optional [`INatsHeaders`](xref:NATS.Client.Core.INatsHeaders) parameter, +allowing serializers to read or modify NATS headers during serialization. This can be used for scenarios like +content-type negotiation, encoding metadata, or any other header-driven serialization logic. + +Here is an example of a serializer that writes a content-type header during serialization and uses it during deserialization: + +[!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#header-aware-serializer)] + ## Using Multiple Serializers (chaining) You can also chain multiple serializers together to support multiple serialization formats. The first serializer in the From b59cd35078408030d28017def03c69156bbbd420 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 4 Mar 2026 13:00:29 +0000 Subject: [PATCH 04/20] Fix `Content-Type` header deserialization by updating to use `StringValues` --- tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index 58145737b..e6b23c692 100644 --- a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -15,6 +15,7 @@ using System.Text.Json.Serialization; using Google.Protobuf; using Google.Protobuf.Reflection; +using Microsoft.Extensions.Primitives; using NATS.Client.Core; namespace NATS.Net.DocsExamples.Advanced; @@ -303,7 +304,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h { // Read the content-type header to determine how to deserialize if (headers != null - && headers.TryGetValue("Content-Type", out var contentType) + && headers.TryGetValue("Content-Type", out StringValues contentType) && contentType.ToString() == "application/json") { return _jsonSerializer.Deserialize(buffer, headers); From dcc358847d2c9e4ad5068e74d0fcdfcf36d9b5bb Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 4 Mar 2026 13:25:57 +0000 Subject: [PATCH 05/20] Fix NuidTests flap --- tests/NATS.Client.CoreUnit.Tests/NuidTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs index 7e1b1005b..8ffeaf5f8 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs @@ -137,7 +137,7 @@ public void GetNextNuid_PrefixRenewed_Char() }); executionThread.Start(); - executionThread.Join(1_000); + executionThread.Join(10_000); // Assert Assert.Equal(2, Interlocked.CompareExchange(ref _result, 0, 0)); @@ -175,7 +175,7 @@ public void InitAndWrite_Char() Volatile.Write(ref completedSuccessfully, didWrite && isMatch); }); t.Start(); - t.Join(1_000); + t.Join(10_000); Assert.True(completedSuccessfully); } @@ -202,7 +202,7 @@ public void DifferentThreads_DifferentPrefixes() threads.Add(t); } - threads.ForEach(t => t.Join(1_000)); + threads.ForEach(t => t.Join(10_000)); // Assert var uniquePrefixes = new HashSet(); From 42b8cbd4b7879f90f0961d302538b16d57a7898d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 4 Mar 2026 13:35:41 +0000 Subject: [PATCH 06/20] Improve NuidTests stability --- tests/NATS.Client.CoreUnit.Tests/NuidTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs index 8ffeaf5f8..f09fa29aa 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs @@ -137,7 +137,7 @@ public void GetNextNuid_PrefixRenewed_Char() }); executionThread.Start(); - executionThread.Join(10_000); + Assert.True(executionThread.Join(10_000), $"Thread {executionThread.ManagedThreadId} did not complete within the timeout."); // Assert Assert.Equal(2, Interlocked.CompareExchange(ref _result, 0, 0)); @@ -175,7 +175,7 @@ public void InitAndWrite_Char() Volatile.Write(ref completedSuccessfully, didWrite && isMatch); }); t.Start(); - t.Join(10_000); + Assert.True(t.Join(10_000), $"Thread {t.ManagedThreadId} did not complete within the timeout."); Assert.True(completedSuccessfully); } @@ -202,7 +202,10 @@ public void DifferentThreads_DifferentPrefixes() threads.Add(t); } - threads.ForEach(t => t.Join(10_000)); + foreach (var thread in threads) + { + Assert.True(thread.Join(10_000), $"Thread {thread.ManagedThreadId} did not complete within the timeout."); + } // Assert var uniquePrefixes = new HashSet(); From a3fc5f2dfef79fed1542af69a27b346afc41f37c Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 5 Mar 2026 13:41:38 +0000 Subject: [PATCH 07/20] Remove old default methods --- examples/Example.NativeAot/Program.cs | 6 ++ .../INatsSerialize.cs | 66 +------------------ .../NatsSerializeGuard.cs | 56 ---------------- tests/NATS.Client.Core.Tests/NatsMsgTests.cs | 2 - .../NATS.Client.Core2.Tests/SerializerTest.cs | 4 -- .../CustomSerializerTest.cs | 2 - .../Advanced/SerializationPage.cs | 12 ++++ 7 files changed, 20 insertions(+), 128 deletions(-) delete mode 100644 src/NATS.Client.Abstractions/NatsSerializeGuard.cs diff --git a/examples/Example.NativeAot/Program.cs b/examples/Example.NativeAot/Program.cs index c069f8e91..cde927370 100644 --- a/examples/Example.NativeAot/Program.cs +++ b/examples/Example.NativeAot/Program.cs @@ -201,6 +201,12 @@ public class MyProtoBufSerializer : INatsSerializer { public static readonly INatsSerializer Default = new MyProtoBufSerializer(); +#pragma warning disable CS0618 // Type or member is obsolete + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { if (value is IMessage message) diff --git a/src/NATS.Client.Abstractions/INatsSerialize.cs b/src/NATS.Client.Abstractions/INatsSerialize.cs index 9ab248aeb..b9163f851 100644 --- a/src/NATS.Client.Abstractions/INatsSerialize.cs +++ b/src/NATS.Client.Abstractions/INatsSerialize.cs @@ -28,26 +28,7 @@ public interface INatsSerialize /// Buffer to write the serialized data. /// Object to be serialized. [Obsolete("Use Serialize(IBufferWriter, T, INatsHeaders?) overload instead.")] -#if NET8_0_OR_GREATER - void Serialize(IBufferWriter bufferWriter, T value) - { - if (!NatsSerializeGuard.TryEnter()) - { - throw new NotImplementedException("You must implement either Serialize(IBufferWriter, T) or Serialize(IBufferWriter, T, INatsHeaders?)."); - } - - try - { - Serialize(bufferWriter, value, null); - } - finally - { - NatsSerializeGuard.Exit(); - } - } -#else void Serialize(IBufferWriter bufferWriter, T value); -#endif /// /// Serialize value to buffer. @@ -58,21 +39,9 @@ void Serialize(IBufferWriter bufferWriter, T value) #if NET8_0_OR_GREATER void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { - if (!NatsSerializeGuard.TryEnter()) - { - throw new NotImplementedException("You must implement either Serialize(IBufferWriter, T) or Serialize(IBufferWriter, T, INatsHeaders?)."); - } - - try - { #pragma warning disable CS0618 // Type or member is obsolete - Serialize(bufferWriter, value); + Serialize(bufferWriter, value); #pragma warning restore CS0618 - } - finally - { - NatsSerializeGuard.Exit(); - } } #else void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers); @@ -90,26 +59,7 @@ public interface INatsDeserialize /// /// Buffer with the serialized data. [Obsolete("Use Deserialize(in ReadOnlySequence, INatsHeaders?) overload instead.")] -#if NET8_0_OR_GREATER - T? Deserialize(in ReadOnlySequence buffer) - { - if (!NatsDeserializeGuard.TryEnter()) - { - throw new NotImplementedException("You must implement either Deserialize(in ReadOnlySequence) or Deserialize(in ReadOnlySequence, INatsHeaders?)."); - } - - try - { - return Deserialize(buffer, null); - } - finally - { - NatsDeserializeGuard.Exit(); - } - } -#else T? Deserialize(in ReadOnlySequence buffer); -#endif /// /// Deserialize value from buffer. @@ -119,21 +69,9 @@ public interface INatsDeserialize #if NET8_0_OR_GREATER T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) { - if (!NatsDeserializeGuard.TryEnter()) - { - throw new NotImplementedException("You must implement either Deserialize(in ReadOnlySequence) or Deserialize(in ReadOnlySequence, INatsHeaders?)."); - } - - try - { #pragma warning disable CS0618 // Type or member is obsolete - return Deserialize(buffer); + return Deserialize(buffer); #pragma warning restore CS0618 - } - finally - { - NatsDeserializeGuard.Exit(); - } } #else T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers); diff --git a/src/NATS.Client.Abstractions/NatsSerializeGuard.cs b/src/NATS.Client.Abstractions/NatsSerializeGuard.cs deleted file mode 100644 index 16f1261cf..000000000 --- a/src/NATS.Client.Abstractions/NatsSerializeGuard.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace NATS.Client.Core; - -/// -/// Recursion guard for default interface method (DIM) pairs on . -/// The interface has two Serialize overloads (with and without headers) whose DIMs call each other, -/// so implementors only need to provide one. If neither is implemented, the DIMs would infinitely -/// recurse; this guard uses a counter to detect re-entry on -/// the same thread and signal the DIM to throw instead. -/// Thread-safe: each thread has its own counter, so concurrent calls do not interfere. -/// -internal static class NatsSerializeGuard -{ - [ThreadStatic] - private static int _depth; - - public static bool TryEnter() - { - if (_depth > 0) - { - return false; - } - - _depth++; - return true; - } - - public static void Exit() => _depth--; -} - -/// -/// Recursion guard for default interface method (DIM) pairs on . -/// The interface has two Deserialize overloads (with and without headers) whose DIMs call each other, -/// so implementors only need to provide one. If neither is implemented, the DIMs would infinitely -/// recurse; this guard uses a counter to detect re-entry on -/// the same thread and signal the DIM to throw instead. -/// Separate from so that serialize and deserialize calls on the -/// same thread do not interfere with each other. -/// -internal static class NatsDeserializeGuard -{ - [ThreadStatic] - private static int _depth; - - public static bool TryEnter() - { - if (_depth > 0) - { - return false; - } - - _depth++; - return true; - } - - public static void Exit() => _depth--; -} diff --git a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs index 912ad7980..735241a0c 100644 --- a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs +++ b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs @@ -148,13 +148,11 @@ private class HeaderAwareSerializer : INatsSerializer { private readonly NatsJsonSerializer _inner = new(); -#if !NET8_0_OR_GREATER #pragma warning disable CS0618 // Type or member is obsolete public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); #pragma warning restore CS0618 -#endif public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { diff --git a/tests/NATS.Client.Core2.Tests/SerializerTest.cs b/tests/NATS.Client.Core2.Tests/SerializerTest.cs index 56ae99c57..9448d428c 100644 --- a/tests/NATS.Client.Core2.Tests/SerializerTest.cs +++ b/tests/NATS.Client.Core2.Tests/SerializerTest.cs @@ -343,13 +343,11 @@ public class TestSerializerRegistry : INatsSerializerRegistry public class TestSerializer : INatsSerializer { -#if !NET8_0_OR_GREATER #pragma warning disable CS0618 // Type or member is obsolete public void Serialize(IBufferWriter bufferWriter, T? value) => Serialize(bufferWriter, value, null); public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); #pragma warning restore CS0618 -#endif public void Serialize(IBufferWriter bufferWriter, T? value, INatsHeaders? headers) => throw new TestSerializerException(); @@ -362,13 +360,11 @@ public class TestSerializerException : Exception; public class TestSerializerWithEmpty : INatsSerializer { -#if !NET8_0_OR_GREATER #pragma warning disable CS0618 // Type or member is obsolete public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); #pragma warning restore CS0618 -#endif public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => (T)(object)(buffer.IsEmpty ? new TestData("__EMPTY__") diff --git a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs index 6d02f4055..2d7605357 100644 --- a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs +++ b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs @@ -75,13 +75,11 @@ public async Task When_consuming_ack_should_be_serialized_normally_if_custom_ser private class Level42Serializer : INatsSerializer { -#if !NET8_0_OR_GREATER #pragma warning disable CS0618 // Type or member is obsolete public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); #pragma warning restore CS0618 -#endif public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { diff --git a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index e6b23c692..3dd18682d 100644 --- a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -237,6 +237,12 @@ public class MyProtoBufSerializer : INatsSerializer { public static readonly INatsSerializer Default = new MyProtoBufSerializer(); +#pragma warning disable CS0618 // Type or member is obsolete + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { if (value is IMessage message) @@ -289,6 +295,12 @@ public MyHeaderAwareSerializer(JsonSerializerContext context) _jsonSerializer = new NatsJsonContextSerializer(context); } +#pragma warning disable CS0618 // Type or member is obsolete + public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + + public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); +#pragma warning restore CS0618 + public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { // Set a content-type header so the deserializer knows the format From 0a58dd7770298fc2e5f40e8f43956904749db6c1 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 5 Mar 2026 13:44:34 +0000 Subject: [PATCH 08/20] Remove `IDictionary` implementation from `NatsHeaders` --- src/NATS.Client.Core/NatsHeaders.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 50cbadd66..284844792 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -15,7 +15,7 @@ namespace NATS.Client.Core; [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1516:Elements should be separated by blank line", Justification = "Keep class format as is for reference")] [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Keep class format as is for reference")] [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1214:Readonly fields should appear before non-readonly fields", Justification = "Keep class format as is for reference")] -public class NatsHeaders : INatsHeaders, IDictionary +public class NatsHeaders : INatsHeaders { public enum Messages { From 822474f315ed24ea51f99eac58aebafa61b29902 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 13 Mar 2026 12:41:41 +0000 Subject: [PATCH 09/20] Add INatsSerializeWithHeaders --- .../INatsSerialize.cs | 87 ++++++++++++------- src/NATS.Client.Core/NatsSerialize.cs | 51 +++-------- .../Internal/NatsJSJsonDocumentSerializer.cs | 7 +- src/NATS.Client.KeyValueStore/NatsKVStore.cs | 2 +- src/NATS.Client.ObjectStore/NatsObjStore.cs | 2 +- ...atsJsonContextOptionsSerializerRegistry.cs | 11 +-- .../NatsJsonSerializer.cs | 13 +-- tests/NATS.Client.Core.Tests/NatsMsgTests.cs | 16 ++-- .../NATS.Client.Core2.Tests/SerializerTest.cs | 36 +++----- .../CustomSerializerTest.cs | 10 +-- .../Advanced/SerializationPage.cs | 24 ++--- 11 files changed, 106 insertions(+), 153 deletions(-) diff --git a/src/NATS.Client.Abstractions/INatsSerialize.cs b/src/NATS.Client.Abstractions/INatsSerialize.cs index b9163f851..e958823e2 100644 --- a/src/NATS.Client.Abstractions/INatsSerialize.cs +++ b/src/NATS.Client.Abstractions/INatsSerialize.cs @@ -27,25 +27,7 @@ public interface INatsSerialize /// /// Buffer to write the serialized data. /// Object to be serialized. - [Obsolete("Use Serialize(IBufferWriter, T, INatsHeaders?) overload instead.")] void Serialize(IBufferWriter bufferWriter, T value); - - /// - /// Serialize value to buffer. - /// - /// Buffer to write the serialized data. - /// Object to be serialized. - /// Optional NATS headers associated with the message. -#if NET8_0_OR_GREATER - void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) - { -#pragma warning disable CS0618 // Type or member is obsolete - Serialize(bufferWriter, value); -#pragma warning restore CS0618 - } -#else - void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers); -#endif } /// @@ -58,29 +40,76 @@ public interface INatsDeserialize /// Deserialize value from buffer. /// /// Buffer with the serialized data. - [Obsolete("Use Deserialize(in ReadOnlySequence, INatsHeaders?) overload instead.")] T? Deserialize(in ReadOnlySequence buffer); +} +/// +/// Extended serializer interface that supports sending headers during serialization. +/// +/// Serialized object type +public interface INatsSerializeWithHeaders : INatsSerialize +{ /// - /// Deserialize value from buffer. + /// Serialize value to buffer with headers. + /// + /// Buffer to write the serialized data. + /// Object to be serialized. + /// Optional NATS headers associated with the message. + void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers); +} + +/// +/// Extended deserializer interface that supports receiving headers during deserialization. +/// +/// Deserialized object type +public interface INatsDeserializeWithHeaders : INatsDeserialize +{ + /// + /// Deserialize value from buffer with headers. /// /// Buffer with the serialized data. /// Optional NATS headers associated with the message. -#if NET8_0_OR_GREATER - T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) - { -#pragma warning disable CS0618 // Type or member is obsolete - return Deserialize(buffer); -#pragma warning restore CS0618 - } -#else + /// Deserialized object T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers); -#endif } +/// +/// Registry for serializers and deserializers. +/// public interface INatsSerializerRegistry { INatsSerialize GetSerializer(); INatsDeserialize GetDeserializer(); } + +/// +/// Extension methods to support header-aware serialization with fallback to standard serialization. +/// +public static class NatsSerializationExtensions +{ + /// + /// Serializes the value with header support, falling back to standard serialization if headers are not supported. + /// + public static void Serialize(this INatsSerialize serializer, IBufferWriter bufferWriter, T value, INatsHeaders? headers) + { + if (headers != null && serializer is INatsSerializeWithHeaders withHeaders) + { + withHeaders.Serialize(bufferWriter, value, headers); + return; + } + + serializer.Serialize(bufferWriter, value); + } + + /// + /// Deserializes the value with header support, falling back to standard deserialization if headers are not supported. + /// + public static T? Deserialize(this INatsDeserialize deserializer, in ReadOnlySequence buffer, INatsHeaders? headers) + { + if (deserializer is INatsDeserializeWithHeaders withHeaders) + return withHeaders.Deserialize(buffer, headers); + + return deserializer.Deserialize(buffer); + } +} diff --git a/src/NATS.Client.Core/NatsSerialize.cs b/src/NATS.Client.Core/NatsSerialize.cs index 8a04dbe31..3f889935b 100644 --- a/src/NATS.Client.Core/NatsSerialize.cs +++ b/src/NATS.Client.Core/NatsSerialize.cs @@ -49,20 +49,11 @@ public class NatsUtf8PrimitivesSerializer : INatsSerializer /// The next serializer in chain. public NatsUtf8PrimitivesSerializer(INatsSerializer? next = default) => _next = next; -#pragma warning disable CS0618 // Type or member is obsolete - - /// - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - /// - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - /// public INatsSerializer CombineWith(INatsSerializer? next) => new NatsUtf8PrimitivesSerializer(next); /// - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value) { if (value is string str) { @@ -344,11 +335,11 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h throw new NatsException($"Can't serialize {typeof(T)}"); } - _next.Serialize(bufferWriter, value, headers); + _next.Serialize(bufferWriter, value); } /// - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer) { if (typeof(T) == typeof(string)) { @@ -559,7 +550,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h throw new NatsException($"Can't deserialize {typeof(T)}"); } - return _next.Deserialize(buffer, headers); + return _next.Deserialize(buffer); } } @@ -578,20 +569,11 @@ public class NatsRawSerializer : INatsSerializer /// Next serializer in chain. public NatsRawSerializer(INatsSerializer? next = default) => _next = next; -#pragma warning disable CS0618 // Type or member is obsolete - - /// - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - /// - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - /// public INatsSerializer CombineWith(INatsSerializer? next) => new NatsRawSerializer(next); /// - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value) { if (value is byte[] bytes) { @@ -648,11 +630,11 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h throw new NatsException($"Can't serialize {typeof(T)}"); } - _next.Serialize(bufferWriter, value, headers); + _next.Serialize(bufferWriter, value); } /// - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer) { if (typeof(T) == typeof(byte[])) { @@ -696,7 +678,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h throw new NatsException($"Can't deserialize {typeof(T)}"); } - return _next.Deserialize(buffer, headers); + return _next.Deserialize(buffer); } } @@ -742,20 +724,11 @@ public NatsJsonContextSerializer(JsonSerializerContext context, INatsSerializer< { } -#pragma warning disable CS0618 // Type or member is obsolete - - /// - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - /// - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - /// public INatsSerializer CombineWith(INatsSerializer next) => new NatsJsonContextSerializer(_contexts, next); /// - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value) { foreach (var context in _contexts) { @@ -784,11 +757,11 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h throw new NatsException($"Can't serialize {typeof(T)}"); } - _next.Serialize(bufferWriter, value, headers); + _next.Serialize(bufferWriter, value); } /// - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer) { if (buffer.Length == 0) { @@ -805,7 +778,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h } if (_next != null) - return _next.Deserialize(buffer, headers); + return _next.Deserialize(buffer); throw new NatsException($"Can't deserialize {typeof(T)}"); } diff --git a/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs b/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs index 0a0538c20..c022b47a0 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSJsonDocumentSerializer.cs @@ -8,12 +8,7 @@ internal sealed class NatsJSJsonDocumentSerializer : INatsDeserialize Default = new(); -#pragma warning disable CS0618 // Type or member is obsolete - - public NatsJSApiResult Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - - public NatsJSApiResult Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public NatsJSApiResult Deserialize(in ReadOnlySequence buffer) { if (buffer.Length == 0) { diff --git a/src/NATS.Client.KeyValueStore/NatsKVStore.cs b/src/NATS.Client.KeyValueStore/NatsKVStore.cs index 96a1485ad..260f9914c 100644 --- a/src/NATS.Client.KeyValueStore/NatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/NatsKVStore.cs @@ -400,7 +400,7 @@ public async ValueTask>> TryGetEntryAsync(string ke try { - data = serializer.Deserialize(buffer, null); + data = serializer.Deserialize(buffer); } catch (Exception e) { diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 783be6eea..befdca53c 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -473,7 +473,7 @@ public async ValueTask GetInfoAsync(string key, bool showDeleted } var buffer = new ReadOnlySequence(response.Message.Data); - var data = NatsObjJsonSerializer.Default.Deserialize(buffer, null) ?? throw new NatsObjException("Can't deserialize object metadata"); + var data = NatsObjJsonSerializer.Default.Deserialize(buffer) ?? throw new NatsObjException("Can't deserialize object metadata"); data.MTime = response.Message.Time; diff --git a/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs b/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs index 9c3553b84..c205a9213 100644 --- a/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs +++ b/src/NATS.Client.Serializers.Json/NatsJsonContextOptionsSerializerRegistry.cs @@ -20,14 +20,7 @@ public class NatsJsonOptionsSerializer(JsonSerializerOptions options) : INats [ThreadStatic] private static Utf8JsonWriter? jsonWriter; -#pragma warning disable CS0618 // Type or member is obsolete - - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value) { Utf8JsonWriter writer; if (jsonWriter == null) @@ -45,7 +38,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h writer.Reset(NullBufferWriter.Instance); } - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer) { if (buffer.Length == 0) { diff --git a/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs b/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs index 0b62afab5..376318993 100644 --- a/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs +++ b/src/NATS.Client.Serializers.Json/NatsJsonSerializer.cs @@ -48,20 +48,11 @@ public NatsJsonSerializer() /// public static NatsJsonSerializer Default { get; } = new(); -#pragma warning disable CS0618 // Type or member is obsolete - - /// - public void Serialize(IBufferWriter bufferWriter, T? value) => Serialize(bufferWriter, value, null); - - /// - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - /// public INatsSerializer CombineWith(INatsSerializer next) => throw new NotSupportedException(); /// - public void Serialize(IBufferWriter bufferWriter, T? value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T? value) { Utf8JsonWriter writer; if (_jsonWriter == null) @@ -80,7 +71,7 @@ public void Serialize(IBufferWriter bufferWriter, T? value, INatsHeaders? } /// - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer) { if (buffer.Length == 0) { diff --git a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs index 735241a0c..1b5155d73 100644 --- a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs +++ b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs @@ -77,7 +77,7 @@ public void Create_nats_msg_by_object() var msg = builder.Msg; var bufferWriter = new NatsPooledBufferWriter(256); - serializer.Serialize(bufferWriter, data, headers); + serializer.Serialize(bufferWriter, data); var serializedSize = bufferWriter.WrittenCount; var expectedSize = subject.Length + (replyTo?.Length ?? 0) + headers.GetBytesLength() + serializedSize; @@ -109,7 +109,7 @@ public void Create_nats_msg_by_object_with_header_aware_serializer() var msg = builder.Msg; var bufferWriter = new NatsPooledBufferWriter(256); - serializer.Serialize(bufferWriter, data, headers); + ((INatsSerializeWithHeaders)serializer).Serialize(bufferWriter, data, headers); var serializedSize = bufferWriter.WrittenCount; var expectedSize = subject.Length + (replyTo?.Length ?? 0) + headers.GetBytesLength() + serializedSize; @@ -144,15 +144,13 @@ private class TestData public string Name { get; set; } = null!; } - private class HeaderAwareSerializer : INatsSerializer + private class HeaderAwareSerializer : INatsSerializer, INatsSerializeWithHeaders, INatsDeserializeWithHeaders { private readonly NatsJsonSerializer _inner = new(); -#pragma warning disable CS0618 // Type or member is obsolete - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + public void Serialize(IBufferWriter bufferWriter, T value) => _inner.Serialize(bufferWriter, value); - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 + public T? Deserialize(in ReadOnlySequence buffer) => _inner.Deserialize(buffer); public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { @@ -165,11 +163,11 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h bufferWriter.Advance(prefix.Length); } - _inner.Serialize(bufferWriter, value, headers); + _inner.Serialize(bufferWriter, value); } public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => - _inner.Deserialize(buffer, headers); + _inner.Deserialize(buffer); public INatsSerializer CombineWith(INatsSerializer next) => this; } diff --git a/tests/NATS.Client.Core2.Tests/SerializerTest.cs b/tests/NATS.Client.Core2.Tests/SerializerTest.cs index 9448d428c..fbd987e79 100644 --- a/tests/NATS.Client.Core2.Tests/SerializerTest.cs +++ b/tests/NATS.Client.Core2.Tests/SerializerTest.cs @@ -107,7 +107,7 @@ void SerializeDeserialize(T actual, string expected) void Serialize(INatsSerialize serializer, T value, string expected) { var buffer = new NatsBufferWriter(); - serializer.Serialize(buffer, value, null); + serializer.Serialize(buffer, value); var actual = Encoding.UTF8.GetString(buffer.WrittenMemory.Span.ToArray()); Assert.Equal(expected, actual); } @@ -115,7 +115,7 @@ void Serialize(INatsSerialize serializer, T value, string expected) void Deserialize(INatsDeserialize serializer, string input, T expected) { var buffer = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); - var actual = serializer.Deserialize(buffer, null); + var actual = serializer.Deserialize(buffer); Assert.Equal(expected, actual); } } @@ -145,14 +145,14 @@ public void Utf8_deserialize_empty() // Test chaining var testDataSerializer = new NatsUtf8PrimitivesSerializer(new TestSerializer()); - Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer, null)); + Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer)); return; void Deserialize() { var serializer = new NatsUtf8PrimitivesSerializer(new TestSerializer()); - var actual = serializer.Deserialize(emptyBuffer, null); + var actual = serializer.Deserialize(emptyBuffer); Assert.Equal(actual, default); } } @@ -195,7 +195,7 @@ void SerializeDeserialize(byte[] inputBuffer, Func input, Func(INatsSerialize serializer, byte[] inputBuffer, Func input) { var buffer = new NatsBufferWriter(); - serializer.Serialize(buffer, input(inputBuffer), null); + serializer.Serialize(buffer, input(inputBuffer)); var actual = buffer.WrittenMemory.ToArray(); AssertByteArray(inputBuffer, actual); } @@ -203,7 +203,7 @@ void Serialize(INatsSerialize serializer, byte[] inputBuffer, Func(INatsDeserialize serializer, byte[] inputBuffer, Func output) { var buffer = new ReadOnlySequence(inputBuffer); - var actual = serializer.Deserialize(buffer, null); + var actual = serializer.Deserialize(buffer); Assert.True(actual is { }); AssertByteArray(inputBuffer, output(actual)); } @@ -222,14 +222,14 @@ public void Raw_deserialize_empty() // Test chaining var testDataSerializer = new NatsRawSerializer(new TestSerializer()); - Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer, null)); + Assert.Throws(() => testDataSerializer.Deserialize(emptyBuffer)); return; void Deserialize() { var serializer = new NatsRawSerializer(new TestSerializer()); - var actual = serializer.Deserialize(emptyBuffer, null); + var actual = serializer.Deserialize(emptyBuffer); Assert.Equal(actual, default); } } @@ -343,15 +343,9 @@ public class TestSerializerRegistry : INatsSerializerRegistry public class TestSerializer : INatsSerializer { -#pragma warning disable CS0618 // Type or member is obsolete - public void Serialize(IBufferWriter bufferWriter, T? value) => Serialize(bufferWriter, value, null); + public void Serialize(IBufferWriter bufferWriter, T? value) => throw new TestSerializerException(); - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - - public void Serialize(IBufferWriter bufferWriter, T? value, INatsHeaders? headers) => throw new TestSerializerException(); - - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => throw new TestSerializerException(); + public T? Deserialize(in ReadOnlySequence buffer) => throw new TestSerializerException(); public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); } @@ -360,17 +354,11 @@ public class TestSerializerException : Exception; public class TestSerializerWithEmpty : INatsSerializer { -#pragma warning disable CS0618 // Type or member is obsolete - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => (T)(object)(buffer.IsEmpty + public T? Deserialize(in ReadOnlySequence buffer) => (T)(object)(buffer.IsEmpty ? new TestData("__EMPTY__") : new TestData(Encoding.ASCII.GetString(buffer.ToArray()))); - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) => throw new Exception("not used"); + public void Serialize(IBufferWriter bufferWriter, T value) => throw new Exception("not used"); public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); } diff --git a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs index 2d7605357..a865e4867 100644 --- a/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs +++ b/tests/NATS.Client.JetStream.Tests/CustomSerializerTest.cs @@ -75,19 +75,13 @@ public async Task When_consuming_ack_should_be_serialized_normally_if_custom_ser private class Level42Serializer : INatsSerializer { -#pragma warning disable CS0618 // Type or member is obsolete - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value) { bufferWriter.Write(new byte[] { 42 }); bufferWriter.Advance(1); } - public T Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => (T)(object)new byte[] { 42 }; + public T Deserialize(in ReadOnlySequence buffer) => (T)(object)new byte[] { 42 }; public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); } diff --git a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index 3dd18682d..a4cabc153 100644 --- a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -237,13 +237,7 @@ public class MyProtoBufSerializer : INatsSerializer { public static readonly INatsSerializer Default = new MyProtoBufSerializer(); -#pragma warning disable CS0618 // Type or member is obsolete - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 - - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value) { if (value is IMessage message) { @@ -255,7 +249,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h } } - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer) { if (typeof(T) == typeof(Greeting)) { @@ -286,7 +280,7 @@ public class MixedSerializerRegistry : INatsSerializerRegistry #endregion #region header-aware-serializer -public class MyHeaderAwareSerializer : INatsSerializer +public class MyHeaderAwareSerializer : INatsSerializer, INatsSerializeWithHeaders, INatsDeserializeWithHeaders { private readonly NatsJsonContextSerializer _jsonSerializer; @@ -295,11 +289,9 @@ public MyHeaderAwareSerializer(JsonSerializerContext context) _jsonSerializer = new NatsJsonContextSerializer(context); } -#pragma warning disable CS0618 // Type or member is obsolete - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); + public void Serialize(IBufferWriter bufferWriter, T value) => _jsonSerializer.Serialize(bufferWriter, value); - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 + public T? Deserialize(in ReadOnlySequence buffer) => _jsonSerializer.Deserialize(buffer); public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { @@ -309,7 +301,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h mutableHeaders["Content-Type"] = "application/json"; } - _jsonSerializer.Serialize(bufferWriter, value, headers); + _jsonSerializer.Serialize(bufferWriter, value); } public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) @@ -319,13 +311,13 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h && headers.TryGetValue("Content-Type", out StringValues contentType) && contentType.ToString() == "application/json") { - return _jsonSerializer.Deserialize(buffer, headers); + return _jsonSerializer.Deserialize(buffer); } throw new NatsException($"Unsupported content type for {typeof(T)}"); } - public INatsSerializer CombineWith(INatsSerializer next) => throw new NotImplementedException(); + public INatsSerializer CombineWith(INatsSerializer next) => throw new NotSupportedException(); } #endregion From 4b794dc718b09e84b9aa1895d5679ff326649e72 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Tue, 31 Mar 2026 11:14:20 +0100 Subject: [PATCH 10/20] serializer: remove unnecessary obsolete warning pragmas The CS0618 suppressions were leftover from the DIM approach where interface members were marked obsolete. No longer needed with the separate interface design. --- examples/Example.NativeAot/Program.cs | 2 -- tests/NATS.Client.CheckAbi/Program.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/examples/Example.NativeAot/Program.cs b/examples/Example.NativeAot/Program.cs index cde927370..75570cf3a 100644 --- a/examples/Example.NativeAot/Program.cs +++ b/examples/Example.NativeAot/Program.cs @@ -201,11 +201,9 @@ public class MyProtoBufSerializer : INatsSerializer { public static readonly INatsSerializer Default = new MyProtoBufSerializer(); -#pragma warning disable CS0618 // Type or member is obsolete public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); -#pragma warning restore CS0618 public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) { diff --git a/tests/NATS.Client.CheckAbi/Program.cs b/tests/NATS.Client.CheckAbi/Program.cs index 9b2e68c29..054a1c184 100644 --- a/tests/NATS.Client.CheckAbi/Program.cs +++ b/tests/NATS.Client.CheckAbi/Program.cs @@ -27,12 +27,10 @@ Console.WriteLine("Testing TransientLib.MySerializer (compiled against 2.6.0):"); var serializer = new MySerializer(); var buffer = new ArrayBufferWriter(); -#pragma warning disable CS0618 // Type or member is obsolete serializer.Serialize(buffer, "hello from transient dependency"); Console.WriteLine($" Serialized to {buffer.WrittenCount} bytes"); var deserialized = serializer.Deserialize(new ReadOnlySequence(buffer.WrittenSpan.ToArray())); -#pragma warning restore CS0618 Console.WriteLine($" Deserialized: '{deserialized}'"); Console.WriteLine(); From bfee5ed4e97f0a6a294b9cff6e448a0441f78f91 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Tue, 31 Mar 2026 12:08:31 +0100 Subject: [PATCH 11/20] tests: add serialization extension and ABI checks Add unit tests for NatsSerializationExtensions covering both the header-aware path and the fallback path for serialize and deserialize. Make the ABI check program assert correctness instead of just printing, including the new WithHeaders interfaces. --- tests/NATS.Client.CheckAbi/Program.cs | 38 +++-- .../NatsSerializationExtensionsTests.cs | 131 ++++++++++++++++++ 2 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs diff --git a/tests/NATS.Client.CheckAbi/Program.cs b/tests/NATS.Client.CheckAbi/Program.cs index 054a1c184..74e615624 100644 --- a/tests/NATS.Client.CheckAbi/Program.cs +++ b/tests/NATS.Client.CheckAbi/Program.cs @@ -6,24 +6,28 @@ // - AbiCheck references NATS.Net 2.7.0 (local source with type forwarders) // - TransientLib was compiled against NATS.Net 2.6.0 (types in NATS.Client.Core) // - At runtime, type forwarding should allow TransientLib to work with 2.7.0 -Console.WriteLine("ABI Compatibility Check (Transient Dependency Simulation)"); -Console.WriteLine("=========================================================="); -Console.WriteLine(); +var errors = new List(); + +void Check(string name, string expected, string actual) +{ + Console.WriteLine($" {name}: {actual}"); + if (actual != expected) + errors.Add($"{name}: expected '{expected}', got '{actual}'"); +} -// Check where THIS project sees the types (should be Abstractions since we use 2.7.0) Console.WriteLine("Types as seen by this project (compiled against 2.7.0):"); -Console.WriteLine($" INatsSerialize<> assembly: {typeof(INatsSerialize<>).Assembly.GetName().Name}"); -Console.WriteLine($" INatsDeserialize<> assembly: {typeof(INatsDeserialize<>).Assembly.GetName().Name}"); -Console.WriteLine($" INatsSerializer<> assembly: {typeof(INatsSerializer<>).Assembly.GetName().Name}"); -Console.WriteLine($" INatsSerializerRegistry assembly: {typeof(INatsSerializerRegistry).Assembly.GetName().Name}"); +Check("INatsSerialize<>", "NATS.Client.Abstractions", typeof(INatsSerialize<>).Assembly.GetName().Name!); +Check("INatsDeserialize<>", "NATS.Client.Abstractions", typeof(INatsDeserialize<>).Assembly.GetName().Name!); +Check("INatsSerializer<>", "NATS.Client.Abstractions", typeof(INatsSerializer<>).Assembly.GetName().Name!); +Check("INatsSerializerRegistry", "NATS.Client.Abstractions", typeof(INatsSerializerRegistry).Assembly.GetName().Name!); +Check("INatsSerializeWithHeaders<>", "NATS.Client.Abstractions", typeof(INatsSerializeWithHeaders<>).Assembly.GetName().Name!); +Check("INatsDeserializeWithHeaders<>", "NATS.Client.Abstractions", typeof(INatsDeserializeWithHeaders<>).Assembly.GetName().Name!); Console.WriteLine(); -// Check where TransientLib sees the types (compiled against 2.6.0, but should resolve via forwarding) Console.WriteLine("Types as seen by TransientLib (compiled against 2.6.0):"); -Console.WriteLine($" INatsSerialize<> assembly: {MySerializer.GetSerializerInterfaceAssembly()}"); +Check("INatsSerialize<> (transient)", "NATS.Client.Abstractions", MySerializer.GetSerializerInterfaceAssembly()); Console.WriteLine(); -// Use the serializer from TransientLib (compiled against 2.6.0) Console.WriteLine("Testing TransientLib.MySerializer (compiled against 2.6.0):"); var serializer = new MySerializer(); var buffer = new ArrayBufferWriter(); @@ -32,6 +36,18 @@ var deserialized = serializer.Deserialize(new ReadOnlySequence(buffer.WrittenSpan.ToArray())); Console.WriteLine($" Deserialized: '{deserialized}'"); +if (deserialized != "hello from transient dependency") + errors.Add($"Round-trip failed: got '{deserialized}'"); Console.WriteLine(); + +if (errors.Count > 0) +{ + Console.Error.WriteLine($"FAILED: {errors.Count} error(s):"); + foreach (var error in errors) + Console.Error.WriteLine($" {error}"); + return 1; +} + Console.WriteLine("SUCCESS: ABI compatibility verified with transient dependency!"); +return 0; diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs b/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs new file mode 100644 index 000000000..22ffeac76 --- /dev/null +++ b/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs @@ -0,0 +1,131 @@ +using System.Buffers; + +namespace NATS.Client.CoreUnit.Tests; + +public class NatsSerializationExtensionsTests +{ + [Fact] + public void Serialize_with_header_aware_serializer_calls_header_overload() + { + var serializer = new TrackingSerializerWithHeaders(); + var buffer = new NatsPooledBufferWriter(256); + var headers = new NatsHeaders { { "X-Test", "value" } }; + + ((INatsSerialize)serializer).Serialize(buffer, "test", headers); + + serializer.HeaderSerializeCalled.Should().BeTrue(); + serializer.StandardSerializeCalled.Should().BeFalse(); + } + + [Fact] + public void Serialize_without_header_aware_serializer_falls_back() + { + var serializer = new TrackingSerializer(); + var buffer = new NatsPooledBufferWriter(256); + var headers = new NatsHeaders { { "X-Test", "value" } }; + + ((INatsSerialize)serializer).Serialize(buffer, "test", headers); + + serializer.StandardSerializeCalled.Should().BeTrue(); + } + + [Fact] + public void Serialize_with_null_headers_skips_header_overload() + { + var serializer = new TrackingSerializerWithHeaders(); + var buffer = new NatsPooledBufferWriter(256); + + ((INatsSerialize)serializer).Serialize(buffer, "test", null); + + serializer.HeaderSerializeCalled.Should().BeFalse(); + serializer.StandardSerializeCalled.Should().BeTrue(); + } + + [Fact] + public void Deserialize_with_header_aware_deserializer_calls_header_overload() + { + var deserializer = new TrackingDeserializerWithHeaders(); + var buffer = new ReadOnlySequence(new byte[] { 1 }); + var headers = new NatsHeaders { { "X-Test", "value" } }; + + ((INatsDeserialize)deserializer).Deserialize(buffer, headers); + + deserializer.HeaderDeserializeCalled.Should().BeTrue(); + deserializer.StandardDeserializeCalled.Should().BeFalse(); + } + + [Fact] + public void Deserialize_without_header_aware_deserializer_falls_back() + { + var deserializer = new TrackingDeserializer(); + var buffer = new ReadOnlySequence(new byte[] { 1 }); + var headers = new NatsHeaders { { "X-Test", "value" } }; + + ((INatsDeserialize)deserializer).Deserialize(buffer, headers); + + deserializer.StandardDeserializeCalled.Should().BeTrue(); + } + + [Fact] + public void Deserialize_with_null_headers_still_calls_header_overload() + { + var deserializer = new TrackingDeserializerWithHeaders(); + var buffer = new ReadOnlySequence(new byte[] { 1 }); + + ((INatsDeserialize)deserializer).Deserialize(buffer, null); + + deserializer.HeaderDeserializeCalled.Should().BeTrue(); + deserializer.StandardDeserializeCalled.Should().BeFalse(); + } + + private class TrackingSerializer : INatsSerialize + { + public bool StandardSerializeCalled { get; private set; } + + public void Serialize(IBufferWriter bufferWriter, string value) => + StandardSerializeCalled = true; + } + + private class TrackingSerializerWithHeaders : INatsSerialize, INatsSerializeWithHeaders + { + public bool StandardSerializeCalled { get; private set; } + + public bool HeaderSerializeCalled { get; private set; } + + public void Serialize(IBufferWriter bufferWriter, string value) => + StandardSerializeCalled = true; + + public void Serialize(IBufferWriter bufferWriter, string value, INatsHeaders? headers) => + HeaderSerializeCalled = true; + } + + private class TrackingDeserializer : INatsDeserialize + { + public bool StandardDeserializeCalled { get; private set; } + + public string? Deserialize(in ReadOnlySequence buffer) + { + StandardDeserializeCalled = true; + return null; + } + } + + private class TrackingDeserializerWithHeaders : INatsDeserialize, INatsDeserializeWithHeaders + { + public bool StandardDeserializeCalled { get; private set; } + + public bool HeaderDeserializeCalled { get; private set; } + + public string? Deserialize(in ReadOnlySequence buffer) + { + StandardDeserializeCalled = true; + return null; + } + + public string? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + { + HeaderDeserializeCalled = true; + return null; + } + } +} From 207eabcb4945b7e37182f6a2782dd6c588e2fc2a Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 1 Apr 2026 14:14:24 +0100 Subject: [PATCH 12/20] serializer: replace WithHeaders with WithContext interfaces Replace INatsSerializeWithHeaders/INatsDeserializeWithHeaders with INatsSerializeWithContext/INatsDeserializeWithContext, passing a NatsMsgContext struct that carries Subject, ReplyTo, and Headers. This gives serializers access to the full message envelope, enabling subject-based dispatch and reply-to inspection without requiring further interface changes in the future. Also fixes the null-headers asymmetry in the extension methods: both Serialize and Deserialize now always dispatch to the context overload when the interface is implemented, regardless of whether Headers is null. --- .../INatsSerialize.cs | 38 +++++------ .../NatsMsgContext.cs | 16 +++++ src/NATS.Client.Abstractions/netstandard.cs | 12 ++++ .../Commands/CommandWriter.cs | 2 +- src/NATS.Client.Core/NatsMsg.cs | 4 +- tests/NATS.Client.CheckAbi/Program.cs | 5 +- tests/NATS.Client.Core.Tests/NatsMsgTests.cs | 10 +-- .../NatsJsonSerializerTests.cs | 10 +-- .../NatsSerializationExtensionsTests.cs | 68 ++++++++++--------- .../EnumJsonTests.cs | 60 ++++++++-------- .../JetStreamApiSerializerTest.cs | 6 +- .../ParseJsonTests.cs | 6 +- .../TimeSpanJsonTests.cs | 56 +++++++-------- .../Advanced/SerializationPage.cs | 14 ++-- 14 files changed, 169 insertions(+), 138 deletions(-) create mode 100644 src/NATS.Client.Abstractions/NatsMsgContext.cs create mode 100644 src/NATS.Client.Abstractions/netstandard.cs diff --git a/src/NATS.Client.Abstractions/INatsSerialize.cs b/src/NATS.Client.Abstractions/INatsSerialize.cs index e958823e2..f1a964b75 100644 --- a/src/NATS.Client.Abstractions/INatsSerialize.cs +++ b/src/NATS.Client.Abstractions/INatsSerialize.cs @@ -44,33 +44,33 @@ public interface INatsDeserialize } /// -/// Extended serializer interface that supports sending headers during serialization. +/// Extended serializer interface with access to message context during serialization. /// /// Serialized object type -public interface INatsSerializeWithHeaders : INatsSerialize +public interface INatsSerializeWithContext : INatsSerialize { /// - /// Serialize value to buffer with headers. + /// Serialize value to buffer with message context. /// /// Buffer to write the serialized data. /// Object to be serialized. - /// Optional NATS headers associated with the message. - void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers); + /// Message envelope metadata. + void Serialize(IBufferWriter bufferWriter, T value, in NatsMsgContext context); } /// -/// Extended deserializer interface that supports receiving headers during deserialization. +/// Extended deserializer interface with access to message context during deserialization. /// /// Deserialized object type -public interface INatsDeserializeWithHeaders : INatsDeserialize +public interface INatsDeserializeWithContext : INatsDeserialize { /// - /// Deserialize value from buffer with headers. + /// Deserialize value from buffer with message context. /// /// Buffer with the serialized data. - /// Optional NATS headers associated with the message. + /// Message envelope metadata. /// Deserialized object - T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers); + T? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context); } /// @@ -84,18 +84,18 @@ public interface INatsSerializerRegistry } /// -/// Extension methods to support header-aware serialization with fallback to standard serialization. +/// Extension methods to support context-aware serialization with fallback to standard serialization. /// public static class NatsSerializationExtensions { /// - /// Serializes the value with header support, falling back to standard serialization if headers are not supported. + /// Serializes the value with message context, falling back to standard serialization if not supported. /// - public static void Serialize(this INatsSerialize serializer, IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public static void Serialize(this INatsSerialize serializer, IBufferWriter bufferWriter, T value, in NatsMsgContext context) { - if (headers != null && serializer is INatsSerializeWithHeaders withHeaders) + if (serializer is INatsSerializeWithContext withContext) { - withHeaders.Serialize(bufferWriter, value, headers); + withContext.Serialize(bufferWriter, value, in context); return; } @@ -103,12 +103,12 @@ public static void Serialize(this INatsSerialize serializer, IBufferWriter } /// - /// Deserializes the value with header support, falling back to standard deserialization if headers are not supported. + /// Deserializes the value with message context, falling back to standard deserialization if not supported. /// - public static T? Deserialize(this INatsDeserialize deserializer, in ReadOnlySequence buffer, INatsHeaders? headers) + public static T? Deserialize(this INatsDeserialize deserializer, in ReadOnlySequence buffer, in NatsMsgContext context) { - if (deserializer is INatsDeserializeWithHeaders withHeaders) - return withHeaders.Deserialize(buffer, headers); + if (deserializer is INatsDeserializeWithContext withContext) + return withContext.Deserialize(buffer, in context); return deserializer.Deserialize(buffer); } diff --git a/src/NATS.Client.Abstractions/NatsMsgContext.cs b/src/NATS.Client.Abstractions/NatsMsgContext.cs new file mode 100644 index 000000000..742f03b52 --- /dev/null +++ b/src/NATS.Client.Abstractions/NatsMsgContext.cs @@ -0,0 +1,16 @@ +namespace NATS.Client.Core; + +/// +/// Provides message envelope metadata available during serialization and deserialization. +/// +public readonly struct NatsMsgContext +{ + /// Subject the message was published to. + public string Subject { get; init; } + + /// Optional reply-to subject. + public string? ReplyTo { get; init; } + + /// Optional message headers. + public INatsHeaders? Headers { get; init; } +} diff --git a/src/NATS.Client.Abstractions/netstandard.cs b/src/NATS.Client.Abstractions/netstandard.cs new file mode 100644 index 000000000..c0115cfdb --- /dev/null +++ b/src/NATS.Client.Abstractions/netstandard.cs @@ -0,0 +1,12 @@ +#pragma warning disable SA1403 +#pragma warning disable SA1204 + +// Enable init-only setters on netstandard targets +#if NETSTANDARD +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} +#endif diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 434204882..79fe2196d 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -335,7 +335,7 @@ public ValueTask PublishAsync(string subject, T? value, NatsHeaders? headers, try { if (value != null) - serializer.Serialize(payloadBuffer, value, headers); + serializer.Serialize(payloadBuffer, value, new NatsMsgContext { Subject = subject, ReplyTo = replyTo, Headers = headers }); if (headers != null) _headerWriter.Write(headersBuffer!, headers); diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index 4a3b32e28..dd1749e2a 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -401,7 +401,7 @@ public static NatsMsg Build( { try { - data = serializer.Deserialize(payloadBuffer, headers); + data = serializer.Deserialize(payloadBuffer, new NatsMsgContext { Subject = subject, ReplyTo = replyTo, Headers = headers }); } catch (Exception e) { @@ -519,7 +519,7 @@ public NatsMsg Msg if (Serializer != null && Data != null) { var bufferWriter = new NatsPooledBufferWriter(SerializationBufferSize); - Serializer.Serialize(bufferWriter, Data, Headers); + Serializer.Serialize(bufferWriter, Data, new NatsMsgContext { Subject = Subject, ReplyTo = ReplyTo, Headers = Headers }); size = bufferWriter.WrittenMemory.Length; } diff --git a/tests/NATS.Client.CheckAbi/Program.cs b/tests/NATS.Client.CheckAbi/Program.cs index 74e615624..c0b376685 100644 --- a/tests/NATS.Client.CheckAbi/Program.cs +++ b/tests/NATS.Client.CheckAbi/Program.cs @@ -20,8 +20,9 @@ void Check(string name, string expected, string actual) Check("INatsDeserialize<>", "NATS.Client.Abstractions", typeof(INatsDeserialize<>).Assembly.GetName().Name!); Check("INatsSerializer<>", "NATS.Client.Abstractions", typeof(INatsSerializer<>).Assembly.GetName().Name!); Check("INatsSerializerRegistry", "NATS.Client.Abstractions", typeof(INatsSerializerRegistry).Assembly.GetName().Name!); -Check("INatsSerializeWithHeaders<>", "NATS.Client.Abstractions", typeof(INatsSerializeWithHeaders<>).Assembly.GetName().Name!); -Check("INatsDeserializeWithHeaders<>", "NATS.Client.Abstractions", typeof(INatsDeserializeWithHeaders<>).Assembly.GetName().Name!); +Check("INatsSerializeWithContext<>", "NATS.Client.Abstractions", typeof(INatsSerializeWithContext<>).Assembly.GetName().Name!); +Check("INatsDeserializeWithContext<>", "NATS.Client.Abstractions", typeof(INatsDeserializeWithContext<>).Assembly.GetName().Name!); +Check("NatsMsgContext", "NATS.Client.Abstractions", typeof(NatsMsgContext).Assembly.GetName().Name!); Console.WriteLine(); Console.WriteLine("Types as seen by TransientLib (compiled against 2.6.0):"); diff --git a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs index 1b5155d73..a1c0bd3be 100644 --- a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs +++ b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs @@ -109,7 +109,7 @@ public void Create_nats_msg_by_object_with_header_aware_serializer() var msg = builder.Msg; var bufferWriter = new NatsPooledBufferWriter(256); - ((INatsSerializeWithHeaders)serializer).Serialize(bufferWriter, data, headers); + ((INatsSerializeWithContext)serializer).Serialize(bufferWriter, data, new NatsMsgContext { Subject = subject, ReplyTo = replyTo, Headers = headers }); var serializedSize = bufferWriter.WrittenCount; var expectedSize = subject.Length + (replyTo?.Length ?? 0) + headers.GetBytesLength() + serializedSize; @@ -144,7 +144,7 @@ private class TestData public string Name { get; set; } = null!; } - private class HeaderAwareSerializer : INatsSerializer, INatsSerializeWithHeaders, INatsDeserializeWithHeaders + private class HeaderAwareSerializer : INatsSerializer, INatsSerializeWithContext, INatsDeserializeWithContext { private readonly NatsJsonSerializer _inner = new(); @@ -152,10 +152,10 @@ private class HeaderAwareSerializer : INatsSerializer, INatsSerializeWithH public T? Deserialize(in ReadOnlySequence buffer) => _inner.Deserialize(buffer); - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value, in NatsMsgContext context) { // Write a header-based prefix before the JSON payload - if (headers != null && headers.TryGetValue("X-Type", out var values)) + if (context.Headers != null && context.Headers.TryGetValue("X-Type", out var values)) { var prefix = Encoding.UTF8.GetBytes(values.ToString() + ":"); var span = bufferWriter.GetSpan(prefix.Length); @@ -166,7 +166,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h _inner.Serialize(bufferWriter, value); } - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) => + public T? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context) => _inner.Deserialize(buffer); public INatsSerializer CombineWith(INatsSerializer next) => this; diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs b/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs index 4aa9291b5..166a4f8ec 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsJsonSerializerTests.cs @@ -24,11 +24,11 @@ public void RoundTrip_RequiredNullableProperty_ShouldSucceed() var bufferWriter = new ArrayBufferWriter(); // Act - Serialize - serializer.Serialize(bufferWriter, obj, null); + serializer.Serialize(bufferWriter, obj, default); // Deserialize back var buffer = new ReadOnlySequence(bufferWriter.WrittenMemory); - var result = serializer.Deserialize(buffer, null); + var result = serializer.Deserialize(buffer, default); // Assert - Round trip should succeed Assert.NotNull(result); @@ -44,7 +44,7 @@ public void Deserialize_RequiredNullableProperty_WithJsonSerializerOptionsDefaul var buffer = new ReadOnlySequence(json); // Act - var result = serializer.Deserialize(buffer, null); + var result = serializer.Deserialize(buffer, default); // Assert - This should work fine Assert.NotNull(result); @@ -60,7 +60,7 @@ public void RoundTrip_RequiredNullableProperty_WithJsonSerializerOptionsDefault_ var bufferWriter = new ArrayBufferWriter(); // Act - Serialize - serializer.Serialize(bufferWriter, obj, null); + serializer.Serialize(bufferWriter, obj, default); var json = Encoding.UTF8.GetString(bufferWriter.WrittenSpan); // With default options, null is included in JSON @@ -68,7 +68,7 @@ public void RoundTrip_RequiredNullableProperty_WithJsonSerializerOptionsDefault_ // Deserialize back var buffer = new ReadOnlySequence(bufferWriter.WrittenMemory); - var result = serializer.Deserialize(buffer, null); + var result = serializer.Deserialize(buffer, default); // Assert - Round trip succeeds Assert.NotNull(result); diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs b/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs index 22ffeac76..ad16643f4 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs @@ -5,76 +5,78 @@ namespace NATS.Client.CoreUnit.Tests; public class NatsSerializationExtensionsTests { [Fact] - public void Serialize_with_header_aware_serializer_calls_header_overload() + public void Serialize_with_context_aware_serializer_calls_context_overload() { - var serializer = new TrackingSerializerWithHeaders(); + var serializer = new TrackingSerializerWithContext(); var buffer = new NatsPooledBufferWriter(256); - var headers = new NatsHeaders { { "X-Test", "value" } }; + var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; - ((INatsSerialize)serializer).Serialize(buffer, "test", headers); + ((INatsSerialize)serializer).Serialize(buffer, "test", in context); - serializer.HeaderSerializeCalled.Should().BeTrue(); + serializer.ContextSerializeCalled.Should().BeTrue(); serializer.StandardSerializeCalled.Should().BeFalse(); } [Fact] - public void Serialize_without_header_aware_serializer_falls_back() + public void Serialize_without_context_aware_serializer_falls_back() { var serializer = new TrackingSerializer(); var buffer = new NatsPooledBufferWriter(256); - var headers = new NatsHeaders { { "X-Test", "value" } }; + var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; - ((INatsSerialize)serializer).Serialize(buffer, "test", headers); + ((INatsSerialize)serializer).Serialize(buffer, "test", in context); serializer.StandardSerializeCalled.Should().BeTrue(); } [Fact] - public void Serialize_with_null_headers_skips_header_overload() + public void Serialize_with_null_headers_still_calls_context_overload() { - var serializer = new TrackingSerializerWithHeaders(); + var serializer = new TrackingSerializerWithContext(); var buffer = new NatsPooledBufferWriter(256); + var context = new NatsMsgContext { Subject = "test" }; - ((INatsSerialize)serializer).Serialize(buffer, "test", null); + ((INatsSerialize)serializer).Serialize(buffer, "test", in context); - serializer.HeaderSerializeCalled.Should().BeFalse(); - serializer.StandardSerializeCalled.Should().BeTrue(); + serializer.ContextSerializeCalled.Should().BeTrue(); + serializer.StandardSerializeCalled.Should().BeFalse(); } [Fact] - public void Deserialize_with_header_aware_deserializer_calls_header_overload() + public void Deserialize_with_context_aware_deserializer_calls_context_overload() { - var deserializer = new TrackingDeserializerWithHeaders(); + var deserializer = new TrackingDeserializerWithContext(); var buffer = new ReadOnlySequence(new byte[] { 1 }); - var headers = new NatsHeaders { { "X-Test", "value" } }; + var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; - ((INatsDeserialize)deserializer).Deserialize(buffer, headers); + ((INatsDeserialize)deserializer).Deserialize(buffer, in context); - deserializer.HeaderDeserializeCalled.Should().BeTrue(); + deserializer.ContextDeserializeCalled.Should().BeTrue(); deserializer.StandardDeserializeCalled.Should().BeFalse(); } [Fact] - public void Deserialize_without_header_aware_deserializer_falls_back() + public void Deserialize_without_context_aware_deserializer_falls_back() { var deserializer = new TrackingDeserializer(); var buffer = new ReadOnlySequence(new byte[] { 1 }); - var headers = new NatsHeaders { { "X-Test", "value" } }; + var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; - ((INatsDeserialize)deserializer).Deserialize(buffer, headers); + ((INatsDeserialize)deserializer).Deserialize(buffer, in context); deserializer.StandardDeserializeCalled.Should().BeTrue(); } [Fact] - public void Deserialize_with_null_headers_still_calls_header_overload() + public void Deserialize_with_null_headers_still_calls_context_overload() { - var deserializer = new TrackingDeserializerWithHeaders(); + var deserializer = new TrackingDeserializerWithContext(); var buffer = new ReadOnlySequence(new byte[] { 1 }); + var context = new NatsMsgContext { Subject = "test" }; - ((INatsDeserialize)deserializer).Deserialize(buffer, null); + ((INatsDeserialize)deserializer).Deserialize(buffer, in context); - deserializer.HeaderDeserializeCalled.Should().BeTrue(); + deserializer.ContextDeserializeCalled.Should().BeTrue(); deserializer.StandardDeserializeCalled.Should().BeFalse(); } @@ -86,17 +88,17 @@ public void Serialize(IBufferWriter bufferWriter, string value) => StandardSerializeCalled = true; } - private class TrackingSerializerWithHeaders : INatsSerialize, INatsSerializeWithHeaders + private class TrackingSerializerWithContext : INatsSerialize, INatsSerializeWithContext { public bool StandardSerializeCalled { get; private set; } - public bool HeaderSerializeCalled { get; private set; } + public bool ContextSerializeCalled { get; private set; } public void Serialize(IBufferWriter bufferWriter, string value) => StandardSerializeCalled = true; - public void Serialize(IBufferWriter bufferWriter, string value, INatsHeaders? headers) => - HeaderSerializeCalled = true; + public void Serialize(IBufferWriter bufferWriter, string value, in NatsMsgContext context) => + ContextSerializeCalled = true; } private class TrackingDeserializer : INatsDeserialize @@ -110,11 +112,11 @@ private class TrackingDeserializer : INatsDeserialize } } - private class TrackingDeserializerWithHeaders : INatsDeserialize, INatsDeserializeWithHeaders + private class TrackingDeserializerWithContext : INatsDeserialize, INatsDeserializeWithContext { public bool StandardDeserializeCalled { get; private set; } - public bool HeaderDeserializeCalled { get; private set; } + public bool ContextDeserializeCalled { get; private set; } public string? Deserialize(in ReadOnlySequence buffer) { @@ -122,9 +124,9 @@ private class TrackingDeserializerWithHeaders : INatsDeserialize, INatsD return null; } - public string? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public string? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context) { - HeaderDeserializeCalled = true; + ContextDeserializeCalled = true; return null; } } diff --git a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs index ccd1baea0..b3eab5190 100644 --- a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs @@ -20,12 +20,12 @@ public void ConsumerConfigAckPolicy_test(ConsumerConfigAckPolicy value, string e var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { AckPolicy = value }, null); + serializer.Serialize(bw, new ConsumerConfig { AckPolicy = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.AckPolicy); } @@ -42,12 +42,12 @@ public void ConsumerConfigDeliverPolicy_test(ConsumerConfigDeliverPolicy value, var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { DeliverPolicy = value }, null); + serializer.Serialize(bw, new ConsumerConfig { DeliverPolicy = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.DeliverPolicy); } @@ -60,12 +60,12 @@ public void ConsumerConfigReplayPolicy_test(ConsumerConfigReplayPolicy value, st var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { ReplayPolicy = value }, null); + serializer.Serialize(bw, new ConsumerConfig { ReplayPolicy = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.ReplayPolicy); } @@ -78,7 +78,7 @@ public void StreamConfigCompression_test(StreamConfigCompression value, string e var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Compression = value }, null); + serializer.Serialize(bw, new StreamConfig { Compression = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); if (value == default) @@ -86,7 +86,7 @@ public void StreamConfigCompression_test(StreamConfigCompression value, string e else Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.Compression); } @@ -99,7 +99,7 @@ public void StreamConfigDiscard_test(StreamConfigDiscard value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Discard = value }, null); + serializer.Serialize(bw, new StreamConfig { Discard = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); if (value == default) @@ -107,7 +107,7 @@ public void StreamConfigDiscard_test(StreamConfigDiscard value, string expected) else Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.Discard); } @@ -121,12 +121,12 @@ public void StreamConfigRetention_test(StreamConfigRetention value, string expec var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Retention = value }, null); + serializer.Serialize(bw, new StreamConfig { Retention = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.Retention); } @@ -139,12 +139,12 @@ public void StreamConfigStorage_test(StreamConfigStorage value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { Storage = value }, null); + serializer.Serialize(bw, new StreamConfig { Storage = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.Storage); } @@ -158,12 +158,12 @@ public void ConsumerCreateRequestAction_Test(ConsumerCreateAction value, string var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerCreateRequest { Action = value, StreamName = string.Empty }, null); + serializer.Serialize(bw, new ConsumerCreateRequest { Action = value, StreamName = string.Empty }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.Action); } @@ -176,13 +176,13 @@ public void StreamConfigPersistMode_null_not_serialized() // When PersistMode is null (not explicitly set), it should not be included in JSON var bw = new NatsBufferWriter(); var config = new StreamConfig { PersistMode = null }; - serializer.Serialize(bw, config, null); + serializer.Serialize(bw, config, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.DoesNotContain("persist_mode", json); // Deserialize and verify it remains null - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Null(result.PersistMode); } @@ -197,13 +197,13 @@ public void StreamConfigPersistMode_explicit_value_serialized(StreamConfigPersis // When PersistMode is explicitly set (even to Default), it should be included in JSON var bw = new NatsBufferWriter(); var config = new StreamConfig { PersistMode = value }; - serializer.Serialize(bw, config, null); + serializer.Serialize(bw, config, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); // Deserialize and verify the value is preserved - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.PersistMode); } @@ -216,39 +216,39 @@ public void StreamConfigPersistMode_roundtrip_preserves_server_values() // Test case 1: Server returns "default" - should preserve it var jsonWithDefault = "{\"persist_mode\":\"default\",\"retention\":\"limits\",\"storage\":\"file\"}"; var bytes = Encoding.UTF8.GetBytes(jsonWithDefault); - var configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), null); + var configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), default); Assert.NotNull(configFromServer); Assert.Equal(StreamConfigPersistMode.Default, configFromServer.PersistMode); // When we serialize it again, it should include persist_mode var bw1 = new NatsBufferWriter(); - serializer.Serialize(bw1, configFromServer, null); + serializer.Serialize(bw1, configFromServer, default); var jsonOut1 = Encoding.UTF8.GetString(bw1.WrittenSpan.ToArray()); Assert.Contains("\"persist_mode\":\"default\"", jsonOut1); // Test case 2: Server returns "async" - should preserve it var jsonWithAsync = "{\"persist_mode\":\"async\",\"retention\":\"limits\",\"storage\":\"file\"}"; bytes = Encoding.UTF8.GetBytes(jsonWithAsync); - configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), null); + configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), default); Assert.NotNull(configFromServer); Assert.Equal(StreamConfigPersistMode.Async, configFromServer.PersistMode); // When we serialize it again, it should include persist_mode var bw2 = new NatsBufferWriter(); - serializer.Serialize(bw2, configFromServer, null); + serializer.Serialize(bw2, configFromServer, default); var jsonOut2 = Encoding.UTF8.GetString(bw2.WrittenSpan.ToArray()); Assert.Contains("\"persist_mode\":\"async\"", jsonOut2); // Test case 3: Server doesn't return persist_mode - should remain null var jsonWithoutPersistMode = "{\"retention\":\"limits\",\"storage\":\"file\"}"; bytes = Encoding.UTF8.GetBytes(jsonWithoutPersistMode); - configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), null); + configFromServer = serializer.Deserialize(new ReadOnlySequence(bytes), default); Assert.NotNull(configFromServer); Assert.Null(configFromServer.PersistMode); // When we serialize it again, it should NOT include persist_mode var bw3 = new NatsBufferWriter(); - serializer.Serialize(bw3, configFromServer, null); + serializer.Serialize(bw3, configFromServer, default); var jsonOut3 = Encoding.UTF8.GetString(bw3.WrittenSpan.ToArray()); Assert.DoesNotContain("persist_mode", jsonOut3); } @@ -262,12 +262,12 @@ public void ConsumerConfigPriorityPolicy_test(ConsumerConfigPriorityPolicy value var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = value }, null); + serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = value }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(value, result.PriorityPolicy); } @@ -278,12 +278,12 @@ public void ConsumerConfigPriorityPolicy_null_test() var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = null }, null); + serializer.Serialize(bw, new ConsumerConfig { PriorityPolicy = null }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.DoesNotContain("priority_policy", json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Null(result.PriorityPolicy); } diff --git a/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs b/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs index 971381a32..58c0cdb2b 100644 --- a/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs +++ b/tests/NATS.Client.JetStream.Tests/JetStreamApiSerializerTest.cs @@ -101,7 +101,7 @@ await Retry.Until( public void Deserialize_value() { var serializer = NatsJSJsonDocumentSerializer.Default; - var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"memory":1}""")), null); + var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"memory":1}""")), default); result.Value.Memory.Should().Be(1); } @@ -109,7 +109,7 @@ public void Deserialize_value() public void Deserialize_empty_buffer() { var serializer = NatsJSJsonDocumentSerializer.Default; - var result = serializer.Deserialize(ReadOnlySequence.Empty, null); + var result = serializer.Deserialize(ReadOnlySequence.Empty, default); result.Exception.Message.Should().Be("Buffer is empty"); } @@ -117,7 +117,7 @@ public void Deserialize_empty_buffer() public void Deserialize_error() { var serializer = NatsJSJsonDocumentSerializer.Default; - var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"error":{"code":2}}""")), null); + var result = serializer.Deserialize(new ReadOnlySequence(Encoding.UTF8.GetBytes("""{"error":{"code":2}}""")), default); result.Error.Code.Should().Be(2); } } diff --git a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs index bd8cf6139..b0a9acd57 100644 --- a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs @@ -24,12 +24,12 @@ public void Placement_properties_should_be_optional() var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new Placement(), null); + serializer.Serialize(bw, new Placement(), default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Equal("{}", json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Null(result.Cluster); Assert.Null(result.Tags); @@ -41,7 +41,7 @@ public void Default_consumer_ack_policy_should_be_explicit() var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig(), null); + serializer.Serialize(bw, new ConsumerConfig(), default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches("\"ack_policy\":\"explicit\"", json); diff --git a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs index 4e4673798..ff1c36f04 100644 --- a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs @@ -48,12 +48,12 @@ public void ConsumerConfigAckWait_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { AckWait = time }, null); + serializer.Serialize(bw, new ConsumerConfig { AckWait = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.AckWait); } @@ -68,12 +68,12 @@ public void ConsumerConfigIdleHeartbeat_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { IdleHeartbeat = time }, null); + serializer.Serialize(bw, new ConsumerConfig { IdleHeartbeat = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.IdleHeartbeat); } @@ -88,12 +88,12 @@ public void ConsumerConfigInactiveThreshold_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { InactiveThreshold = time }, null); + serializer.Serialize(bw, new ConsumerConfig { InactiveThreshold = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.InactiveThreshold); } @@ -108,12 +108,12 @@ public void ConsumerConfigMaxExpires_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { MaxExpires = time }, null); + serializer.Serialize(bw, new ConsumerConfig { MaxExpires = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.MaxExpires); } @@ -128,12 +128,12 @@ public void ConsumerGetnextRequestExpires_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerGetnextRequest { Expires = time }, null); + serializer.Serialize(bw, new ConsumerGetnextRequest { Expires = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.Expires); } @@ -148,12 +148,12 @@ public void ConsumerGetnextRequestIdleHeartbeat_test(string value, string expect var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerGetnextRequest { IdleHeartbeat = time }, null); + serializer.Serialize(bw, new ConsumerGetnextRequest { IdleHeartbeat = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.IdleHeartbeat); } @@ -168,12 +168,12 @@ public void PeerInfoActive_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new PeerInfo { Name = "test", Active = time }, null); + serializer.Serialize(bw, new PeerInfo { Name = "test", Active = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.Active); } @@ -188,12 +188,12 @@ public void StreamConfigMaxAge_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { MaxAge = time }, null); + serializer.Serialize(bw, new StreamConfig { MaxAge = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.MaxAge); } @@ -208,12 +208,12 @@ public void StreamConfigDuplicateWindow_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamConfig { DuplicateWindow = time }, null); + serializer.Serialize(bw, new StreamConfig { DuplicateWindow = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.DuplicateWindow); } @@ -228,12 +228,12 @@ public void StreamSourceInfoActive_test(string value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = time }, null); + serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.Active); } @@ -246,12 +246,12 @@ public void StreamSourceInfoActive_minus_one_indicates_no_activity() var serializer = NatsJSJsonSerializer.Default; var jsonWithMinusOne = """{"name":"test","lag":0,"active":-1}"""u8; - var resultMinusOne = serializer.Deserialize(new ReadOnlySequence(jsonWithMinusOne.ToArray()), null); + var resultMinusOne = serializer.Deserialize(new ReadOnlySequence(jsonWithMinusOne.ToArray()), default); Assert.NotNull(resultMinusOne); Assert.Null(resultMinusOne.Active); var jsonWithZero = """{"name":"test","lag":0,"active":0}"""u8; - var resultZero = serializer.Deserialize(new ReadOnlySequence(jsonWithZero.ToArray()), null); + var resultZero = serializer.Deserialize(new ReadOnlySequence(jsonWithZero.ToArray()), default); Assert.NotNull(resultZero); Assert.Equal(TimeSpan.Zero, resultZero.Active); @@ -267,12 +267,12 @@ public void StreamSourceInfoActive_null_roundtrip() // Serialize null -> should produce -1 var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = null }, null); + serializer.Serialize(bw, new StreamSourceInfo { Name = "test", Active = null }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Contains("\"active\":-1", json); // Deserialize -1 -> should produce null - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Null(result.Active); } @@ -288,7 +288,7 @@ public void ConsumerInfoPauseRemaining_test(string? value, string expected) var serializer = NatsJSJsonSerializer.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerInfo { StreamName = "test", Name = "test", PauseRemaining = time }, null); + serializer.Serialize(bw, new ConsumerInfo { StreamName = "test", Name = "test", PauseRemaining = time }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); if (value != null) @@ -301,7 +301,7 @@ public void ConsumerInfoPauseRemaining_test(string? value, string expected) Assert.DoesNotMatch(expected, "pause_remaining"); } - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(time, result.PauseRemaining); } @@ -316,12 +316,12 @@ public void ConsumerConfigBackoff_test(int minimumFrameworkVersion, List.Default; var bw = new NatsBufferWriter(); - serializer.Serialize(bw, new ConsumerConfig { Backoff = timeSpans }, null); + serializer.Serialize(bw, new ConsumerConfig { Backoff = timeSpans }, default); var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); - var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), null); + var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory), default); Assert.NotNull(result); Assert.Equal(timeSpans, result.Backoff); } diff --git a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index a4cabc153..51359dd0f 100644 --- a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -280,7 +280,7 @@ public class MixedSerializerRegistry : INatsSerializerRegistry #endregion #region header-aware-serializer -public class MyHeaderAwareSerializer : INatsSerializer, INatsSerializeWithHeaders, INatsDeserializeWithHeaders +public class MyHeaderAwareSerializer : INatsSerializer, INatsSerializeWithContext, INatsDeserializeWithContext { private readonly NatsJsonContextSerializer _jsonSerializer; @@ -293,22 +293,22 @@ public MyHeaderAwareSerializer(JsonSerializerContext context) public T? Deserialize(in ReadOnlySequence buffer) => _jsonSerializer.Deserialize(buffer); - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value, in NatsMsgContext context) { // Set a content-type header so the deserializer knows the format - if (headers is NatsHeaders mutableHeaders) + if (context.Headers != null) { - mutableHeaders["Content-Type"] = "application/json"; + context.Headers["Content-Type"] = "application/json"; } _jsonSerializer.Serialize(bufferWriter, value); } - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context) { // Read the content-type header to determine how to deserialize - if (headers != null - && headers.TryGetValue("Content-Type", out StringValues contentType) + if (context.Headers != null + && context.Headers.TryGetValue("Content-Type", out StringValues contentType) && contentType.ToString() == "application/json") { return _jsonSerializer.Deserialize(buffer); From 71a40f85a9921d7a7ccb3e9c1e3c680b0eeda800 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 2 Apr 2026 15:00:52 +0100 Subject: [PATCH 13/20] Revert "Improve NuidTests stability" This reverts commit 42b8cbd4b7879f90f0961d302538b16d57a7898d. --- tests/NATS.Client.CoreUnit.Tests/NuidTests.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs index f09fa29aa..8ffeaf5f8 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs @@ -137,7 +137,7 @@ public void GetNextNuid_PrefixRenewed_Char() }); executionThread.Start(); - Assert.True(executionThread.Join(10_000), $"Thread {executionThread.ManagedThreadId} did not complete within the timeout."); + executionThread.Join(10_000); // Assert Assert.Equal(2, Interlocked.CompareExchange(ref _result, 0, 0)); @@ -175,7 +175,7 @@ public void InitAndWrite_Char() Volatile.Write(ref completedSuccessfully, didWrite && isMatch); }); t.Start(); - Assert.True(t.Join(10_000), $"Thread {t.ManagedThreadId} did not complete within the timeout."); + t.Join(10_000); Assert.True(completedSuccessfully); } @@ -202,10 +202,7 @@ public void DifferentThreads_DifferentPrefixes() threads.Add(t); } - foreach (var thread in threads) - { - Assert.True(thread.Join(10_000), $"Thread {thread.ManagedThreadId} did not complete within the timeout."); - } + threads.ForEach(t => t.Join(10_000)); // Assert var uniquePrefixes = new HashSet(); From ff71ca5b5cdc77fe4d7f5242265a399b4421181c Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 2 Apr 2026 15:00:56 +0100 Subject: [PATCH 14/20] Revert "Fix NuidTests flap" This reverts commit dcc358847d2c9e4ad5068e74d0fcdfcf36d9b5bb. --- tests/NATS.Client.CoreUnit.Tests/NuidTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs index 8ffeaf5f8..7e1b1005b 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NuidTests.cs @@ -137,7 +137,7 @@ public void GetNextNuid_PrefixRenewed_Char() }); executionThread.Start(); - executionThread.Join(10_000); + executionThread.Join(1_000); // Assert Assert.Equal(2, Interlocked.CompareExchange(ref _result, 0, 0)); @@ -175,7 +175,7 @@ public void InitAndWrite_Char() Volatile.Write(ref completedSuccessfully, didWrite && isMatch); }); t.Start(); - t.Join(10_000); + t.Join(1_000); Assert.True(completedSuccessfully); } @@ -202,7 +202,7 @@ public void DifferentThreads_DifferentPrefixes() threads.Add(t); } - threads.ForEach(t => t.Join(10_000)); + threads.ForEach(t => t.Join(1_000)); // Assert var uniquePrefixes = new HashSet(); From ad54bd93a7de20859d65aaaaf1115fc75c977ced Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 2 Apr 2026 15:11:34 +0100 Subject: [PATCH 15/20] docs: fix stale DIM references and update serialization docs Remove incorrect DIM claims from serialization docs, rewrite the headers section to describe the WithContext interface design. Clean up NativeAot example that had leftover INatsHeaders parameter overloads from the old DIM approach. Restore dropped doc comment on INatsDeserialize.Deserialize. --- examples/Example.NativeAot/Program.cs | 8 ++------ src/NATS.Client.Abstractions/INatsSerialize.cs | 1 + .../documentation/advanced/serialization.md | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/examples/Example.NativeAot/Program.cs b/examples/Example.NativeAot/Program.cs index 75570cf3a..0bb978429 100644 --- a/examples/Example.NativeAot/Program.cs +++ b/examples/Example.NativeAot/Program.cs @@ -201,11 +201,7 @@ public class MyProtoBufSerializer : INatsSerializer { public static readonly INatsSerializer Default = new MyProtoBufSerializer(); - public void Serialize(IBufferWriter bufferWriter, T value) => Serialize(bufferWriter, value, null); - - public T? Deserialize(in ReadOnlySequence buffer) => Deserialize(buffer, null); - - public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? headers) + public void Serialize(IBufferWriter bufferWriter, T value) { if (value is IMessage message) { @@ -217,7 +213,7 @@ public void Serialize(IBufferWriter bufferWriter, T value, INatsHeaders? h } } - public T? Deserialize(in ReadOnlySequence buffer, INatsHeaders? headers) + public T? Deserialize(in ReadOnlySequence buffer) { if (typeof(T) == typeof(Greeting)) { diff --git a/src/NATS.Client.Abstractions/INatsSerialize.cs b/src/NATS.Client.Abstractions/INatsSerialize.cs index f1a964b75..1d0f8a6f9 100644 --- a/src/NATS.Client.Abstractions/INatsSerialize.cs +++ b/src/NATS.Client.Abstractions/INatsSerialize.cs @@ -40,6 +40,7 @@ public interface INatsDeserialize /// Deserialize value from buffer. /// /// Buffer with the serialized data. + /// Deserialized object T? Deserialize(in ReadOnlySequence buffer); } diff --git a/tools/site_src/documentation/advanced/serialization.md b/tools/site_src/documentation/advanced/serialization.md index 339335fe8..d1124c698 100644 --- a/tools/site_src/documentation/advanced/serialization.md +++ b/tools/site_src/documentation/advanced/serialization.md @@ -75,10 +75,6 @@ You can also set the serializer for a specific subscription or publish call: You can also provide your own serializer by implementing the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) interface. This is useful if you need to support a custom serialization format or if you need to support multiple serialization formats. -On .NET 8+, you only need to implement one overload of `Serialize` and `Deserialize` thanks to default interface methods (DIMs). -If you implement the headers overload, the old headerless overload is provided automatically (and vice versa). -On older targets (netstandard2.0/2.1), both overloads must be implemented explicitly. - Here is an example of a custom serializer that uses the Google ProtoBuf serializer to serialize and deserialize: [!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#custom-serializer)] @@ -87,11 +83,17 @@ You can then use the custom serializer as the default for the connection: [!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#custom)] -## Using Headers in Serializers +## Using Message Context in Serializers + +Serializers can opt into receiving message context (subject, reply-to, headers) by implementing +[`INatsSerializeWithContext`](xref:NATS.Client.Core.INatsSerializeWithContext`1) and/or +[`INatsDeserializeWithContext`](xref:NATS.Client.Core.INatsDeserializeWithContext`1). +These interfaces extend the base serialization interfaces, so existing serializers continue to work without changes. +When a context-aware serializer is detected, the library automatically dispatches to the context overload; +otherwise it falls back to the standard method. -The `Serialize` and `Deserialize` methods receive an optional [`INatsHeaders`](xref:NATS.Client.Core.INatsHeaders) parameter, -allowing serializers to read or modify NATS headers during serialization. This can be used for scenarios like -content-type negotiation, encoding metadata, or any other header-driven serialization logic. +This can be used for scenarios like content-type negotiation, subject-based dispatch, encoding metadata, +or any other context-driven serialization logic. Here is an example of a serializer that writes a content-type header during serialization and uses it during deserialization: From 3a54d03bfecdf3be983ef08bec5e5f5e116a59ef Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Tue, 7 Apr 2026 14:00:44 +0100 Subject: [PATCH 16/20] headers: drop read-only state The read-only flag was set on NatsHeaders before passing to publish to catch accidental mutation after publish. It was a weak guarantee since headers are copied to a byte buffer synchronously inside CommandWriter, so post-publish mutation cannot affect wire data anyway. Removing it lets serializers implementing INatsSerializeWithContext mutate headers (e.g. set Content-Type) during serialization, which was otherwise blocked by ThrowIfReadOnly. Telemetry's SetOverrideReadOnly escape hatch is no longer needed and uses the normal indexer instead. --- src/NATS.Client.Core/Internal/Telemetry.cs | 6 +-- .../NatsConnection.Publish.cs | 4 -- src/NATS.Client.Core/NatsHeaders.cs | 47 ++--------------- src/NATS.Client.Core/NatsMsg.cs | 2 - .../NATS.Client.Core2.Tests/SerializerTest.cs | 51 +++++++++++++++++++ .../NatsConnectionTest.Headers.cs | 7 --- 6 files changed, 55 insertions(+), 62 deletions(-) diff --git a/src/NATS.Client.Core/Internal/Telemetry.cs b/src/NATS.Client.Core/Internal/Telemetry.cs index 02944bfba..87fd0bff6 100644 --- a/src/NATS.Client.Core/Internal/Telemetry.cs +++ b/src/NATS.Client.Core/Internal/Telemetry.cs @@ -104,11 +104,7 @@ public static void AddTraceContextHeaders(Activity? activity, ref NatsHeaders? h return; } - // There are cases where headers reused publicly (e.g. JetStream publish retry) - // there may also be cases where application can reuse the same header - // in which case we should still be able to overwrite headers with telemetry fields - // even though headers would be set to readonly before being passed down in publish methods. - headers.SetOverrideReadOnly(fieldName, fieldValue); + headers[fieldName] = fieldValue; }); } diff --git a/src/NATS.Client.Core/NatsConnection.Publish.cs b/src/NATS.Client.Core/NatsConnection.Publish.cs index 68eb59e3d..8dd86e629 100644 --- a/src/NATS.Client.Core/NatsConnection.Publish.cs +++ b/src/NATS.Client.Core/NatsConnection.Publish.cs @@ -19,7 +19,6 @@ public ValueTask PublishAsync(string subject, NatsHeaders? headers = default, st Telemetry.AddTraceContextHeaders(activity, ref headers); try { - headers?.SetReadOnly(); return ConnectionState != NatsConnectionState.Open ? ConnectAndPublishAsync(subject, default, headers, replyTo, NatsRawSerializer.Default, cancellationToken) : CommandWriter.PublishAsync(subject, default, headers, replyTo, NatsRawSerializer.Default, cancellationToken); @@ -31,7 +30,6 @@ public ValueTask PublishAsync(string subject, NatsHeaders? headers = default, st } } - headers?.SetReadOnly(); return ConnectionState != NatsConnectionState.Open ? ConnectAndPublishAsync(subject, default, headers, replyTo, NatsRawSerializer.Default, cancellationToken) : CommandWriter.PublishAsync(subject, default, headers, replyTo, NatsRawSerializer.Default, cancellationToken); @@ -53,7 +51,6 @@ public ValueTask PublishAsync(string subject, T? data, NatsHeaders? headers = try { serializer ??= Opts.SerializerRegistry.GetSerializer(); - headers?.SetReadOnly(); return ConnectionState != NatsConnectionState.Open ? ConnectAndPublishAsync(subject, data, headers, replyTo, serializer, cancellationToken) : CommandWriter.PublishAsync(subject, data, headers, replyTo, serializer, cancellationToken); @@ -66,7 +63,6 @@ public ValueTask PublishAsync(string subject, T? data, NatsHeaders? headers = } serializer ??= Opts.SerializerRegistry.GetSerializer(); - headers?.SetReadOnly(); return ConnectionState != NatsConnectionState.Open ? ConnectAndPublishAsync(subject, data, headers, replyTo, serializer, cancellationToken) : CommandWriter.PublishAsync(subject, data, headers, replyTo, serializer, cancellationToken); diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 284844792..583e1209a 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -70,8 +70,6 @@ public enum Messages private static readonly IEnumerator> EmptyIEnumeratorType = default(Enumerator); private static readonly IEnumerator EmptyIEnumerator = default(Enumerator); - private int _readonly = 0; - public int Version => 1; public int Code { get; internal set; } @@ -144,7 +142,6 @@ public StringValues this[string key] { throw new ArgumentNullException(nameof(key)); } - ThrowIfReadOnly(); if (value.Count == 0) { @@ -161,11 +158,7 @@ public StringValues this[string key] StringValues IDictionary.this[string key] { get { return this[key]; } - set - { - ThrowIfReadOnly(); - this[key] = value; - } + set { this[key] = value; } } /// @@ -175,10 +168,9 @@ StringValues IDictionary.this[string key] public int Count => Store?.Count ?? 0; /// - /// Gets a value that indicates whether the is in read-only mode. + /// Gets a value that indicates whether the is read-only. Always false. /// - /// true if the is in read-only mode; otherwise, false. - public bool IsReadOnly => Volatile.Read(ref _readonly) == 1; + public bool IsReadOnly => false; /// /// Gets the collection of HTTP header names in this instance. @@ -222,7 +214,6 @@ public void Add(KeyValuePair item) { throw new ArgumentException("The key is null"); } - ThrowIfReadOnly(); EnsureStore(1); Store.Add(item.Key, item.Value); } @@ -238,7 +229,6 @@ public void Add(string key, StringValues value) { throw new ArgumentNullException(nameof(key)); } - ThrowIfReadOnly(); EnsureStore(1); Store.Add(key, value); } @@ -248,7 +238,6 @@ public void Add(string key, StringValues value) /// public void Clear() { - ThrowIfReadOnly(); Store?.Clear(); } @@ -322,7 +311,6 @@ public int GetBytesLength(Encoding? encoding = null) /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { - ThrowIfReadOnly(); if (Store == null) { return false; @@ -342,7 +330,6 @@ public bool Remove(KeyValuePair item) /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { - ThrowIfReadOnly(); if (Store == null) { return false; @@ -426,34 +413,6 @@ IEnumerator IEnumerable.GetEnumerator() return Store.GetEnumerator(); } - internal void SetReadOnly() => Interlocked.Exchange(ref _readonly, 1); - - internal void SetOverrideReadOnly(string key, StringValues value) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (value.Count == 0) - { - Store?.Remove(key); - } - else - { - EnsureStore(1); - Store[key] = value; - } - } - - private void ThrowIfReadOnly() - { - if (IsReadOnly) - { - throw new InvalidOperationException("The response headers cannot be modified because the response has already started."); - } - } - /// /// Enumerates a . /// diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index dd1749e2a..c2eaf7d65 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -364,8 +364,6 @@ public static NatsMsg Build( } } - headers?.SetReadOnly(); - var size = subject.Length + (replyTo?.Length ?? 0) + (headersBuffer?.Length ?? 0) diff --git a/tests/NATS.Client.Core2.Tests/SerializerTest.cs b/tests/NATS.Client.Core2.Tests/SerializerTest.cs index fbd987e79..89fedfaa8 100644 --- a/tests/NATS.Client.Core2.Tests/SerializerTest.cs +++ b/tests/NATS.Client.Core2.Tests/SerializerTest.cs @@ -324,6 +324,33 @@ public async Task Deserialize_using_json_stream_serializer_registry() Assert.Equal("two", result2.Data.Name); } + [Fact] + public async Task Serializer_can_mutate_headers_during_serialization() + { + await using var nats = new NatsConnection(new NatsOpts + { + Url = _server.Url, + SerializerRegistry = new HeaderMutatingSerializerRegistry(), + }); + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + var cancellationToken = cts.Token; + + await nats.ConnectAsync(); + + var subject = _server.GetNextId(); + var sub = await nats.SubscribeCoreAsync(subject, cancellationToken: cancellationToken); + await nats.PingAsync(cancellationToken); + + await nats.PublishAsync(subject, "hello", headers: new NatsHeaders(), cancellationToken: cancellationToken); + + var msg = await sub.Msgs.ReadAsync(cancellationToken); + + Assert.Equal("hello", msg.Data); + Assert.NotNull(msg.Headers); + Assert.Equal("application/json", msg.Headers["Content-Type"].ToString()); + } + private static void AssertByteArray(byte[] expected, byte[] actual) { Assert.Equal(expected.Length, actual.Length); @@ -369,6 +396,30 @@ public record TestMessage1(string Name); public record TestMessage2(string Name); +public class HeaderMutatingSerializerRegistry : INatsSerializerRegistry +{ + public INatsSerialize GetSerializer() => new HeaderMutatingSerializer(); + + public INatsDeserialize GetDeserializer() => NatsDefaultSerializer.Default; +} + +public class HeaderMutatingSerializer : INatsSerialize, INatsSerializeWithContext +{ + public void Serialize(IBufferWriter bufferWriter, T value) + { + var bytes = Encoding.UTF8.GetBytes(value?.ToString() ?? string.Empty); + bufferWriter.Write(bytes); + } + + public void Serialize(IBufferWriter bufferWriter, T value, in NatsMsgContext context) + { + if (context.Headers != null) + context.Headers["Content-Type"] = "application/json"; + + Serialize(bufferWriter, value); + } +} + [JsonSerializable(typeof(TestMessage1))] [JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, WriteIndented = false)] internal partial class TestSerializerContext1 : JsonSerializerContext; diff --git a/tests/NATS.Slow.Tests/NatsConnectionTest.Headers.cs b/tests/NATS.Slow.Tests/NatsConnectionTest.Headers.cs index ee3c95332..ebf65275a 100644 --- a/tests/NATS.Slow.Tests/NatsConnectionTest.Headers.cs +++ b/tests/NATS.Slow.Tests/NatsConnectionTest.Headers.cs @@ -37,17 +37,10 @@ await Retry.Until( ["Test-Header-Key"] = "test-header-value", ["Multi"] = new[] { "multi-value-0", "multi-value-1" }, }; - Assert.False(headers.IsReadOnly); // Send with headers await nats.PublishAsync("foo", 100, headers: headers); - Assert.True(headers.IsReadOnly); - Assert.Throws(() => - { - headers["should-not-set"] = "value"; - }); - var msg1 = await signal1; Assert.Equal(100, msg1.Data); Assert.NotNull(msg1.Headers); From 2a28fbb29833cbbc11a538e8385b8434838e0953 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 10 Apr 2026 11:43:20 +0100 Subject: [PATCH 17/20] tests: add net10.0 TFM and improve ABI checks Add net10.0 target to CoreUnit, Core, and Core2 test projects. Add CheckAbiOld control project that runs against the old NuGet version to prove type forwarding works by contrasting assembly versions. Centralize the old NuGet version in Directory.Build.props and make the run script assert loaded assembly versions. --- Directory.Build.props | 3 + NATS.Net.sln | 435 ++++++++++++++++++ tests/NATS.Client.CheckAbi/Program.cs | 12 + tests/NATS.Client.CheckAbi/run-abi-check.sh | 59 ++- .../NATS.Client.CheckAbiOld.csproj | 29 ++ tests/NATS.Client.CheckAbiOld/Program.cs | 29 ++ .../MySerializer.cs | 5 + .../NATS.Client.CheckAbiTransientLib.csproj | 2 +- .../NATS.Client.Core.Tests.csproj | 2 +- .../NATS.Client.Core2.Tests.csproj | 2 +- .../NATS.Client.CoreUnit.Tests.csproj | 2 +- 11 files changed, 567 insertions(+), 13 deletions(-) create mode 100644 tests/NATS.Client.CheckAbiOld/NATS.Client.CheckAbiOld.csproj create mode 100644 tests/NATS.Client.CheckAbiOld/Program.cs diff --git a/Directory.Build.props b/Directory.Build.props index 35d960128..3f3104456 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,6 +9,9 @@ false + + + 2.6.0 diff --git a/NATS.Net.sln b/NATS.Net.sln index 0cd496046..57e4fd5d5 100644 --- a/NATS.Net.sln +++ b/NATS.Net.sln @@ -158,220 +158,654 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.CheckAbiTransie EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.CheckAbi", "tests\NATS.Client.CheckAbi\NATS.Client.CheckAbi.csproj", "{DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.CheckAbiOld", "tests\NATS.Client.CheckAbiOld\NATS.Client.CheckAbiOld.csproj", "{93E92151-28BF-4176-92F4-65E1227C3269}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x64.ActiveCfg = Debug|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x64.Build.0 = Debug|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x86.ActiveCfg = Debug|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x86.Build.0 = Debug|Any CPU {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|Any CPU.ActiveCfg = Release|Any CPU {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|Any CPU.Build.0 = Release|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x64.ActiveCfg = Release|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x64.Build.0 = Release|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x86.ActiveCfg = Release|Any CPU + {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x86.Build.0 = Release|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x64.ActiveCfg = Debug|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x64.Build.0 = Debug|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x86.ActiveCfg = Debug|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x86.Build.0 = Debug|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|Any CPU.ActiveCfg = Release|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|Any CPU.Build.0 = Release|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x64.ActiveCfg = Release|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x64.Build.0 = Release|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x86.ActiveCfg = Release|Any CPU + {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x86.Build.0 = Release|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x64.ActiveCfg = Debug|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x64.Build.0 = Debug|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x86.ActiveCfg = Debug|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x86.Build.0 = Debug|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|Any CPU.ActiveCfg = Release|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|Any CPU.Build.0 = Release|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x64.ActiveCfg = Release|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x64.Build.0 = Release|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x86.ActiveCfg = Release|Any CPU + {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x86.Build.0 = Release|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x64.ActiveCfg = Debug|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x64.Build.0 = Debug|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x86.ActiveCfg = Debug|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x86.Build.0 = Debug|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|Any CPU.ActiveCfg = Release|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|Any CPU.Build.0 = Release|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x64.ActiveCfg = Release|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x64.Build.0 = Release|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x86.ActiveCfg = Release|Any CPU + {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x86.Build.0 = Release|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x64.ActiveCfg = Debug|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x64.Build.0 = Debug|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x86.ActiveCfg = Debug|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x86.Build.0 = Debug|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|Any CPU.Build.0 = Release|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x64.ActiveCfg = Release|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x64.Build.0 = Release|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x86.ActiveCfg = Release|Any CPU + {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x86.Build.0 = Release|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x64.ActiveCfg = Debug|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x64.Build.0 = Debug|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x86.ActiveCfg = Debug|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x86.Build.0 = Debug|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|Any CPU.Build.0 = Release|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x64.ActiveCfg = Release|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x64.Build.0 = Release|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x86.ActiveCfg = Release|Any CPU + {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x86.Build.0 = Release|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x64.ActiveCfg = Debug|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x64.Build.0 = Debug|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x86.ActiveCfg = Debug|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x86.Build.0 = Debug|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|Any CPU.ActiveCfg = Release|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|Any CPU.Build.0 = Release|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x64.ActiveCfg = Release|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x64.Build.0 = Release|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x86.ActiveCfg = Release|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x86.Build.0 = Release|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x64.Build.0 = Debug|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x86.ActiveCfg = Debug|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x86.Build.0 = Debug|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|Any CPU.Build.0 = Release|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x64.ActiveCfg = Release|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x64.Build.0 = Release|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x86.ActiveCfg = Release|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x86.Build.0 = Release|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x64.ActiveCfg = Debug|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x64.Build.0 = Debug|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x86.ActiveCfg = Debug|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x86.Build.0 = Debug|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|Any CPU.Build.0 = Release|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x64.ActiveCfg = Release|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x64.Build.0 = Release|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x86.ActiveCfg = Release|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x86.Build.0 = Release|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x64.ActiveCfg = Debug|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x64.Build.0 = Debug|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x86.ActiveCfg = Debug|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x86.Build.0 = Debug|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|Any CPU.Build.0 = Release|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x64.ActiveCfg = Release|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x64.Build.0 = Release|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x86.ActiveCfg = Release|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x86.Build.0 = Release|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x64.ActiveCfg = Debug|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x64.Build.0 = Debug|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x86.ActiveCfg = Debug|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x86.Build.0 = Debug|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|Any CPU.Build.0 = Release|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x64.ActiveCfg = Release|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x64.Build.0 = Release|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x86.ActiveCfg = Release|Any CPU + {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x86.Build.0 = Release|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x64.ActiveCfg = Debug|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x64.Build.0 = Debug|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x86.ActiveCfg = Debug|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x86.Build.0 = Debug|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Release|Any CPU.ActiveCfg = Release|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Release|Any CPU.Build.0 = Release|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Release|x64.ActiveCfg = Release|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Release|x64.Build.0 = Release|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Release|x86.ActiveCfg = Release|Any CPU + {A102AB7B-A90C-4717-B17C-045240838060}.Release|x86.Build.0 = Release|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x64.ActiveCfg = Debug|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x64.Build.0 = Debug|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x86.Build.0 = Debug|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|Any CPU.Build.0 = Release|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x64.ActiveCfg = Release|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x64.Build.0 = Release|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x86.ActiveCfg = Release|Any CPU + {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x86.Build.0 = Release|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x64.ActiveCfg = Debug|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x64.Build.0 = Debug|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x86.Build.0 = Debug|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|Any CPU.Build.0 = Release|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x64.ActiveCfg = Release|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x64.Build.0 = Release|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x86.ActiveCfg = Release|Any CPU + {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x86.Build.0 = Release|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x64.ActiveCfg = Debug|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x64.Build.0 = Debug|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x86.Build.0 = Debug|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|Any CPU.Build.0 = Release|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x64.ActiveCfg = Release|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x64.Build.0 = Release|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x86.ActiveCfg = Release|Any CPU + {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x86.Build.0 = Release|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x64.ActiveCfg = Debug|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x64.Build.0 = Debug|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x86.ActiveCfg = Debug|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x86.Build.0 = Debug|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|Any CPU.ActiveCfg = Release|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|Any CPU.Build.0 = Release|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x64.ActiveCfg = Release|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x64.Build.0 = Release|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x86.ActiveCfg = Release|Any CPU + {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x86.Build.0 = Release|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x64.ActiveCfg = Debug|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x64.Build.0 = Debug|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x86.ActiveCfg = Debug|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x86.Build.0 = Debug|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|Any CPU.ActiveCfg = Release|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|Any CPU.Build.0 = Release|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x64.ActiveCfg = Release|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x64.Build.0 = Release|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x86.ActiveCfg = Release|Any CPU + {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x86.Build.0 = Release|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|Any CPU.Build.0 = Debug|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x64.ActiveCfg = Debug|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x64.Build.0 = Debug|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x86.ActiveCfg = Debug|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x86.Build.0 = Debug|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|Any CPU.ActiveCfg = Release|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|Any CPU.Build.0 = Release|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x64.ActiveCfg = Release|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x64.Build.0 = Release|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x86.ActiveCfg = Release|Any CPU + {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x86.Build.0 = Release|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x64.ActiveCfg = Debug|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x64.Build.0 = Debug|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x86.ActiveCfg = Debug|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x86.Build.0 = Debug|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|Any CPU.ActiveCfg = Release|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|Any CPU.Build.0 = Release|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x64.ActiveCfg = Release|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x64.Build.0 = Release|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x86.ActiveCfg = Release|Any CPU + {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x86.Build.0 = Release|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x64.Build.0 = Debug|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x86.Build.0 = Debug|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|Any CPU.Build.0 = Release|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x64.ActiveCfg = Release|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x64.Build.0 = Release|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x86.ActiveCfg = Release|Any CPU + {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x86.Build.0 = Release|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x64.Build.0 = Debug|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x86.Build.0 = Debug|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|Any CPU.Build.0 = Release|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x64.ActiveCfg = Release|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x64.Build.0 = Release|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x86.ActiveCfg = Release|Any CPU + {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x86.Build.0 = Release|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x64.Build.0 = Debug|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x86.Build.0 = Debug|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|Any CPU.ActiveCfg = Release|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|Any CPU.Build.0 = Release|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x64.ActiveCfg = Release|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x64.Build.0 = Release|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x86.ActiveCfg = Release|Any CPU + {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x86.Build.0 = Release|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x64.Build.0 = Debug|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x86.ActiveCfg = Debug|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x86.Build.0 = Debug|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|Any CPU.Build.0 = Release|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x64.ActiveCfg = Release|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x64.Build.0 = Release|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x86.ActiveCfg = Release|Any CPU + {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x86.Build.0 = Release|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x64.ActiveCfg = Debug|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x64.Build.0 = Debug|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x86.ActiveCfg = Debug|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x86.Build.0 = Debug|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|Any CPU.ActiveCfg = Release|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|Any CPU.Build.0 = Release|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x64.ActiveCfg = Release|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x64.Build.0 = Release|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x86.ActiveCfg = Release|Any CPU + {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x86.Build.0 = Release|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x64.Build.0 = Debug|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x86.Build.0 = Debug|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|Any CPU.Build.0 = Release|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x64.ActiveCfg = Release|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x64.Build.0 = Release|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x86.ActiveCfg = Release|Any CPU + {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x86.Build.0 = Release|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x64.ActiveCfg = Debug|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x64.Build.0 = Debug|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x86.ActiveCfg = Debug|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x86.Build.0 = Debug|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|Any CPU.ActiveCfg = Release|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|Any CPU.Build.0 = Release|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x64.ActiveCfg = Release|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x64.Build.0 = Release|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x86.ActiveCfg = Release|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x86.Build.0 = Release|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x64.ActiveCfg = Debug|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x64.Build.0 = Debug|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x86.Build.0 = Debug|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|Any CPU.ActiveCfg = Release|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|Any CPU.Build.0 = Release|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x64.ActiveCfg = Release|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x64.Build.0 = Release|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x86.ActiveCfg = Release|Any CPU + {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x86.Build.0 = Release|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x64.ActiveCfg = Debug|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x64.Build.0 = Debug|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x86.ActiveCfg = Debug|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x86.Build.0 = Debug|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Release|Any CPU.Build.0 = Release|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Release|x64.ActiveCfg = Release|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Release|x64.Build.0 = Release|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Release|x86.ActiveCfg = Release|Any CPU + {227C88B1-0510-4010-B142-C44725578FCD}.Release|x86.Build.0 = Release|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x64.Build.0 = Debug|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x86.Build.0 = Debug|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|Any CPU.Build.0 = Release|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x64.ActiveCfg = Release|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x64.Build.0 = Release|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x86.ActiveCfg = Release|Any CPU + {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x86.Build.0 = Release|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x64.ActiveCfg = Debug|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x64.Build.0 = Debug|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x86.ActiveCfg = Debug|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x86.Build.0 = Debug|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|Any CPU.ActiveCfg = Release|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|Any CPU.Build.0 = Release|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x64.ActiveCfg = Release|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x64.Build.0 = Release|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x86.ActiveCfg = Release|Any CPU + {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x86.Build.0 = Release|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x64.ActiveCfg = Debug|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x64.Build.0 = Debug|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x86.ActiveCfg = Debug|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x86.Build.0 = Debug|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|Any CPU.Build.0 = Release|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x64.ActiveCfg = Release|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x64.Build.0 = Release|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x86.ActiveCfg = Release|Any CPU + {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x86.Build.0 = Release|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x64.ActiveCfg = Debug|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x64.Build.0 = Debug|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x86.ActiveCfg = Debug|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x86.Build.0 = Debug|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|Any CPU.Build.0 = Release|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x64.ActiveCfg = Release|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x64.Build.0 = Release|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x86.ActiveCfg = Release|Any CPU + {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x86.Build.0 = Release|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x64.ActiveCfg = Debug|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x64.Build.0 = Debug|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x86.ActiveCfg = Debug|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x86.Build.0 = Debug|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|Any CPU.Build.0 = Release|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x64.ActiveCfg = Release|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x64.Build.0 = Release|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x86.ActiveCfg = Release|Any CPU + {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x86.Build.0 = Release|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x64.Build.0 = Debug|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x86.Build.0 = Debug|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Release|Any CPU.Build.0 = Release|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x64.ActiveCfg = Release|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x64.Build.0 = Release|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x86.ActiveCfg = Release|Any CPU + {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x86.Build.0 = Release|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x64.Build.0 = Debug|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x86.Build.0 = Debug|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|Any CPU.Build.0 = Release|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x64.ActiveCfg = Release|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x64.Build.0 = Release|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x86.ActiveCfg = Release|Any CPU + {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x86.Build.0 = Release|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x64.ActiveCfg = Debug|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x64.Build.0 = Debug|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x86.Build.0 = Debug|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|Any CPU.ActiveCfg = Release|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|Any CPU.Build.0 = Release|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x64.ActiveCfg = Release|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x64.Build.0 = Release|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x86.ActiveCfg = Release|Any CPU + {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x86.Build.0 = Release|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x64.Build.0 = Debug|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x86.Build.0 = Debug|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|Any CPU.Build.0 = Release|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x64.ActiveCfg = Release|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x64.Build.0 = Release|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x86.ActiveCfg = Release|Any CPU + {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x86.Build.0 = Release|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x64.ActiveCfg = Debug|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x64.Build.0 = Debug|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x86.ActiveCfg = Debug|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x86.Build.0 = Debug|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|Any CPU.Build.0 = Release|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x64.ActiveCfg = Release|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x64.Build.0 = Release|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x86.ActiveCfg = Release|Any CPU + {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x86.Build.0 = Release|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x64.ActiveCfg = Debug|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x64.Build.0 = Debug|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x86.ActiveCfg = Debug|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x86.Build.0 = Debug|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|Any CPU.ActiveCfg = Release|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|Any CPU.Build.0 = Release|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x64.ActiveCfg = Release|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x64.Build.0 = Release|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x86.ActiveCfg = Release|Any CPU + {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x86.Build.0 = Release|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x64.ActiveCfg = Debug|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x64.Build.0 = Debug|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x86.ActiveCfg = Debug|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x86.Build.0 = Debug|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|Any CPU.Build.0 = Release|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x64.ActiveCfg = Release|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x64.Build.0 = Release|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x86.ActiveCfg = Release|Any CPU + {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x86.Build.0 = Release|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x64.ActiveCfg = Debug|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x64.Build.0 = Debug|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x86.ActiveCfg = Debug|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x86.Build.0 = Debug|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|Any CPU.ActiveCfg = Release|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|Any CPU.Build.0 = Release|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x64.ActiveCfg = Release|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x64.Build.0 = Release|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x86.ActiveCfg = Release|Any CPU + {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x86.Build.0 = Release|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x64.ActiveCfg = Debug|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x64.Build.0 = Debug|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x86.ActiveCfg = Debug|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x86.Build.0 = Debug|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|Any CPU.ActiveCfg = Release|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|Any CPU.Build.0 = Release|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x64.ActiveCfg = Release|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x64.Build.0 = Release|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x86.ActiveCfg = Release|Any CPU + {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x86.Build.0 = Release|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x64.ActiveCfg = Debug|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x64.Build.0 = Debug|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x86.ActiveCfg = Debug|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x86.Build.0 = Debug|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|Any CPU.Build.0 = Release|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x64.ActiveCfg = Release|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x64.Build.0 = Release|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x86.ActiveCfg = Release|Any CPU + {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x86.Build.0 = Release|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x64.ActiveCfg = Debug|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x64.Build.0 = Debug|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x86.ActiveCfg = Debug|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x86.Build.0 = Debug|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|Any CPU.Build.0 = Release|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x64.ActiveCfg = Release|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x64.Build.0 = Release|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x86.ActiveCfg = Release|Any CPU + {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x86.Build.0 = Release|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x64.Build.0 = Debug|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x86.Build.0 = Debug|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|Any CPU.Build.0 = Release|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x64.ActiveCfg = Release|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x64.Build.0 = Release|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x86.ActiveCfg = Release|Any CPU + {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x86.Build.0 = Release|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x64.ActiveCfg = Debug|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x64.Build.0 = Debug|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x86.ActiveCfg = Debug|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x86.Build.0 = Debug|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|Any CPU.ActiveCfg = Release|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|Any CPU.Build.0 = Release|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x64.ActiveCfg = Release|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x64.Build.0 = Release|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x86.ActiveCfg = Release|Any CPU + {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x86.Build.0 = Release|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x64.ActiveCfg = Debug|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x64.Build.0 = Debug|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x86.ActiveCfg = Debug|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x86.Build.0 = Debug|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|Any CPU.Build.0 = Release|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x64.ActiveCfg = Release|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x64.Build.0 = Release|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x86.ActiveCfg = Release|Any CPU + {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x86.Build.0 = Release|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x64.ActiveCfg = Debug|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x64.Build.0 = Debug|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x86.Build.0 = Debug|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|Any CPU.Build.0 = Release|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x64.ActiveCfg = Release|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x64.Build.0 = Release|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x86.ActiveCfg = Release|Any CPU + {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x86.Build.0 = Release|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x64.Build.0 = Debug|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x86.Build.0 = Debug|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|Any CPU.Build.0 = Release|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x64.ActiveCfg = Release|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x64.Build.0 = Release|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x86.ActiveCfg = Release|Any CPU + {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x86.Build.0 = Release|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x64.ActiveCfg = Debug|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x64.Build.0 = Debug|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x86.ActiveCfg = Debug|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x86.Build.0 = Debug|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|Any CPU.ActiveCfg = Release|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|Any CPU.Build.0 = Release|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x64.ActiveCfg = Release|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x64.Build.0 = Release|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x86.ActiveCfg = Release|Any CPU + {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x86.Build.0 = Release|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x64.ActiveCfg = Debug|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x64.Build.0 = Debug|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x86.ActiveCfg = Debug|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x86.Build.0 = Debug|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|Any CPU.ActiveCfg = Release|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|Any CPU.Build.0 = Release|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x64.ActiveCfg = Release|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x64.Build.0 = Release|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x86.ActiveCfg = Release|Any CPU + {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x86.Build.0 = Release|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x64.ActiveCfg = Debug|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x64.Build.0 = Debug|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x86.ActiveCfg = Debug|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x86.Build.0 = Debug|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|Any CPU.Build.0 = Release|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x64.ActiveCfg = Release|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x64.Build.0 = Release|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x86.ActiveCfg = Release|Any CPU + {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x86.Build.0 = Release|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x64.ActiveCfg = Debug|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x64.Build.0 = Debug|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x86.ActiveCfg = Debug|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x86.Build.0 = Debug|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Release|Any CPU.Build.0 = Release|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x64.ActiveCfg = Release|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x64.Build.0 = Release|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x86.ActiveCfg = Release|Any CPU + {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -432,6 +866,7 @@ Global {71C2BC96-C104-4748-B6CF-E74BAC4E709D} = {4827B3EC-73D8-436D-AE2A-5E29AC95FD0C} {21649885-B40A-4F6B-9286-A7E378276C3E} = {C526E8AB-739A-48D7-8FC4-048978C9B650} {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8} = {C526E8AB-739A-48D7-8FC4-048978C9B650} + {93E92151-28BF-4176-92F4-65E1227C3269} = {C526E8AB-739A-48D7-8FC4-048978C9B650} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8CBB7278-D093-448E-B3DE-B5991209A1AA} diff --git a/tests/NATS.Client.CheckAbi/Program.cs b/tests/NATS.Client.CheckAbi/Program.cs index c0b376685..433b42ed0 100644 --- a/tests/NATS.Client.CheckAbi/Program.cs +++ b/tests/NATS.Client.CheckAbi/Program.cs @@ -29,6 +29,18 @@ void Check(string name, string expected, string actual) Check("INatsSerialize<> (transient)", "NATS.Client.Abstractions", MySerializer.GetSerializerInterfaceAssembly()); Console.WriteLine(); +Console.WriteLine("Assembly versions at runtime:"); +Console.WriteLine($" INatsSerialize<> assembly version (from this project): {typeof(INatsSerialize<>).Assembly.GetName().Version}"); +Console.WriteLine($" INatsSerialize<> assembly version (from TransientLib): {MySerializer.GetSerializerInterfaceAssemblyVersion()}"); +foreach (var asm in AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.GetName().Name?.Contains("NATS") == true) + .OrderBy(a => a.GetName().Name)) +{ + Console.WriteLine($" {asm.GetName().Name} v{asm.GetName().Version} [{asm.Location}]"); +} + +Console.WriteLine(); + Console.WriteLine("Testing TransientLib.MySerializer (compiled against 2.6.0):"); var serializer = new MySerializer(); var buffer = new ArrayBufferWriter(); diff --git a/tests/NATS.Client.CheckAbi/run-abi-check.sh b/tests/NATS.Client.CheckAbi/run-abi-check.sh index 94d1f25c3..ad352e9dd 100644 --- a/tests/NATS.Client.CheckAbi/run-abi-check.sh +++ b/tests/NATS.Client.CheckAbi/run-abi-check.sh @@ -5,30 +5,71 @@ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" TRANSIENT_LIB_CSPROJ="$SCRIPT_DIR/../NATS.Client.CheckAbiTransientLib/NATS.Client.CheckAbiTransientLib.csproj" CHECK_ABI_CSPROJ="$SCRIPT_DIR/NATS.Client.CheckAbi.csproj" +CHECK_ABI_OLD_CSPROJ="$SCRIPT_DIR/../NATS.Client.CheckAbiOld/NATS.Client.CheckAbiOld.csproj" + +VERSION=$(cat "$SCRIPT_DIR/../../version.txt" | tr -d '[:space:]') +OLD_VERSION=$(grep 'NatsAbiCheckVersion' "$SCRIPT_DIR/../../Directory.Build.props" | sed 's/.*>\([^<]*\)<.*/\1/') echo "=== NATS.Net ABI Compatibility Check (Transient Dependency Simulation) ===" echo "" echo "This test simulates:" -echo " - An app using NATS.Net 2.7.0 (with type forwarders)" -echo " - A transient dependency (TransientLib) compiled against NATS.Net 2.6.0" +echo " - An app using local NATS.Net $VERSION (with type forwarders)" +echo " - A transient dependency (TransientLib) compiled against NATS.Net $OLD_VERSION" echo " - Type forwarding should allow the old library to work with new NATS.Net" echo "" -# Step 1: Build CheckAbiTransientLib against 2.6.0 NuGet package -echo "[1/3] Building NATS.Client.CheckAbiTransientLib against NATS.Net 2.6.0..." +# Step 1: Build CheckAbiTransientLib against NuGet package +echo "[1/4] Building NATS.Client.CheckAbiTransientLib against NATS.Net $OLD_VERSION..." dotnet clean dotnet build "$TRANSIENT_LIB_CSPROJ" -c Release -# Step 2: Build AbiCheck (references local 2.7.0 source + TransientLib.dll) +# Step 2: Build and run CheckAbiOld (control: everything is 2.6.0) +echo "" +echo "[2/4] Building and running CheckAbiOld (control, NATS.Net $OLD_VERSION NuGet)..." +dotnet build "$CHECK_ABI_OLD_CSPROJ" -c Release + +OLD_OUTPUT=$(dotnet run --project "$CHECK_ABI_OLD_CSPROJ" -c Release --no-build) +echo "$OLD_OUTPUT" echo "" -echo "[2/3] Building AbiCheck against local NATS.Net 2.7.0 source + TransientLib..." + +# Verify old project sees types in NATS.Client.Core with the old version +if ! echo "$OLD_OUTPUT" | grep -q "NATS.Client.Core v${OLD_VERSION}.0"; then + echo "FAILED: CheckAbiOld should load NATS.Client.Core v${OLD_VERSION}.0" + exit 1 +fi +if echo "$OLD_OUTPUT" | grep -q "NATS.Client.Abstractions"; then + echo "FAILED: CheckAbiOld should NOT reference NATS.Client.Abstractions" + exit 1 +fi +echo "OK: CheckAbiOld correctly uses NATS.Client.Core v${OLD_VERSION}.0" + +# Step 3: Build AbiCheck (references local 2.7.0 source + TransientLib.dll) +echo "" +echo "[3/4] Building AbiCheck against local NATS.Net source + TransientLib..." dotnet build "$CHECK_ABI_CSPROJ" -c Release -# Step 3: Run the test +# Step 4: Run the test +echo "" +echo "[4/4] Running ABI compatibility check..." echo "" -echo "[3/3] Running ABI compatibility check..." + +NEW_OUTPUT=$(dotnet run --project "$CHECK_ABI_CSPROJ" -c Release --no-build) +echo "$NEW_OUTPUT" echo "" -dotnet run --project "$CHECK_ABI_CSPROJ" -c Release --no-build + +# Verify new project sees types forwarded to NATS.Client.Abstractions +if ! echo "$NEW_OUTPUT" | grep -q "NATS.Client.Abstractions"; then + echo "FAILED: CheckAbi should resolve types to NATS.Client.Abstractions" + exit 1 +fi +if echo "$NEW_OUTPUT" | grep -q "NATS.Client.Core v${OLD_VERSION}.0"; then + echo "FAILED: CheckAbi should NOT load NATS.Client.Core v${OLD_VERSION}.0" + exit 1 +fi +if ! echo "$NEW_OUTPUT" | grep -q "SUCCESS"; then + echo "FAILED: ABI compatibility check did not succeed" + exit 1 +fi echo "" echo "=== ABI Check Complete ===" diff --git a/tests/NATS.Client.CheckAbiOld/NATS.Client.CheckAbiOld.csproj b/tests/NATS.Client.CheckAbiOld/NATS.Client.CheckAbiOld.csproj new file mode 100644 index 000000000..7a5ac5499 --- /dev/null +++ b/tests/NATS.Client.CheckAbiOld/NATS.Client.CheckAbiOld.csproj @@ -0,0 +1,29 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + false + false + + + + + ..\NATS.Client.CheckAbiTransientLib\bin\$(Configuration)\net8.0\NATS.Client.CheckAbiTransientLib.dll + true + + + + diff --git a/tests/NATS.Client.CheckAbiOld/Program.cs b/tests/NATS.Client.CheckAbiOld/Program.cs new file mode 100644 index 000000000..c2023778e --- /dev/null +++ b/tests/NATS.Client.CheckAbiOld/Program.cs @@ -0,0 +1,29 @@ +using NATS.Client.CheckAbiTransientLib; +using NATS.Client.Core; + +// This project references an older NATS.Net NuGet (same as TransientLib). +// It serves as a control: all types should resolve to NATS.Client.Core. +var coreVersion = typeof(INatsSerialize<>).Assembly.GetName().Version; +Console.WriteLine($"=== CheckAbiOld: running against NATS.Client.Core v{coreVersion} NuGet ==="); +Console.WriteLine(); + +Console.WriteLine($"Types as seen by this project:"); +Console.WriteLine($" INatsSerialize<>: {typeof(INatsSerialize<>).Assembly.GetName().Name}"); +Console.WriteLine($" INatsDeserialize<>: {typeof(INatsDeserialize<>).Assembly.GetName().Name}"); +Console.WriteLine($" INatsSerializer<>: {typeof(INatsSerializer<>).Assembly.GetName().Name}"); +Console.WriteLine($" INatsSerializerRegistry: {typeof(INatsSerializerRegistry).Assembly.GetName().Name}"); +Console.WriteLine(); + +Console.WriteLine($"Types as seen by TransientLib:"); +Console.WriteLine($" INatsSerialize<> (transient): {MySerializer.GetSerializerInterfaceAssembly()}"); +Console.WriteLine($" INatsSerialize<> version (transient): {MySerializer.GetSerializerInterfaceAssemblyVersion()}"); +Console.WriteLine(); + +Console.WriteLine("Assembly versions at runtime:"); +Console.WriteLine($" INatsSerialize<> assembly version: {typeof(INatsSerialize<>).Assembly.GetName().Version}"); +foreach (var asm in AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.GetName().Name?.Contains("NATS") == true) + .OrderBy(a => a.GetName().Name)) +{ + Console.WriteLine($" {asm.GetName().Name} v{asm.GetName().Version} [{asm.Location}]"); +} diff --git a/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs b/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs index 0d1e60648..7d70d9864 100644 --- a/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs +++ b/tests/NATS.Client.CheckAbiTransientLib/MySerializer.cs @@ -14,6 +14,11 @@ public static string GetSerializerInterfaceAssembly() return typeof(INatsSerialize<>).Assembly.GetName().Name!; } + public static string GetSerializerInterfaceAssemblyVersion() + { + return typeof(INatsSerialize<>).Assembly.GetName().Version?.ToString() ?? "(null)"; + } + public void Serialize(IBufferWriter bufferWriter, string value) { var bytes = System.Text.Encoding.UTF8.GetBytes(value); diff --git a/tests/NATS.Client.CheckAbiTransientLib/NATS.Client.CheckAbiTransientLib.csproj b/tests/NATS.Client.CheckAbiTransientLib/NATS.Client.CheckAbiTransientLib.csproj index 794a47fe6..9517cc2f7 100644 --- a/tests/NATS.Client.CheckAbiTransientLib/NATS.Client.CheckAbiTransientLib.csproj +++ b/tests/NATS.Client.CheckAbiTransientLib/NATS.Client.CheckAbiTransientLib.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj index 6013ee793..fdbdefdc0 100644 --- a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj +++ b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net10.0 $(TargetFrameworks);net481 any;win-x86 enable diff --git a/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj b/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj index 5d5a58b3f..56d0ebfc4 100644 --- a/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj +++ b/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net10.0 $(TargetFrameworks);net481 any;win-x86 enable diff --git a/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj b/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj index 9ea506692..6acf6581d 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj +++ b/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net10.0 $(TargetFrameworks);net481 any;win-x86 enable From c774bf8a1b13cca7e68a907d519992cba46f5449 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 10 Apr 2026 11:47:58 +0100 Subject: [PATCH 18/20] tests: fix NU1510 package pruning errors on net10.0 Condition System.Net.Http and System.Text.RegularExpressions package references to net481 only, where they are actually needed. On net10.0 these are built-in and NuGet's package pruning treats the unnecessary references as errors. --- tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj | 4 ++-- .../NATS.Client.CoreUnit.Tests.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj b/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj index 56d0ebfc4..6179c98db 100644 --- a/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj +++ b/tests/NATS.Client.Core2.Tests/NATS.Client.Core2.Tests.csproj @@ -28,8 +28,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + diff --git a/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj b/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj index 6acf6581d..eb8d97dbc 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj +++ b/tests/NATS.Client.CoreUnit.Tests/NATS.Client.CoreUnit.Tests.csproj @@ -26,8 +26,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + From 388798099890d0dcc36ed369616f7570a119dbe0 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 16 Apr 2026 14:05:40 +0100 Subject: [PATCH 19/20] serializers: address context review feedback - NatsMsgContext: require Subject via constructor; drop init setters so netstandard shim for IsExternalInit is no longer needed. - Add INatsSerializerWithContext as a combined interface mirroring INatsSerializer, simplifying class declarations for context-aware serializer+deserializer pairs. - Built-in NatsUtf8PrimitivesSerializer, NatsRawSerializer and NatsJsonContextSerializer now implement INatsSerializerWithContext and propagate the context through _next when delegating. Without this, chaining a built-in with a context-aware leaf silently dropped context at the leaf. - Document NatsHeaders thread-safety: not safe to share across concurrent publishes, since readonly-locking was removed. - Document that header-mutating serializers must get non-null headers from the caller; the library does not allocate on their behalf. - Add chain-propagation tests for serialize and deserialize paths. --- .../INatsSerialize.cs | 16 + .../NatsMsgContext.cs | 24 +- src/NATS.Client.Abstractions/netstandard.cs | 12 - .../Commands/CommandWriter.cs | 2 +- src/NATS.Client.Core/NatsHeaders.cs | 6 + src/NATS.Client.Core/NatsMsg.cs | 4 +- src/NATS.Client.Core/NatsSerialize.cs | 423 +++++++++++++----- tests/NATS.Client.Core.Tests/NatsMsgTests.cs | 4 +- .../NatsSerializationExtensionsTests.cs | 73 ++- .../Advanced/SerializationPage.cs | 2 +- 10 files changed, 429 insertions(+), 137 deletions(-) delete mode 100644 src/NATS.Client.Abstractions/netstandard.cs diff --git a/src/NATS.Client.Abstractions/INatsSerialize.cs b/src/NATS.Client.Abstractions/INatsSerialize.cs index 1d0f8a6f9..c901b4289 100644 --- a/src/NATS.Client.Abstractions/INatsSerialize.cs +++ b/src/NATS.Client.Abstractions/INatsSerialize.cs @@ -74,6 +74,15 @@ public interface INatsDeserializeWithContext : INatsDeserialize T? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context); } +/// +/// Combined context-aware serializer interface that supports both serialization and deserialization +/// with access to message context. +/// +/// Object type +public interface INatsSerializerWithContext : INatsSerializeWithContext, INatsDeserializeWithContext +{ +} + /// /// Registry for serializers and deserializers. /// @@ -87,6 +96,13 @@ public interface INatsSerializerRegistry /// /// Extension methods to support context-aware serialization with fallback to standard serialization. /// +/// +/// Context-aware serializers that want to mutate must check +/// for null before doing so: the library passes whatever headers the caller supplied to +/// PublishAsync, and does not allocate a new for serializers that +/// opt in to context. Callers who want header-mutating behavior must pass a non-null +/// headers instance to PublishAsync. +/// public static class NatsSerializationExtensions { /// diff --git a/src/NATS.Client.Abstractions/NatsMsgContext.cs b/src/NATS.Client.Abstractions/NatsMsgContext.cs index 742f03b52..420600d33 100644 --- a/src/NATS.Client.Abstractions/NatsMsgContext.cs +++ b/src/NATS.Client.Abstractions/NatsMsgContext.cs @@ -3,14 +3,30 @@ namespace NATS.Client.Core; /// /// Provides message envelope metadata available during serialization and deserialization. /// +/// +/// Use the constructor to create instances. A default(NatsMsgContext) value has a null +/// and is not a valid context; the library never passes such a value to +/// user code. +/// public readonly struct NatsMsgContext { + /// Creates a new . + /// Subject the message was published to. + /// Optional reply-to subject. + /// Optional message headers. + public NatsMsgContext(string subject, string? replyTo = null, INatsHeaders? headers = null) + { + Subject = subject ?? throw new ArgumentNullException(nameof(subject)); + ReplyTo = replyTo; + Headers = headers; + } + /// Subject the message was published to. - public string Subject { get; init; } + public string Subject { get; } /// Optional reply-to subject. - public string? ReplyTo { get; init; } + public string? ReplyTo { get; } - /// Optional message headers. - public INatsHeaders? Headers { get; init; } + /// Optional message headers. May be null if the caller did not supply headers. + public INatsHeaders? Headers { get; } } diff --git a/src/NATS.Client.Abstractions/netstandard.cs b/src/NATS.Client.Abstractions/netstandard.cs deleted file mode 100644 index c0115cfdb..000000000 --- a/src/NATS.Client.Abstractions/netstandard.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable SA1403 -#pragma warning disable SA1204 - -// Enable init-only setters on netstandard targets -#if NETSTANDARD -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit - { - } -} -#endif diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 79fe2196d..364faf241 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -335,7 +335,7 @@ public ValueTask PublishAsync(string subject, T? value, NatsHeaders? headers, try { if (value != null) - serializer.Serialize(payloadBuffer, value, new NatsMsgContext { Subject = subject, ReplyTo = replyTo, Headers = headers }); + serializer.Serialize(payloadBuffer, value, new NatsMsgContext(subject, replyTo, headers)); if (headers != null) _headerWriter.Write(headersBuffer!, headers); diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 583e1209a..53324f541 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -10,6 +10,12 @@ namespace NATS.Client.Core; /// /// Represents a wrapper for RequestHeaders and ResponseHeaders. /// +/// +/// Not thread-safe. Do not share a single instance across concurrent +/// publishes: the writer may read and context-aware serializers may mutate the dictionary while +/// it is being written to the wire. Construct a fresh instance per publish, or wait for one +/// publish to complete before reusing the same instance. +/// [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Keep class format as is for reference")] [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1504:All accessors should be single-line or multi-line", Justification = "Keep class format as is for reference")] [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1516:Elements should be separated by blank line", Justification = "Keep class format as is for reference")] diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index c2eaf7d65..3ca4c9c5e 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -399,7 +399,7 @@ public static NatsMsg Build( { try { - data = serializer.Deserialize(payloadBuffer, new NatsMsgContext { Subject = subject, ReplyTo = replyTo, Headers = headers }); + data = serializer.Deserialize(payloadBuffer, new NatsMsgContext(subject, replyTo, headers)); } catch (Exception e) { @@ -517,7 +517,7 @@ public NatsMsg Msg if (Serializer != null && Data != null) { var bufferWriter = new NatsPooledBufferWriter(SerializationBufferSize); - Serializer.Serialize(bufferWriter, Data, new NatsMsgContext { Subject = Subject, ReplyTo = ReplyTo, Headers = Headers }); + Serializer.Serialize(bufferWriter, Data, new NatsMsgContext(Subject, ReplyTo, Headers)); size = bufferWriter.WrittenMemory.Length; } diff --git a/src/NATS.Client.Core/NatsSerialize.cs b/src/NATS.Client.Core/NatsSerialize.cs index 3f889935b..6234508ac 100644 --- a/src/NATS.Client.Core/NatsSerialize.cs +++ b/src/NATS.Client.Core/NatsSerialize.cs @@ -37,7 +37,7 @@ public class NatsDefaultSerializerRegistry : INatsSerializerRegistry /// TimeSpan, bool, byte, decimal, double, float, /// int, long, sbyte, short, uint and ulong. /// -public class NatsUtf8PrimitivesSerializer : INatsSerializer +public class NatsUtf8PrimitivesSerializer : INatsSerializer, INatsSerializerWithContext { public static readonly NatsUtf8PrimitivesSerializer Default = new(); @@ -54,11 +54,58 @@ public class NatsUtf8PrimitivesSerializer : INatsSerializer /// public void Serialize(IBufferWriter bufferWriter, T value) + { + if (TrySerializePrimitive(bufferWriter, value)) + return; + + if (_next == null) + throw new NatsException($"Can't serialize {typeof(T)}"); + + _next.Serialize(bufferWriter, value); + } + + /// + public void Serialize(IBufferWriter bufferWriter, T value, in NatsMsgContext context) + { + if (TrySerializePrimitive(bufferWriter, value)) + return; + + if (_next == null) + throw new NatsException($"Can't serialize {typeof(T)}"); + + _next.Serialize(bufferWriter, value, in context); + } + + /// + public T? Deserialize(in ReadOnlySequence buffer) + { + if (TryDeserializePrimitive(buffer, out var result)) + return result; + + if (_next == null) + throw new NatsException($"Can't deserialize {typeof(T)}"); + + return _next.Deserialize(buffer); + } + + /// + public T? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context) + { + if (TryDeserializePrimitive(buffer, out var result)) + return result; + + if (_next == null) + throw new NatsException($"Can't deserialize {typeof(T)}"); + + return _next.Deserialize(buffer, in context); + } + + private static bool TrySerializePrimitive(IBufferWriter bufferWriter, T value) { if (value is string str) { Encoding.UTF8.GetBytes(str, bufferWriter); - return; + return true; } var span = bufferWriter.GetSpan(128); @@ -76,7 +123,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -105,7 +152,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -122,7 +169,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -139,7 +186,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -156,7 +203,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -173,7 +220,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -190,7 +237,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -207,7 +254,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -224,7 +271,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -241,7 +288,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -258,7 +305,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -275,7 +322,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -292,7 +339,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -309,7 +356,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } @@ -326,26 +373,25 @@ public void Serialize(IBufferWriter bufferWriter, T value) throw new NatsException($"Can't serialize {typeof(T)}, format error"); } - return; + return true; } } - if (_next == null) - { - throw new NatsException($"Can't serialize {typeof(T)}"); - } - - _next.Serialize(bufferWriter, value); + return false; } - /// - public T? Deserialize(in ReadOnlySequence buffer) + private static bool TryDeserializePrimitive(in ReadOnlySequence buffer, out T? result) { if (typeof(T) == typeof(string)) { if (buffer.Length == 0) - return default; - return (T)(object)Encoding.UTF8.GetString(buffer); + { + result = default; + return true; + } + + result = (T)(object)Encoding.UTF8.GetString(buffer); + return true; } var span = buffer.IsSingleSegment ? buffer.GetFirstSpan() : buffer.ToArray(); @@ -353,11 +399,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out DateTime value, out _, 'O')) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -366,11 +416,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(DateTimeOffset) || typeof(T) == typeof(DateTimeOffset?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out DateTimeOffset value, out _, 'O')) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -379,11 +433,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out Guid value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -392,11 +450,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out TimeSpan value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -405,11 +467,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out bool value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -418,11 +484,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out byte value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -431,11 +501,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out decimal value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -444,11 +518,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out double value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -457,11 +535,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(float) || typeof(T) == typeof(float?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out float value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -470,11 +552,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out int value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -483,11 +569,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out long value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -496,11 +586,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out sbyte value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -509,11 +603,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(short) || typeof(T) == typeof(short?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out short value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -522,11 +620,15 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out uint value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); @@ -535,29 +637,29 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } if (Utf8Parser.TryParse(span, out ulong value, out _)) { - return (T)(object)value; + result = (T)(object)value; + return true; } throw new NatsException($"Can't deserialize {typeof(T)}. Parsing error"); } - if (_next == null) - { - throw new NatsException($"Can't deserialize {typeof(T)}"); - } - - return _next.Deserialize(buffer); + result = default; + return false; } } /// /// Serializer for binary data. /// -public class NatsRawSerializer : INatsSerializer +public class NatsRawSerializer : INatsSerializer, INatsSerializerWithContext { public static readonly NatsRawSerializer Default = new(NatsUtf8PrimitivesSerializer.Default); @@ -574,23 +676,70 @@ public class NatsRawSerializer : INatsSerializer /// public void Serialize(IBufferWriter bufferWriter, T value) + { + if (TrySerializeRaw(bufferWriter, value)) + return; + + if (_next == null) + throw new NatsException($"Can't serialize {typeof(T)}"); + + _next.Serialize(bufferWriter, value); + } + + /// + public void Serialize(IBufferWriter bufferWriter, T value, in NatsMsgContext context) + { + if (TrySerializeRaw(bufferWriter, value)) + return; + + if (_next == null) + throw new NatsException($"Can't serialize {typeof(T)}"); + + _next.Serialize(bufferWriter, value, in context); + } + + /// + public T? Deserialize(in ReadOnlySequence buffer) + { + if (TryDeserializeRaw(buffer, out var result)) + return result; + + if (_next == null) + throw new NatsException($"Can't deserialize {typeof(T)}"); + + return _next.Deserialize(buffer); + } + + /// + public T? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context) + { + if (TryDeserializeRaw(buffer, out var result)) + return result; + + if (_next == null) + throw new NatsException($"Can't deserialize {typeof(T)}"); + + return _next.Deserialize(buffer, in context); + } + + private static bool TrySerializeRaw(IBufferWriter bufferWriter, T value) { if (value is byte[] bytes) { bufferWriter.Write(bytes); - return; + return true; } if (value is Memory memory) { bufferWriter.Write(memory.Span); - return; + return true; } if (value is ReadOnlyMemory readOnlyMemory) { bufferWriter.Write(readOnlyMemory.Span); - return; + return true; } if (value is ReadOnlySequence readOnlySequence) @@ -607,7 +756,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) } } - return; + return true; } if (value is IMemoryOwner memoryOwner) @@ -621,64 +770,79 @@ public void Serialize(IBufferWriter bufferWriter, T value) bufferWriter.Advance(length); - return; + return true; } } - if (_next == null) - { - throw new NatsException($"Can't serialize {typeof(T)}"); - } - - _next.Serialize(bufferWriter, value); + return false; } - /// - public T? Deserialize(in ReadOnlySequence buffer) + private static bool TryDeserializeRaw(in ReadOnlySequence buffer, out T? result) { if (typeof(T) == typeof(byte[])) { if (buffer.Length == 0) - return default; - return (T)(object)buffer.ToArray(); + { + result = default; + return true; + } + + result = (T)(object)buffer.ToArray(); + return true; } if (typeof(T) == typeof(Memory)) { if (buffer.Length == 0) - return default; - return (T)(object)new Memory(buffer.ToArray()); + { + result = default; + return true; + } + + result = (T)(object)new Memory(buffer.ToArray()); + return true; } if (typeof(T) == typeof(ReadOnlyMemory)) { if (buffer.Length == 0) - return default; - return (T)(object)new ReadOnlyMemory(buffer.ToArray()); + { + result = default; + return true; + } + + result = (T)(object)new ReadOnlyMemory(buffer.ToArray()); + return true; } if (typeof(T) == typeof(ReadOnlySequence)) { if (buffer.Length == 0) - return default; - return (T)(object)new ReadOnlySequence(buffer.ToArray()); + { + result = default; + return true; + } + + result = (T)(object)new ReadOnlySequence(buffer.ToArray()); + return true; } if (typeof(T) == typeof(IMemoryOwner) || typeof(T) == typeof(NatsMemoryOwner)) { if (buffer.Length == 0) - return default; + { + result = default; + return true; + } + var memoryOwner = NatsMemoryOwner.Allocate((int)buffer.Length); buffer.CopyTo(memoryOwner.Memory.Span); - return (T)(object)memoryOwner; + result = (T)(object)memoryOwner; + return true; } - if (_next == null) - { - throw new NatsException($"Can't deserialize {typeof(T)}"); - } - - return _next.Deserialize(buffer); + result = default; + return false; } } @@ -696,7 +860,7 @@ public sealed class NatsJsonContextSerializerRegistry : INatsSerializerRegistry /// /// Serializer with support for . /// -public sealed class NatsJsonContextSerializer : INatsSerializer +public sealed class NatsJsonContextSerializer : INatsSerializer, INatsSerializerWithContext { // ReSharper disable once StaticMemberInGenericType private static readonly JsonWriterOptions JsonWriterOpts = new() { Indented = false, SkipValidation = true }; @@ -729,6 +893,53 @@ public NatsJsonContextSerializer(JsonSerializerContext context, INatsSerializer< /// public void Serialize(IBufferWriter bufferWriter, T value) + { + if (TrySerializeJson(bufferWriter, value)) + return; + + if (_next == null) + throw new NatsException($"Can't serialize {typeof(T)}"); + + _next.Serialize(bufferWriter, value); + } + + /// + public void Serialize(IBufferWriter bufferWriter, T value, in NatsMsgContext context) + { + if (TrySerializeJson(bufferWriter, value)) + return; + + if (_next == null) + throw new NatsException($"Can't serialize {typeof(T)}"); + + _next.Serialize(bufferWriter, value, in context); + } + + /// + public T? Deserialize(in ReadOnlySequence buffer) + { + if (TryDeserializeJson(buffer, out var result)) + return result; + + if (_next == null) + throw new NatsException($"Can't deserialize {typeof(T)}"); + + return _next.Deserialize(buffer); + } + + /// + public T? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context) + { + if (TryDeserializeJson(buffer, out var result)) + return result; + + if (_next == null) + throw new NatsException($"Can't deserialize {typeof(T)}"); + + return _next.Deserialize(buffer, in context); + } + + private bool TrySerializeJson(IBufferWriter bufferWriter, T value) { foreach (var context in _contexts) { @@ -748,24 +959,19 @@ public void Serialize(IBufferWriter bufferWriter, T value) JsonSerializer.Serialize(writer, value, jsonTypeInfo); writer.Reset(NullBufferWriter.Instance); - return; + return true; } } - if (_next == null) - { - throw new NatsException($"Can't serialize {typeof(T)}"); - } - - _next.Serialize(bufferWriter, value); + return false; } - /// - public T? Deserialize(in ReadOnlySequence buffer) + private bool TryDeserializeJson(in ReadOnlySequence buffer, out T? result) { if (buffer.Length == 0) { - return default; + result = default; + return true; } foreach (var context in _contexts) @@ -773,14 +979,13 @@ public void Serialize(IBufferWriter bufferWriter, T value) if (context.GetTypeInfo(typeof(T)) is JsonTypeInfo jsonTypeInfo) { var reader = new Utf8JsonReader(buffer); // Utf8JsonReader is ref struct, no allocate. - return JsonSerializer.Deserialize(ref reader, jsonTypeInfo); + result = JsonSerializer.Deserialize(ref reader, jsonTypeInfo); + return true; } } - if (_next != null) - return _next.Deserialize(buffer); - - throw new NatsException($"Can't deserialize {typeof(T)}"); + result = default; + return false; } } diff --git a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs index a1c0bd3be..1d7977cce 100644 --- a/tests/NATS.Client.Core.Tests/NatsMsgTests.cs +++ b/tests/NATS.Client.Core.Tests/NatsMsgTests.cs @@ -109,7 +109,7 @@ public void Create_nats_msg_by_object_with_header_aware_serializer() var msg = builder.Msg; var bufferWriter = new NatsPooledBufferWriter(256); - ((INatsSerializeWithContext)serializer).Serialize(bufferWriter, data, new NatsMsgContext { Subject = subject, ReplyTo = replyTo, Headers = headers }); + ((INatsSerializeWithContext)serializer).Serialize(bufferWriter, data, new NatsMsgContext(subject, replyTo, headers)); var serializedSize = bufferWriter.WrittenCount; var expectedSize = subject.Length + (replyTo?.Length ?? 0) + headers.GetBytesLength() + serializedSize; @@ -144,7 +144,7 @@ private class TestData public string Name { get; set; } = null!; } - private class HeaderAwareSerializer : INatsSerializer, INatsSerializeWithContext, INatsDeserializeWithContext + private class HeaderAwareSerializer : INatsSerializer, INatsSerializerWithContext { private readonly NatsJsonSerializer _inner = new(); diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs b/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs index ad16643f4..85a41936d 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsSerializationExtensionsTests.cs @@ -9,7 +9,7 @@ public void Serialize_with_context_aware_serializer_calls_context_overload() { var serializer = new TrackingSerializerWithContext(); var buffer = new NatsPooledBufferWriter(256); - var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; + var context = new NatsMsgContext("test", headers: new NatsHeaders { { "X-Test", "value" } }); ((INatsSerialize)serializer).Serialize(buffer, "test", in context); @@ -22,7 +22,7 @@ public void Serialize_without_context_aware_serializer_falls_back() { var serializer = new TrackingSerializer(); var buffer = new NatsPooledBufferWriter(256); - var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; + var context = new NatsMsgContext("test", headers: new NatsHeaders { { "X-Test", "value" } }); ((INatsSerialize)serializer).Serialize(buffer, "test", in context); @@ -34,7 +34,7 @@ public void Serialize_with_null_headers_still_calls_context_overload() { var serializer = new TrackingSerializerWithContext(); var buffer = new NatsPooledBufferWriter(256); - var context = new NatsMsgContext { Subject = "test" }; + var context = new NatsMsgContext("test"); ((INatsSerialize)serializer).Serialize(buffer, "test", in context); @@ -47,7 +47,7 @@ public void Deserialize_with_context_aware_deserializer_calls_context_overload() { var deserializer = new TrackingDeserializerWithContext(); var buffer = new ReadOnlySequence(new byte[] { 1 }); - var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; + var context = new NatsMsgContext("test", headers: new NatsHeaders { { "X-Test", "value" } }); ((INatsDeserialize)deserializer).Deserialize(buffer, in context); @@ -60,7 +60,7 @@ public void Deserialize_without_context_aware_deserializer_falls_back() { var deserializer = new TrackingDeserializer(); var buffer = new ReadOnlySequence(new byte[] { 1 }); - var context = new NatsMsgContext { Subject = "test", Headers = new NatsHeaders { { "X-Test", "value" } } }; + var context = new NatsMsgContext("test", headers: new NatsHeaders { { "X-Test", "value" } }); ((INatsDeserialize)deserializer).Deserialize(buffer, in context); @@ -72,7 +72,7 @@ public void Deserialize_with_null_headers_still_calls_context_overload() { var deserializer = new TrackingDeserializerWithContext(); var buffer = new ReadOnlySequence(new byte[] { 1 }); - var context = new NatsMsgContext { Subject = "test" }; + var context = new NatsMsgContext("test"); ((INatsDeserialize)deserializer).Deserialize(buffer, in context); @@ -80,6 +80,36 @@ public void Deserialize_with_null_headers_still_calls_context_overload() deserializer.StandardDeserializeCalled.Should().BeFalse(); } + [Fact] + public void Serialize_built_in_chain_propagates_context_to_leaf() + { + // A non-context built-in (NatsRawSerializer) chained with a context-aware leaf. + // For a type it can't handle, the built-in delegates to _next; the context must arrive at the leaf. + var leaf = new TrackingContextSerializer(); + var chained = NatsRawSerializer.Default.CombineWith(leaf); + var buffer = new NatsPooledBufferWriter(256); + var context = new NatsMsgContext("test", headers: new NatsHeaders { { "X-Test", "value" } }); + + chained.Serialize(buffer, "hello", in context); + + leaf.ContextSerializeCalled.Should().BeTrue(); + leaf.StandardSerializeCalled.Should().BeFalse(); + } + + [Fact] + public void Deserialize_built_in_chain_propagates_context_to_leaf() + { + var leaf = new TrackingContextSerializer(); + var chained = NatsRawSerializer.Default.CombineWith(leaf); + var buffer = new ReadOnlySequence(new byte[] { 1 }); + var context = new NatsMsgContext("test", headers: new NatsHeaders { { "X-Test", "value" } }); + + chained.Deserialize(buffer, in context); + + leaf.ContextDeserializeCalled.Should().BeTrue(); + leaf.StandardDeserializeCalled.Should().BeFalse(); + } + private class TrackingSerializer : INatsSerialize { public bool StandardSerializeCalled { get; private set; } @@ -130,4 +160,35 @@ private class TrackingDeserializerWithContext : INatsDeserialize, INatsD return null; } } + + private class TrackingContextSerializer : INatsSerializer, INatsSerializerWithContext + { + public bool StandardSerializeCalled { get; private set; } + + public bool ContextSerializeCalled { get; private set; } + + public bool StandardDeserializeCalled { get; private set; } + + public bool ContextDeserializeCalled { get; private set; } + + public void Serialize(IBufferWriter bufferWriter, string value) => + StandardSerializeCalled = true; + + public void Serialize(IBufferWriter bufferWriter, string value, in NatsMsgContext context) => + ContextSerializeCalled = true; + + public string? Deserialize(in ReadOnlySequence buffer) + { + StandardDeserializeCalled = true; + return null; + } + + public string? Deserialize(in ReadOnlySequence buffer, in NatsMsgContext context) + { + ContextDeserializeCalled = true; + return null; + } + + public INatsSerializer CombineWith(INatsSerializer next) => throw new NotSupportedException(); + } } diff --git a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index 51359dd0f..301ebc34a 100644 --- a/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -280,7 +280,7 @@ public class MixedSerializerRegistry : INatsSerializerRegistry #endregion #region header-aware-serializer -public class MyHeaderAwareSerializer : INatsSerializer, INatsSerializeWithContext, INatsDeserializeWithContext +public class MyHeaderAwareSerializer : INatsSerializer, INatsSerializerWithContext { private readonly NatsJsonContextSerializer _jsonSerializer; From fe4aa22d9d6d2fc3531023ad4644555226caef9d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 16 Apr 2026 14:06:06 +0100 Subject: [PATCH 20/20] sln: remove x64/x86 platform noise --- NATS.Net.sln | 428 --------------------------------------------------- 1 file changed, 428 deletions(-) diff --git a/NATS.Net.sln b/NATS.Net.sln index 57e4fd5d5..55575bdec 100644 --- a/NATS.Net.sln +++ b/NATS.Net.sln @@ -163,649 +163,221 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|Any CPU.Build.0 = Debug|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x64.ActiveCfg = Debug|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x64.Build.0 = Debug|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x86.ActiveCfg = Debug|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Debug|x86.Build.0 = Debug|Any CPU {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|Any CPU.ActiveCfg = Release|Any CPU {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|Any CPU.Build.0 = Release|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x64.ActiveCfg = Release|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x64.Build.0 = Release|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x86.ActiveCfg = Release|Any CPU - {702B4B02-7FEC-42A1-A399-6F39D4E2CA29}.Release|x86.Build.0 = Release|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x64.ActiveCfg = Debug|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x64.Build.0 = Debug|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x86.ActiveCfg = Debug|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Debug|x86.Build.0 = Debug|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|Any CPU.ActiveCfg = Release|Any CPU {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|Any CPU.Build.0 = Release|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x64.ActiveCfg = Release|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x64.Build.0 = Release|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x86.ActiveCfg = Release|Any CPU - {7FE6D43B-181E-485E-8C14-35EAA0EBF09A}.Release|x86.Build.0 = Release|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x64.ActiveCfg = Debug|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x64.Build.0 = Debug|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x86.ActiveCfg = Debug|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Debug|x86.Build.0 = Debug|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|Any CPU.ActiveCfg = Release|Any CPU {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|Any CPU.Build.0 = Release|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x64.ActiveCfg = Release|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x64.Build.0 = Release|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x86.ActiveCfg = Release|Any CPU - {D60B2ADF-450D-4F8F-A3C5-7803D36FE06B}.Release|x86.Build.0 = Release|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x64.ActiveCfg = Debug|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x64.Build.0 = Debug|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x86.ActiveCfg = Debug|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Debug|x86.Build.0 = Debug|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|Any CPU.ActiveCfg = Release|Any CPU {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|Any CPU.Build.0 = Release|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x64.ActiveCfg = Release|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x64.Build.0 = Release|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x86.ActiveCfg = Release|Any CPU - {A10F0D89-13F3-49B3-ACF7-66E45DC95225}.Release|x86.Build.0 = Release|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x64.ActiveCfg = Debug|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x64.Build.0 = Debug|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x86.ActiveCfg = Debug|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Debug|x86.Build.0 = Debug|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|Any CPU.Build.0 = Release|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x64.ActiveCfg = Release|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x64.Build.0 = Release|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x86.ActiveCfg = Release|Any CPU - {D3F09B30-1ED5-48C2-81CD-A2AD88E751AC}.Release|x86.Build.0 = Release|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x64.ActiveCfg = Debug|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x64.Build.0 = Debug|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x86.ActiveCfg = Debug|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Debug|x86.Build.0 = Debug|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|Any CPU.Build.0 = Release|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x64.ActiveCfg = Release|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x64.Build.0 = Release|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x86.ActiveCfg = Release|Any CPU - {44881DEE-8B49-44EA-B0BA-8BDA4F706E1A}.Release|x86.Build.0 = Release|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x64.ActiveCfg = Debug|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x64.Build.0 = Debug|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x86.ActiveCfg = Debug|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|x86.Build.0 = Debug|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|Any CPU.ActiveCfg = Release|Any CPU {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|Any CPU.Build.0 = Release|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x64.ActiveCfg = Release|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x64.Build.0 = Release|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x86.ActiveCfg = Release|Any CPU - {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|x86.Build.0 = Release|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x64.ActiveCfg = Debug|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x64.Build.0 = Debug|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x86.ActiveCfg = Debug|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|x86.Build.0 = Debug|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|Any CPU.Build.0 = Release|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x64.ActiveCfg = Release|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x64.Build.0 = Release|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x86.ActiveCfg = Release|Any CPU - {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|x86.Build.0 = Release|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x64.ActiveCfg = Debug|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x64.Build.0 = Debug|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x86.ActiveCfg = Debug|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|x86.Build.0 = Debug|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|Any CPU.Build.0 = Release|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x64.ActiveCfg = Release|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x64.Build.0 = Release|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x86.ActiveCfg = Release|Any CPU - {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|x86.Build.0 = Release|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x64.ActiveCfg = Debug|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x64.Build.0 = Debug|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x86.ActiveCfg = Debug|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|x86.Build.0 = Debug|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|Any CPU.Build.0 = Release|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x64.ActiveCfg = Release|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x64.Build.0 = Release|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x86.ActiveCfg = Release|Any CPU - {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|x86.Build.0 = Release|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x64.ActiveCfg = Debug|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x64.Build.0 = Debug|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x86.ActiveCfg = Debug|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Debug|x86.Build.0 = Debug|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|Any CPU.Build.0 = Release|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x64.ActiveCfg = Release|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x64.Build.0 = Release|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x86.ActiveCfg = Release|Any CPU - {ADF66CBA-4F3E-4E91-9842-E194E3BC06A1}.Release|x86.Build.0 = Release|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x64.ActiveCfg = Debug|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x64.Build.0 = Debug|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x86.ActiveCfg = Debug|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Debug|x86.Build.0 = Debug|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Release|Any CPU.ActiveCfg = Release|Any CPU {A102AB7B-A90C-4717-B17C-045240838060}.Release|Any CPU.Build.0 = Release|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Release|x64.ActiveCfg = Release|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Release|x64.Build.0 = Release|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Release|x86.ActiveCfg = Release|Any CPU - {A102AB7B-A90C-4717-B17C-045240838060}.Release|x86.Build.0 = Release|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x64.ActiveCfg = Debug|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x64.Build.0 = Debug|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x86.ActiveCfg = Debug|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Debug|x86.Build.0 = Debug|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|Any CPU.Build.0 = Release|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x64.ActiveCfg = Release|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x64.Build.0 = Release|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x86.ActiveCfg = Release|Any CPU - {908F2CED-CAC0-4A4E-AD19-362A413B5DA4}.Release|x86.Build.0 = Release|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x64.ActiveCfg = Debug|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x64.Build.0 = Debug|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x86.ActiveCfg = Debug|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Debug|x86.Build.0 = Debug|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|Any CPU.Build.0 = Release|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x64.ActiveCfg = Release|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x64.Build.0 = Release|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x86.ActiveCfg = Release|Any CPU - {3F8840BA-4F91-4359-AA53-6B26823E7F55}.Release|x86.Build.0 = Release|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x64.ActiveCfg = Debug|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x64.Build.0 = Debug|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x86.ActiveCfg = Debug|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Debug|x86.Build.0 = Debug|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|Any CPU.Build.0 = Release|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x64.ActiveCfg = Release|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x64.Build.0 = Release|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x86.ActiveCfg = Release|Any CPU - {BB2F4EEE-1AB3-43F7-B004-6C9B3D52353E}.Release|x86.Build.0 = Release|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x64.ActiveCfg = Debug|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x64.Build.0 = Debug|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x86.ActiveCfg = Debug|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Debug|x86.Build.0 = Debug|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|Any CPU.ActiveCfg = Release|Any CPU {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|Any CPU.Build.0 = Release|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x64.ActiveCfg = Release|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x64.Build.0 = Release|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x86.ActiveCfg = Release|Any CPU - {35578296-FF6C-4BA4-BEE5-C1A6E7DB7024}.Release|x86.Build.0 = Release|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x64.ActiveCfg = Debug|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x64.Build.0 = Debug|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x86.ActiveCfg = Debug|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Debug|x86.Build.0 = Debug|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|Any CPU.ActiveCfg = Release|Any CPU {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|Any CPU.Build.0 = Release|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x64.ActiveCfg = Release|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x64.Build.0 = Release|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x86.ActiveCfg = Release|Any CPU - {050C63EE-8F1C-4535-9C6C-E12E62A1FF1D}.Release|x86.Build.0 = Release|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|Any CPU.Build.0 = Debug|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x64.ActiveCfg = Debug|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x64.Build.0 = Debug|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x86.ActiveCfg = Debug|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Debug|x86.Build.0 = Debug|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|Any CPU.ActiveCfg = Release|Any CPU {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|Any CPU.Build.0 = Release|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x64.ActiveCfg = Release|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x64.Build.0 = Release|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x86.ActiveCfg = Release|Any CPU - {749CAE39-4C1E-4627-9E31-A36B987BC453}.Release|x86.Build.0 = Release|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x64.ActiveCfg = Debug|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x64.Build.0 = Debug|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x86.ActiveCfg = Debug|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Debug|x86.Build.0 = Debug|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|Any CPU.ActiveCfg = Release|Any CPU {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|Any CPU.Build.0 = Release|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x64.ActiveCfg = Release|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x64.Build.0 = Release|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x86.ActiveCfg = Release|Any CPU - {CF44A42E-C075-4C5C-BE8B-3DF266FC617B}.Release|x86.Build.0 = Release|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x64.ActiveCfg = Debug|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x64.Build.0 = Debug|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x86.ActiveCfg = Debug|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Debug|x86.Build.0 = Debug|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|Any CPU.Build.0 = Release|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x64.ActiveCfg = Release|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x64.Build.0 = Release|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x86.ActiveCfg = Release|Any CPU - {5B4B30A5-941B-44F9-98C6-06F0BB2242AB}.Release|x86.Build.0 = Release|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x64.ActiveCfg = Debug|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x64.Build.0 = Debug|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x86.ActiveCfg = Debug|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Debug|x86.Build.0 = Debug|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|Any CPU.Build.0 = Release|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x64.ActiveCfg = Release|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x64.Build.0 = Release|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x86.ActiveCfg = Release|Any CPU - {6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD}.Release|x86.Build.0 = Release|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x64.ActiveCfg = Debug|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x64.Build.0 = Debug|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x86.ActiveCfg = Debug|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|x86.Build.0 = Debug|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|Any CPU.ActiveCfg = Release|Any CPU {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|Any CPU.Build.0 = Release|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x64.ActiveCfg = Release|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x64.Build.0 = Release|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x86.ActiveCfg = Release|Any CPU - {389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|x86.Build.0 = Release|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x64.ActiveCfg = Debug|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x64.Build.0 = Debug|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x86.ActiveCfg = Debug|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|x86.Build.0 = Debug|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|Any CPU.Build.0 = Release|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x64.ActiveCfg = Release|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x64.Build.0 = Release|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x86.ActiveCfg = Release|Any CPU - {766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|x86.Build.0 = Release|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x64.ActiveCfg = Debug|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x64.Build.0 = Debug|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x86.ActiveCfg = Debug|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|x86.Build.0 = Debug|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|Any CPU.ActiveCfg = Release|Any CPU {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|Any CPU.Build.0 = Release|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x64.ActiveCfg = Release|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x64.Build.0 = Release|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x86.ActiveCfg = Release|Any CPU - {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|x86.Build.0 = Release|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x64.ActiveCfg = Debug|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x64.Build.0 = Debug|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x86.ActiveCfg = Debug|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|x86.Build.0 = Debug|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|Any CPU.Build.0 = Release|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x64.ActiveCfg = Release|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x64.Build.0 = Release|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x86.ActiveCfg = Release|Any CPU - {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|x86.Build.0 = Release|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x64.ActiveCfg = Debug|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x64.Build.0 = Debug|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x86.ActiveCfg = Debug|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|x86.Build.0 = Debug|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|Any CPU.ActiveCfg = Release|Any CPU {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|Any CPU.Build.0 = Release|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x64.ActiveCfg = Release|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x64.Build.0 = Release|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x86.ActiveCfg = Release|Any CPU - {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|x86.Build.0 = Release|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x64.ActiveCfg = Debug|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x64.Build.0 = Debug|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x86.ActiveCfg = Debug|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Debug|x86.Build.0 = Debug|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|Any CPU.ActiveCfg = Release|Any CPU {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|Any CPU.Build.0 = Release|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x64.ActiveCfg = Release|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x64.Build.0 = Release|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x86.ActiveCfg = Release|Any CPU - {6DAAAA87-8DDF-4E60-81CE-D8900327DE33}.Release|x86.Build.0 = Release|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x64.ActiveCfg = Debug|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x64.Build.0 = Debug|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x86.ActiveCfg = Debug|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Debug|x86.Build.0 = Debug|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU {227C88B1-0510-4010-B142-C44725578FCD}.Release|Any CPU.Build.0 = Release|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Release|x64.ActiveCfg = Release|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Release|x64.Build.0 = Release|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Release|x86.ActiveCfg = Release|Any CPU - {227C88B1-0510-4010-B142-C44725578FCD}.Release|x86.Build.0 = Release|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x64.ActiveCfg = Debug|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x64.Build.0 = Debug|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x86.ActiveCfg = Debug|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Debug|x86.Build.0 = Debug|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|Any CPU.Build.0 = Release|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x64.ActiveCfg = Release|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x64.Build.0 = Release|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x86.ActiveCfg = Release|Any CPU - {8A676AAA-FEE3-4C18-870A-66E59AD9069F}.Release|x86.Build.0 = Release|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x64.ActiveCfg = Debug|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x64.Build.0 = Debug|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x86.ActiveCfg = Debug|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Debug|x86.Build.0 = Debug|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|Any CPU.ActiveCfg = Release|Any CPU {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|Any CPU.Build.0 = Release|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x64.ActiveCfg = Release|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x64.Build.0 = Release|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x86.ActiveCfg = Release|Any CPU - {9521D9E0-642A-4C7E-BD10-372DF235CF62}.Release|x86.Build.0 = Release|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x64.ActiveCfg = Debug|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x64.Build.0 = Debug|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x86.ActiveCfg = Debug|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Debug|x86.Build.0 = Debug|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|Any CPU.Build.0 = Release|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x64.ActiveCfg = Release|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x64.Build.0 = Release|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x86.ActiveCfg = Release|Any CPU - {8C1E83E5-852F-447F-95B3-0CD2199F3A66}.Release|x86.Build.0 = Release|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x64.ActiveCfg = Debug|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x64.Build.0 = Debug|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x86.ActiveCfg = Debug|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Debug|x86.Build.0 = Debug|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|Any CPU.Build.0 = Release|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x64.ActiveCfg = Release|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x64.Build.0 = Release|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x86.ActiveCfg = Release|Any CPU - {D8E29F7F-8F7C-462D-8E5F-7C0DAA08D527}.Release|x86.Build.0 = Release|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x64.ActiveCfg = Debug|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x64.Build.0 = Debug|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x86.ActiveCfg = Debug|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Debug|x86.Build.0 = Debug|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|Any CPU.Build.0 = Release|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x64.ActiveCfg = Release|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x64.Build.0 = Release|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x86.ActiveCfg = Release|Any CPU - {6C039E97-4AFA-4FB7-B361-C40001A632B2}.Release|x86.Build.0 = Release|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x64.ActiveCfg = Debug|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x64.Build.0 = Debug|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x86.ActiveCfg = Debug|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Debug|x86.Build.0 = Debug|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A8222A7-699E-493E-852B-87646FD7C311}.Release|Any CPU.Build.0 = Release|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x64.ActiveCfg = Release|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x64.Build.0 = Release|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x86.ActiveCfg = Release|Any CPU - {1A8222A7-699E-493E-852B-87646FD7C311}.Release|x86.Build.0 = Release|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x64.ActiveCfg = Debug|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x64.Build.0 = Debug|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x86.ActiveCfg = Debug|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Debug|x86.Build.0 = Debug|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|Any CPU.Build.0 = Release|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x64.ActiveCfg = Release|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x64.Build.0 = Release|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x86.ActiveCfg = Release|Any CPU - {A7B6B97C-278B-4593-9967-6D791045C0B6}.Release|x86.Build.0 = Release|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x64.ActiveCfg = Debug|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x64.Build.0 = Debug|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x86.ActiveCfg = Debug|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Debug|x86.Build.0 = Debug|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|Any CPU.ActiveCfg = Release|Any CPU {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|Any CPU.Build.0 = Release|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x64.ActiveCfg = Release|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x64.Build.0 = Release|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x86.ActiveCfg = Release|Any CPU - {94AF0E32-E0F7-417D-A010-0545964641F4}.Release|x86.Build.0 = Release|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x64.ActiveCfg = Debug|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x64.Build.0 = Debug|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x86.ActiveCfg = Debug|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Debug|x86.Build.0 = Debug|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|Any CPU.Build.0 = Release|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x64.ActiveCfg = Release|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x64.Build.0 = Release|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x86.ActiveCfg = Release|Any CPU - {C075FACD-83A3-48F2-9F6E-D181F66FB2EC}.Release|x86.Build.0 = Release|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x64.ActiveCfg = Debug|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x64.Build.0 = Debug|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x86.ActiveCfg = Debug|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Debug|x86.Build.0 = Debug|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|Any CPU.Build.0 = Release|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x64.ActiveCfg = Release|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x64.Build.0 = Release|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x86.ActiveCfg = Release|Any CPU - {4501D331-9428-48AC-8C89-52034BF7F06F}.Release|x86.Build.0 = Release|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x64.ActiveCfg = Debug|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x64.Build.0 = Debug|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x86.ActiveCfg = Debug|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Debug|x86.Build.0 = Debug|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|Any CPU.ActiveCfg = Release|Any CPU {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|Any CPU.Build.0 = Release|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x64.ActiveCfg = Release|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x64.Build.0 = Release|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x86.ActiveCfg = Release|Any CPU - {92AB528E-2BE4-438F-9C0E-83BB3D967403}.Release|x86.Build.0 = Release|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x64.ActiveCfg = Debug|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x64.Build.0 = Debug|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x86.ActiveCfg = Debug|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Debug|x86.Build.0 = Debug|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|Any CPU.Build.0 = Release|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x64.ActiveCfg = Release|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x64.Build.0 = Release|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x86.ActiveCfg = Release|Any CPU - {8E7B180C-7057-4C81-BD0F-D35A74501FCB}.Release|x86.Build.0 = Release|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x64.ActiveCfg = Debug|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x64.Build.0 = Debug|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x86.ActiveCfg = Debug|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Debug|x86.Build.0 = Debug|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|Any CPU.ActiveCfg = Release|Any CPU {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|Any CPU.Build.0 = Release|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x64.ActiveCfg = Release|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x64.Build.0 = Release|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x86.ActiveCfg = Release|Any CPU - {89162E83-DEFA-4474-B6AE-669EB64374DB}.Release|x86.Build.0 = Release|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x64.ActiveCfg = Debug|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x64.Build.0 = Debug|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x86.ActiveCfg = Debug|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Debug|x86.Build.0 = Debug|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|Any CPU.ActiveCfg = Release|Any CPU {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|Any CPU.Build.0 = Release|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x64.ActiveCfg = Release|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x64.Build.0 = Release|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x86.ActiveCfg = Release|Any CPU - {C498BDCC-CD27-4867-85AA-00EFA5EE3809}.Release|x86.Build.0 = Release|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x64.ActiveCfg = Debug|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x64.Build.0 = Debug|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x86.ActiveCfg = Debug|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Debug|x86.Build.0 = Debug|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|Any CPU.Build.0 = Release|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x64.ActiveCfg = Release|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x64.Build.0 = Release|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x86.ActiveCfg = Release|Any CPU - {B86AF861-EB06-45A7-9132-E4DDB498C98C}.Release|x86.Build.0 = Release|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x64.ActiveCfg = Debug|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x64.Build.0 = Debug|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x86.ActiveCfg = Debug|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Debug|x86.Build.0 = Debug|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|Any CPU.Build.0 = Release|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x64.ActiveCfg = Release|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x64.Build.0 = Release|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x86.ActiveCfg = Release|Any CPU - {1B8B170B-4FD8-4CAE-97E3-4351DBD7939A}.Release|x86.Build.0 = Release|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x64.ActiveCfg = Debug|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x64.Build.0 = Debug|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x86.ActiveCfg = Debug|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Debug|x86.Build.0 = Debug|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|Any CPU.Build.0 = Release|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x64.ActiveCfg = Release|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x64.Build.0 = Release|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x86.ActiveCfg = Release|Any CPU - {8ED5D3DC-86F7-4599-8ED5-C0E20FFDE7F6}.Release|x86.Build.0 = Release|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x64.ActiveCfg = Debug|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x64.Build.0 = Debug|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x86.ActiveCfg = Debug|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Debug|x86.Build.0 = Debug|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|Any CPU.ActiveCfg = Release|Any CPU {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|Any CPU.Build.0 = Release|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x64.ActiveCfg = Release|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x64.Build.0 = Release|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x86.ActiveCfg = Release|Any CPU - {0527FB48-60E0-4D38-B3D9-0A889E1FFAB2}.Release|x86.Build.0 = Release|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x64.ActiveCfg = Debug|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x64.Build.0 = Debug|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x86.ActiveCfg = Debug|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Debug|x86.Build.0 = Debug|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|Any CPU.Build.0 = Release|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x64.ActiveCfg = Release|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x64.Build.0 = Release|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x86.ActiveCfg = Release|Any CPU - {1EA44258-04FC-48D5-A60D-AE92214DEB1A}.Release|x86.Build.0 = Release|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x64.ActiveCfg = Debug|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x64.Build.0 = Debug|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x86.ActiveCfg = Debug|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Debug|x86.Build.0 = Debug|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|Any CPU.Build.0 = Release|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x64.ActiveCfg = Release|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x64.Build.0 = Release|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x86.ActiveCfg = Release|Any CPU - {0B6CBF18-D11C-4CC3-A50C-437A9F7E761D}.Release|x86.Build.0 = Release|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x64.Build.0 = Debug|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x86.ActiveCfg = Debug|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Debug|x86.Build.0 = Debug|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|Any CPU.Build.0 = Release|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x64.ActiveCfg = Release|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x64.Build.0 = Release|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x86.ActiveCfg = Release|Any CPU - {1F6C87C7-1911-4E96-ADB4-1EF27BD09C60}.Release|x86.Build.0 = Release|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x64.ActiveCfg = Debug|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x64.Build.0 = Debug|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x86.ActiveCfg = Debug|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Debug|x86.Build.0 = Debug|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|Any CPU.ActiveCfg = Release|Any CPU {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|Any CPU.Build.0 = Release|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x64.ActiveCfg = Release|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x64.Build.0 = Release|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x86.ActiveCfg = Release|Any CPU - {71C2BC96-C104-4748-B6CF-E74BAC4E709D}.Release|x86.Build.0 = Release|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x64.ActiveCfg = Debug|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x64.Build.0 = Debug|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x86.ActiveCfg = Debug|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Debug|x86.Build.0 = Debug|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|Any CPU.ActiveCfg = Release|Any CPU {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|Any CPU.Build.0 = Release|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x64.ActiveCfg = Release|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x64.Build.0 = Release|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x86.ActiveCfg = Release|Any CPU - {21649885-B40A-4F6B-9286-A7E378276C3E}.Release|x86.Build.0 = Release|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x64.ActiveCfg = Debug|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x64.Build.0 = Debug|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x86.ActiveCfg = Debug|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Debug|x86.Build.0 = Debug|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|Any CPU.Build.0 = Release|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x64.ActiveCfg = Release|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x64.Build.0 = Release|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x86.ActiveCfg = Release|Any CPU - {DA8E9EC2-990C-4DA2-A3F3-7E7796C71DD8}.Release|x86.Build.0 = Release|Any CPU {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x64.ActiveCfg = Debug|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x64.Build.0 = Debug|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x86.ActiveCfg = Debug|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Debug|x86.Build.0 = Debug|Any CPU {93E92151-28BF-4176-92F4-65E1227C3269}.Release|Any CPU.ActiveCfg = Release|Any CPU {93E92151-28BF-4176-92F4-65E1227C3269}.Release|Any CPU.Build.0 = Release|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x64.ActiveCfg = Release|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x64.Build.0 = Release|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x86.ActiveCfg = Release|Any CPU - {93E92151-28BF-4176-92F4-65E1227C3269}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE