diff --git a/src/Samples/Sample.Serialization.ConsoleApp/Program.cs b/src/Samples/Sample.Serialization.ConsoleApp/Program.cs index 15a7face..c775e5c0 100644 --- a/src/Samples/Sample.Serialization.ConsoleApp/Program.cs +++ b/src/Samples/Sample.Serialization.ConsoleApp/Program.cs @@ -11,7 +11,6 @@ using SlimMessageBus.Host; using SlimMessageBus.Host.Memory; using SlimMessageBus.Host.Redis; -using SlimMessageBus.Host.Serialization; using SlimMessageBus.Host.Serialization.Avro; using SlimMessageBus.Host.Serialization.Hybrid; using SlimMessageBus.Host.Serialization.Json; @@ -27,7 +26,7 @@ enum Provider /// /// This sample shows: -/// 1. How tu use the Avro serializer (for contract Avro IDL first apprach to generate C# code) +/// 1. How tu use the Avro serializer (for contract Avro IDL first approach to generate C# code) /// 2. How to combine two serializer approaches in one app (using the Hybrid serializer). /// class Program @@ -40,17 +39,11 @@ static async Task Main(string[] args) => await Host.CreateDefaultBuilder(args) services.AddHostedService(); - // alternatively a simpler approach, but using the slower ReflectionMessageCreationStategy and ReflectionSchemaLookupStrategy - var avroSerializer = new AvroMessageSerializer(); - - // Avro serialized using the AvroConvert library - no schema generation neeeded upfront. - var jsonSerializer = new JsonMessageSerializer(); - services .AddSlimMessageBus(mbb => { // Note: remember that Memory provider does not support req-resp yet. - var provider = Provider.Redis; + var provider = Provider.Memory; /* var sl = new DictionarySchemaLookupStrategy(); @@ -59,7 +52,7 @@ static async Task Main(string[] args) => await Host.CreateDefaultBuilder(args) sl.Add(typeof(MultiplyRequest), MultiplyRequest._SCHEMA); sl.Add(typeof(MultiplyResponse), MultiplyResponse._SCHEMA); - var mf = new DictionaryMessageCreationStategy(); + var mf = new DictionaryMessageCreationStrategy(); /// register all your types mf.Add(typeof(AddCommand), () => new AddCommand()); mf.Add(typeof(MultiplyRequest), () => new MultiplyRequest()); @@ -72,12 +65,14 @@ static async Task Main(string[] args) => await Host.CreateDefaultBuilder(args) mbb .AddServicesFromAssemblyContaining() // Note: Certain messages will be serialized by one Avro serializer, other using the Json serializer - .AddHybridSerializer(new Dictionary + .AddAvroSerializer() + .AddJsonSerializer() + // Include AddHybridSerializer after other serializers so that the DI container can be updated + .AddHybridSerializer(o => { - [jsonSerializer] = new[] { typeof(SubtractCommand) }, // the first one will be the default serializer, no need to declare types here - [avroSerializer] = new[] { typeof(AddCommand), typeof(MultiplyRequest), typeof(MultiplyResponse) }, - }, defaultMessageSerializer: jsonSerializer) - + //o.Add(typeof(SubtractCommand)); // can also be omitted as JsonMessageSerializer is the default + o.Add(typeof(AddCommand), typeof(MultiplyRequest), typeof(MultiplyResponse)); + }) .Produce(x => x.DefaultTopic("AddCommand")) .Consume(x => x.Topic("AddCommand").WithConsumer()) @@ -221,7 +216,7 @@ public class SubtractCommandConsumer : IConsumer { public async Task OnHandle(SubtractCommand message) { - Console.WriteLine("Consumer: Subracting {0} and {1} gives {2}", message.Left, message.Right, message.Left - message.Right); + Console.WriteLine("Consumer: Subtracting {0} and {1} gives {2}", message.Left, message.Right, message.Left - message.Right); await Task.Delay(50); // Simulate some work } } diff --git a/src/SlimMessageBus.Host.Serialization.Avro/AvroMessageSerializer.cs b/src/SlimMessageBus.Host.Serialization.Avro/AvroMessageSerializer.cs index 831796af..38a3a5a5 100644 --- a/src/SlimMessageBus.Host.Serialization.Avro/AvroMessageSerializer.cs +++ b/src/SlimMessageBus.Host.Serialization.Avro/AvroMessageSerializer.cs @@ -84,7 +84,7 @@ public object Deserialize(Type t, byte[] payload) var writerSchema = WriteSchemaLookup(t); AssertSchemaNotNull(t, writerSchema, true); - _logger.LogDebug("Type {0} writer schema: {1}, reader schema: {2}", t, writerSchema, readerSchema); + _logger.LogDebug("Type {MessageType} writer schema: {WriterSchema}, reader schema: {ReaderSchema}", t, writerSchema, readerSchema); var reader = new SpecificDefaultReader(writerSchema, readerSchema); reader.Read(message, dec); @@ -108,7 +108,7 @@ public byte[] Serialize(Type t, object message) var writerSchema = WriteSchemaLookup(t); AssertSchemaNotNull(t, writerSchema, true); - _logger.LogDebug("Type {0} writer schema: {1}", t, writerSchema); + _logger.LogDebug("Type {MessageType} writer schema: {WriterSchema}", t, writerSchema); var writer = new SpecificDefaultWriter(writerSchema); // Schema comes from pre-compiled, code-gen phase writer.Write(message, enc); diff --git a/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs b/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs index 5d2106f7..2645a345 100644 --- a/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs +++ b/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs @@ -8,10 +8,12 @@ public class HybridMessageSerializer : IMessageSerializer { private readonly ILogger _logger; - private readonly IList _serializers = new List(); - private readonly IDictionary _serializerByType = new Dictionary(); + private readonly Dictionary _serializerByType = []; + public IMessageSerializer DefaultSerializer { get; set; } + internal IReadOnlyDictionary SerializerByType => _serializerByType; + public HybridMessageSerializer(ILogger logger, IDictionary registration, IMessageSerializer defaultMessageSerializer = null) { _logger = logger; @@ -24,12 +26,14 @@ public HybridMessageSerializer(ILogger logger, IDiction public void Add(IMessageSerializer serializer, params Type[] supportedTypes) { - if (_serializers.Count == 0 && DefaultSerializer == null) - { - DefaultSerializer = serializer; - } +#if NETSTANDARD2_0 + if (serializer is null) throw new ArgumentNullException(nameof(serializer)); +#else + ArgumentNullException.ThrowIfNull(serializer); +#endif + + DefaultSerializer ??= serializer; - _serializers.Add(serializer); foreach (var type in supportedTypes) { _serializerByType.Add(type, serializer); @@ -38,19 +42,19 @@ public void Add(IMessageSerializer serializer, params Type[] supportedTypes) protected virtual IMessageSerializer MatchSerializer(Type t) { - if (_serializers.Count == 0) - { - throw new InvalidOperationException("No serializers registered."); - } - if (!_serializerByType.TryGetValue(t, out var serializer)) { - // use first as default - _logger.LogTrace("Serializer for type {0} not registered, will use default serializer", t); + _logger.LogTrace("Serializer for type {MessageType} not registered, will use default serializer", t); + + if (DefaultSerializer == null) + { + throw new InvalidOperationException("No serializers registered."); + } + serializer = DefaultSerializer; } - _logger.LogDebug("Serializer for type {0} will be {1}", t, serializer); + _logger.LogDebug("Serializer for type {MessageType} will be {Serializer}", t, serializer); return serializer; } diff --git a/src/SlimMessageBus.Host.Serialization.Hybrid/MessageBusBuilderExtensions.cs b/src/SlimMessageBus.Host.Serialization.Hybrid/MessageBusBuilderExtensions.cs index b2a82cb6..5e04f2fe 100644 --- a/src/SlimMessageBus.Host.Serialization.Hybrid/MessageBusBuilderExtensions.cs +++ b/src/SlimMessageBus.Host.Serialization.Hybrid/MessageBusBuilderExtensions.cs @@ -1,5 +1,7 @@ namespace SlimMessageBus.Host.Serialization.Hybrid; +using System.Collections.Concurrent; + using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -24,4 +26,50 @@ public static MessageBusBuilder AddHybridSerializer(this MessageBusBuilder mbb, }); return mbb; } + + /// + /// Registers the with implementation as using serializers as registered in the . + /// + /// + /// Action to register serializers for dependency injection resolution. + /// The default serializer to be used when the message type cannot be matched + /// + public static MessageBusBuilder AddHybridSerializer(this MessageBusBuilder mbb, Action registration) + where TDefaultSerializer : class, IMessageSerializer + { + mbb.PostConfigurationActions.Add(services => + { + services.RemoveAll(typeof(IMessageSerializer)); + services.TryAddSingleton(svp => + { + var builder = new HybridSerializerOptionsBuilder(); + registration(builder); + + var registrations = builder.Registrations.ToDictionary(x => (IMessageSerializer)svp.GetRequiredService(x.Key), x => x.Value.ToArray()); + var defaultMessageSerializer = svp.GetRequiredService(); + return new HybridMessageSerializer(svp.GetRequiredService>(), registrations, defaultMessageSerializer); + }); + + services.TryAddSingleton(svp => svp.GetRequiredService()); + }); + return mbb; + } + + public sealed class HybridSerializerOptionsBuilder + { + internal ConcurrentDictionary> Registrations { get; } = new(); + + public HybridSerializerOptionsBuilder Add(params Type[] types) + where TMessageSerializer : IMessageSerializer + { + if (types.Length == 0) + { + return this; + } + + Registrations.GetOrAdd(typeof(TMessageSerializer), _ => []).AddRange(types); + + return this; + } + } } diff --git a/src/SlimMessageBus.Host.Serialization.Hybrid/README.md b/src/SlimMessageBus.Host.Serialization.Hybrid/README.md index b1da2153..fc26cbcf 100644 --- a/src/SlimMessageBus.Host.Serialization.Hybrid/README.md +++ b/src/SlimMessageBus.Host.Serialization.Hybrid/README.md @@ -1,4 +1,30 @@ # What -Message serialization that based on message type delegates to the respective serializer. +Message serialization that is based on message type, delegating serialization to the respective serializer. +```c# + + services + .AddSlimMessageBus(mbb => + { + mbb + // add required serializers to the DI container first + .AddAvroSerializer() + .AddGoogleProtobufSerializer() + .AddJsonSerializer() + + // Add hybrid serializer last so that it can update DI container and set itself as the default + .AddHybridSerializer( + o => { + + // Message1, Message2 => AvroSerializer + // Message3 => GoogleProtobufMessageSerializer + // all other messages by JsonMessageSerializer + + o.Add(typeof(Message1), typeof(Message2)); + o.Add(typeof(Message3)); + }) + ... + } + +``` diff --git a/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj b/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj index f6633595..eb09035e 100644 --- a/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj +++ b/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj @@ -16,4 +16,8 @@ + + + + diff --git a/src/SlimMessageBus.sln b/src/SlimMessageBus.sln index 885de7c5..ae9bdd87 100644 --- a/src/SlimMessageBus.sln +++ b/src/SlimMessageBus.sln @@ -230,9 +230,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlimMessageBus.Host.RabbitM EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlimMessageBus.Host.RabbitMQ.Test", "Tests\SlimMessageBus.Host.RabbitMQ.Test\SlimMessageBus.Host.RabbitMQ.Test.csproj", "{F5373E1D-A2B4-46CC-9B07-94F6655C8E29}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlimMessageBus.Host.Sql", "SlimMessageBus.Host.Sql\SlimMessageBus.Host.Sql.csproj", "{5EED0E89-2475-40E0-81EF-0F05C9326612}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlimMessageBus.Host.Sql", "SlimMessageBus.Host.Sql\SlimMessageBus.Host.Sql.csproj", "{5EED0E89-2475-40E0-81EF-0F05C9326612}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlimMessageBus.Host.Sql.Common", "SlimMessageBus.Host.Sql.Common\SlimMessageBus.Host.Sql.Common.csproj", "{F19B7A21-7749-465A-8810-4C274A9E8956}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlimMessageBus.Host.Sql.Common", "SlimMessageBus.Host.Sql.Common\SlimMessageBus.Host.Sql.Common.csproj", "{F19B7A21-7749-465A-8810-4C274A9E8956}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlimMessageBus.Host.Serialization.Hybrid.Test", "Tests\SlimMessageBus.Host.Serialization.Hybrid.Test\SlimMessageBus.Host.Serialization.Hybrid.Test.csproj", "{DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -730,6 +732,14 @@ Global {F19B7A21-7749-465A-8810-4C274A9E8956}.Release|Any CPU.Build.0 = Release|Any CPU {F19B7A21-7749-465A-8810-4C274A9E8956}.Release|x86.ActiveCfg = Release|Any CPU {F19B7A21-7749-465A-8810-4C274A9E8956}.Release|x86.Build.0 = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|x86.Build.0 = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|Any CPU.Build.0 = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|x86.ActiveCfg = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -803,6 +813,7 @@ Global {F5373E1D-A2B4-46CC-9B07-94F6655C8E29} = {9F005B5C-A856-4351-8C0C-47A8B785C637} {5EED0E89-2475-40E0-81EF-0F05C9326612} = {9291D340-B4FA-44A3-8060-C14743FB1712} {F19B7A21-7749-465A-8810-4C274A9E8956} = {9291D340-B4FA-44A3-8060-C14743FB1712} + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46} = {9F005B5C-A856-4351-8C0C-47A8B785C637} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {435A0D65-610C-4B84-B1AA-2C7FBE72DB80} diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Helpers/SampleMessages.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Helpers/SampleMessages.cs new file mode 100644 index 00000000..7ebf5623 --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Helpers/SampleMessages.cs @@ -0,0 +1,6 @@ +namespace SlimMessageBus.Host.Serialization.Hybrid.Test.Helpers +{ + public record SampleOne; + public record SampleTwo; + public record SampleThree; +} diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/HybridMessageSerializerTests.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/HybridMessageSerializerTests.cs new file mode 100644 index 00000000..45576ce0 --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/HybridMessageSerializerTests.cs @@ -0,0 +1,112 @@ +namespace SlimMessageBus.Host.Serialization.Hybrid.Test +{ + using System; + + using FluentAssertions; + + using Microsoft.Extensions.Logging; + + using Moq; + + using SlimMessageBus.Host.Serialization.Hybrid.Test.Helpers; + + public class HybridMessageSerializerTests + { + [Fact] + public void When_ConstructorReceivesARepeatedDefinition_Then_ThrowException() + { + // arrange + var mockDefaultSerializer = new Mock(); + var mockSerializer1 = new Mock(); + var mockSerializer2 = new Mock(); + var mockLogger = new Mock>(); + + var serializers = new Dictionary + { + { mockSerializer1.Object, new[] { typeof(SampleOne), typeof(SampleTwo) } }, + { mockSerializer2.Object, new[] { typeof(SampleOne) } }, + }; + + // act + var act = () => new HybridMessageSerializer(mockLogger.Object, serializers, mockDefaultSerializer.Object); + + // assert + act.Should().Throw(); + } + + [Fact] + public void When_NoDefaultSerializerIsSupplied_Then_UseFirstSpecialist() + { + // arrange + var mockDefaultSerializer = new Mock(); + var mockSerializer1 = new Mock(); + var mockLogger = new Mock>(); + + var serializers = new Dictionary + { + { mockDefaultSerializer.Object, new[] { typeof(SampleOne) } }, + { mockSerializer1.Object, new[] { typeof(SampleTwo) } }, + }; + + // act + var target = new HybridMessageSerializer(mockLogger.Object, serializers, default); + var actual = target.DefaultSerializer; + + // assert + actual.Should().BeEquivalentTo(mockDefaultSerializer.Object); + } + + [Fact] + public void When_ASpecialisedMessageIsSerialized_Then_UseSpecializedSerializer() + { + // arrange + var mockDefaultSerializer = new Mock(); + + var mockSerializer1 = new Mock(); + mockSerializer1.Setup(x => x.Serialize(typeof(SampleOne), It.IsAny())).Verifiable(Times.Once()); + + var mockSerializer2 = new Mock(); + + var mockLogger = new Mock>(); + var serializers = new Dictionary + { + { mockSerializer1.Object, new[] { typeof(SampleOne) } }, + { mockSerializer2.Object, new[] { typeof(SampleTwo) } }, + }; + + // act + var target = new HybridMessageSerializer(mockLogger.Object, serializers, mockDefaultSerializer.Object); + var _ = target.Serialize(typeof(SampleOne), new SampleOne()); + + // assert + mockSerializer1.Verify(x => x.Serialize(typeof(SampleOne), It.IsAny())); + mockSerializer2.VerifyNoOtherCalls(); + mockDefaultSerializer.VerifyNoOtherCalls(); + } + + [Fact] + public void When_AGenericMessageIsSerialized_Then_UseDefaultSerializer() + { + // arrange + var mockDefaultSerializer = new Mock(); + mockDefaultSerializer.Setup(x => x.Serialize(typeof(SampleOne), It.IsAny())).Verifiable(Times.Once()); + + var mockSerializer1 = new Mock(); + + var mockLogger = new Mock>(); + var serializers = new Dictionary + { + { mockSerializer1.Object, new[] { typeof(SampleTwo) } } + }; + + // act + var target = new HybridMessageSerializer(mockLogger.Object, serializers, mockDefaultSerializer.Object); + var _ = target.Serialize(typeof(SampleOne), new SampleOne()); + + // assert + mockDefaultSerializer.Verify(x => x.Serialize(typeof(SampleOne), It.IsAny())); + mockSerializer1.VerifyNoOtherCalls(); + } + } +} + diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/MessageBusBuilderExtensionsTests.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/MessageBusBuilderExtensionsTests.cs new file mode 100644 index 00000000..ff3d35dd --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/MessageBusBuilderExtensionsTests.cs @@ -0,0 +1,94 @@ +namespace SlimMessageBus.Host.Serialization.Hybrid.Test; + +using System; + +using FluentAssertions; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; + +using Moq; + +using SlimMessageBus.Host.Serialization.Hybrid; +using SlimMessageBus.Host.Serialization.Hybrid.Test.Helpers; + +public class MessageBusBuilderExtensionsTests +{ + private readonly IServiceCollection _services; + + public MessageBusBuilderExtensionsTests() + { + // arrange + var mockLogger = new Mock>(); + + _services = new ServiceCollection(); + _services.AddSingleton(mockLogger.Object); + _services.TryAddSingleton(svp => new SerializerOne()); + _services.TryAddSingleton(svp => svp.GetRequiredService()); + _services.TryAddSingleton(svp => new SerializerTwo()); + _services.TryAddSingleton(svp => svp.GetRequiredService()); + _services.TryAddSingleton(svp => new SerializerThree()); + _services.TryAddSingleton(svp => svp.GetRequiredService()); + + // act + _services.AddSlimMessageBus(cfg => + { + cfg.AddHybridSerializer(o => + { + o.Add(typeof(SampleTwo)); + o.Add(typeof(SampleThree)); + }); + }); + } + + [Fact] + public void When_IMessageSerializerRegistrationsAlreadyExist_Then_RemovePreviousRegistrations() + { + // assert + _services.Count(x => x.ServiceType == typeof(IMessageSerializer)).Should().Be(1); + } + + [Fact] + public void When_HybridMessageSerializerIsAdded_Then_RegisterAsIMessageSerializer() + { + // act + var serviceProvider = _services.BuildServiceProvider(); + var target = serviceProvider.GetServices().ToList(); + + // assert + target.Count.Should().Be(1); + target.Single().GetType().Should().Be(typeof(HybridMessageSerializer)); + } + + [Fact] + public void When_HybridMessageSerializerIsAdded_Then_SerializersAndTypesShouldConfigured() + { + // act + var serviceProvider = _services.BuildServiceProvider(); + var target = serviceProvider.GetService(); + + // assert + target.DefaultSerializer.GetType().Should().Be(typeof(SerializerOne)); + target.SerializerByType.Count.Should().Be(2); + target.SerializerByType.Should().ContainKey(typeof(SampleTwo)).WhoseValue.Should().BeOfType(); + target.SerializerByType.Should().ContainKey(typeof(SampleThree)).WhoseValue.Should().BeOfType(); + } + + public abstract class AbstractSerializer : IMessageSerializer + { + public object Deserialize(Type t, byte[] payload) + { + throw new NotImplementedException(); + } + + public byte[] Serialize(Type t, object message) + { + throw new NotImplementedException(); + } + } + + public class SerializerOne : AbstractSerializer { } + public class SerializerTwo : AbstractSerializer { } + public class SerializerThree : AbstractSerializer { } +} \ No newline at end of file diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SlimMessageBus.Host.Serialization.Hybrid.Test.csproj b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SlimMessageBus.Host.Serialization.Hybrid.Test.csproj new file mode 100644 index 00000000..5006b25a --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SlimMessageBus.Host.Serialization.Hybrid.Test.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Usings.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Usings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file