From 33b66c8c6fa5ee3e0f7b64ff375f87c69217d0dd Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 1 May 2026 09:56:14 +0100 Subject: [PATCH 1/5] Migrate from Newtonsoft.Json to System.Text.Json Replaced all usages of Newtonsoft.Json with a new StringSerialiser abstraction based on System.Text.Json. Updated ClientProxyBase and related classes to accept injected serialization functions. Refactored event store and test code to use StringSerialiser for all serialization/deserialization, including anonymous types. Expanded serialization options for property naming and formatting. Removed Newtonsoft.Json package references and obsolete code. Added new tests for serialization and health checks. The codebase is now fully System.Text.Json-based and serialization-agnostic. --- ClientProxyBase/ClientProxyBase.cs | 33 ++-- ClientProxyBase/ClientProxyBase_DELETE.cs | 1 - ClientProxyBase/ClientProxyBase_GET.cs | 5 +- ClientProxyBase/ClientProxyBase_PATCH.cs | 6 +- ClientProxyBase/ClientProxyBase_POST.cs | 10 +- ClientProxyBase/ClientProxyBase_PUT.cs | 4 +- Driver/TestAggregate.cs | 2 - .../EventSourcing/DomainEventRecord.cs | 51 +----- .../Shared.DomainDrivenDesign.csproj | 1 - .../AggregateRepositoryTests.cs | 9 +- .../DomainEventFactoryTests.cs | 53 ++----- .../EventDataFactoryTests.cs | 62 ++++++++ ...ntStoreConnectionStringHealthCheckTests.cs | 72 +++++++++ ...tStoreHealthCheckBuilderExtensionsTests.cs | 62 +------- .../PersistentSubscriptionTests.cs | 3 + .../TestObjects/TestData.cs | 8 +- .../TypeMapConvertorTests.cs | 18 ++- .../Aggregate/AggregateVersion.cs | 26 +-- .../Aggregate/DomainEventFactory.cs | 61 ++----- .../Aggregate/EventDataFactory.cs | 63 +------- Shared.EventStore/Shared.EventStore.csproj | 1 - .../PersistentSubscription.cs | 16 +- .../PersistentSubscriptionInfo.cs | 2 - .../SubscriptionRepository.cs | 6 +- .../EventStoreContextTests.cs | 31 +++- .../EventStoreDockerHelper.cs | 18 +-- .../Shared.EventStoreContext.Tests.csproj | 2 +- .../Ductus/BaseDockerHelper.cs | 4 +- .../TestContainers/BaseDockerHelper.cs | 11 +- .../TestContainers/DockerHelper.cs | 8 +- Shared.Tests/ClientProxyBaseTests.cs | 35 ++-- Shared.Tests/ConfigurationReaderTests.cs | 1 + .../ConfigurationRootExtensionsTests.cs | 12 -- Shared.Tests/HttpMethodTests.cs | 22 ++- Shared.Tests/MiddlewareTests.cs | 9 +- Shared.Tests/StringExtensionsTests.cs | 2 +- Shared.Tests/StringSerialiserTests.cs | 76 ++++----- Shared.Tests/TestHelpers.cs | 23 ++- Shared/Extensions/StringExtensions.cs | 37 ++--- Shared/General/ConfigurationReader.cs | 92 +++-------- Shared/HealthChecks/HealthCheckClient.cs | 17 +- Shared/Middleware/CorrelationIdMiddleware.cs | 15 +- .../Middleware/ExceptionHandlerMiddleware.cs | 81 ++-------- ...nIgnoreAttributeIgnorerContractResolver.cs | 36 ----- Shared/Serialisation/StringSerialiser.cs | 149 ++++++++++++++++-- Shared/Shared.csproj | 1 - 46 files changed, 545 insertions(+), 712 deletions(-) create mode 100644 Shared.EventStore.Tests/EventDataFactoryTests.cs create mode 100644 Shared.EventStore.Tests/EventStoreConnectionStringHealthCheckTests.cs delete mode 100644 Shared/Serialisation/JsonIgnoreAttributeIgnorerContractResolver.cs diff --git a/ClientProxyBase/ClientProxyBase.cs b/ClientProxyBase/ClientProxyBase.cs index 60ccf840..432f99f2 100644 --- a/ClientProxyBase/ClientProxyBase.cs +++ b/ClientProxyBase/ClientProxyBase.cs @@ -1,43 +1,28 @@ -using Newtonsoft.Json; using SimpleResults; using System; using System.Linq; namespace ClientProxyBase; -using Shared.Results; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Threading; using System.Threading.Tasks; public abstract partial class ClientProxyBase { - #region Fields - /// - /// The HTTP client - /// protected readonly HttpClient HttpClient; + private readonly Func Serialise; + private readonly Func Deserialise; - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The HTTP client. - protected ClientProxyBase(HttpClient httpClient) { + protected ClientProxyBase(HttpClient httpClient, Func Serialise, Func Deserialise) { this.HttpClient = httpClient; + this.Serialise = Serialise; + this.Deserialise = Deserialise; } - #endregion - #region Methods protected virtual async Task HandleResponse(HttpResponseMessage responseMessage, @@ -103,9 +88,13 @@ protected virtual ResponseData HandleResponseContent(String content) return new ResponseData { Data = data }; } - return JsonConvert.DeserializeObject>(content); + + return this.DeserialiseContent>(content); } + protected virtual T DeserialiseContent(String content) => + (T)this.Deserialise(content, typeof(T)); + static void AddAdditionalHeaders(HttpRequestMessage requestMessage, List<(String header, String value)> additionalHeaders) { if (additionalHeaders != null && additionalHeaders.Any()) { @@ -127,4 +116,4 @@ public ClientHttpException(string? message, public static class AuthenticationSchemes { public static readonly String Bearer = "Bearer"; -} \ No newline at end of file +} diff --git a/ClientProxyBase/ClientProxyBase_DELETE.cs b/ClientProxyBase/ClientProxyBase_DELETE.cs index f6fa74bc..1ec3bb47 100644 --- a/ClientProxyBase/ClientProxyBase_DELETE.cs +++ b/ClientProxyBase/ClientProxyBase_DELETE.cs @@ -6,7 +6,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; using Shared.Results; using SimpleResults; diff --git a/ClientProxyBase/ClientProxyBase_GET.cs b/ClientProxyBase/ClientProxyBase_GET.cs index 8152b8dd..8a00c003 100644 --- a/ClientProxyBase/ClientProxyBase_GET.cs +++ b/ClientProxyBase/ClientProxyBase_GET.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using Shared.Results; +using Shared.Results; using SimpleResults; using System; using System.Collections.Generic; @@ -34,7 +33,7 @@ protected virtual async Task> SendHttpGetRequest(St if (result.IsFailed) return ResultHelpers.CreateFailure(result); - TResponse responseData = JsonConvert.DeserializeObject(result.Data); + TResponse responseData = this.DeserialiseContent(result.Data); return Result.Success(responseData); } diff --git a/ClientProxyBase/ClientProxyBase_PATCH.cs b/ClientProxyBase/ClientProxyBase_PATCH.cs index 7c935f9c..3ac8f509 100644 --- a/ClientProxyBase/ClientProxyBase_PATCH.cs +++ b/ClientProxyBase/ClientProxyBase_PATCH.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using Shared.Results; +using Shared.Results; using SimpleResults; using System; using System.Collections.Generic; @@ -7,6 +6,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -33,7 +33,7 @@ protected virtual async Task SendHttpPatchRequest(String uri, } else { - requestMessage.Content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); + requestMessage.Content = new StringContent(this.Serialise(request), Encoding.UTF8, "application/json"); } // Make the Http Call here diff --git a/ClientProxyBase/ClientProxyBase_POST.cs b/ClientProxyBase/ClientProxyBase_POST.cs index e2d1397c..b77cbc6f 100644 --- a/ClientProxyBase/ClientProxyBase_POST.cs +++ b/ClientProxyBase/ClientProxyBase_POST.cs @@ -1,9 +1,7 @@ -using Newtonsoft.Json; -using Shared.Results; +using Shared.Results; using SimpleResults; using System; using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; @@ -34,7 +32,7 @@ protected virtual async Task> SendHttpPostRequest(S if (result.IsFailed) return ResultHelpers.CreateFailure(result); - TResponse responseData = JsonConvert.DeserializeObject(result.Data); + TResponse responseData = this.DeserialiseContent(result.Data); return Result.Success(responseData); @@ -68,7 +66,7 @@ protected virtual async Task> SendHttpPostRequest> SendHttpPostRequest(result.Data); + TResponse responseData = this.DeserialiseContent(result.Data); return Result.Success(responseData); } diff --git a/ClientProxyBase/ClientProxyBase_PUT.cs b/ClientProxyBase/ClientProxyBase_PUT.cs index 3f2d3655..6765d40c 100644 --- a/ClientProxyBase/ClientProxyBase_PUT.cs +++ b/ClientProxyBase/ClientProxyBase_PUT.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json; using Shared.Results; using SimpleResults; using System; @@ -33,7 +33,7 @@ protected virtual async Task SendHttpPutRequest(String uri, } else { - requestMessage.Content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); + requestMessage.Content = new StringContent(this.Serialise(request), Encoding.UTF8, "application/json"); } // Make the Http Call here diff --git a/Driver/TestAggregate.cs b/Driver/TestAggregate.cs index 6307acfc..f958417a 100644 --- a/Driver/TestAggregate.cs +++ b/Driver/TestAggregate.cs @@ -4,7 +4,6 @@ namespace Driver; -using Newtonsoft.Json; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.Aggregate; using Shared.EventStore.EventStore; @@ -54,7 +53,6 @@ public void SetAggregateName(String aggregateName) public record AggregateNameSetEvent : DomainEvent { - [JsonProperty] public String AggregateName { get; private set; } private AggregateNameSetEvent(Guid aggregateId, diff --git a/Shared.DomainDrivenDesign/EventSourcing/DomainEventRecord.cs b/Shared.DomainDrivenDesign/EventSourcing/DomainEventRecord.cs index f959101b..c4a86643 100644 --- a/Shared.DomainDrivenDesign/EventSourcing/DomainEventRecord.cs +++ b/Shared.DomainDrivenDesign/EventSourcing/DomainEventRecord.cs @@ -1,19 +1,10 @@ namespace Shared.DomainDrivenDesign.EventSourcing; using System; -using Newtonsoft.Json; -#region Others public record DomainEvent : IDomainEvent { - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The aggregate identifier. - /// The event identifier. public DomainEvent(Guid aggregateId, Guid eventId) { this.AggregateId = aggregateId; @@ -21,59 +12,19 @@ public DomainEvent(Guid aggregateId, this.EventType = DomainHelper.GetEventTypeName(this.GetType()); } - #endregion - #region Properties - /// - /// Gets the aggregate identifier. - /// - /// - /// The aggregate identifier. - /// - [JsonIgnore] public Guid AggregateId { get; init; } - /// - /// Gets the aggregate version. - /// - /// - /// The aggregate version. - /// - [JsonIgnore] public Int64 AggregateVersion { get; init; } - /// - /// Gets the event identifier. - /// - /// - /// The event identifier. - /// - [JsonIgnore] public Guid EventId { get; init; } - /// - /// Gets the event number. - /// - /// - /// The event number. - /// - [JsonIgnore] public Int64 EventNumber { get; init; } - /// - /// Gets the event timestamp. - /// - /// - /// The event timestamp. - /// - [JsonIgnore] public DateTimeOffset EventTimestamp { get; init; } - [JsonIgnore] public String EventType { get; init; } #endregion -} - -#endregion \ No newline at end of file +} \ No newline at end of file diff --git a/Shared.DomainDrivenDesign/Shared.DomainDrivenDesign.csproj b/Shared.DomainDrivenDesign/Shared.DomainDrivenDesign.csproj index 3865e879..271d95a6 100644 --- a/Shared.DomainDrivenDesign/Shared.DomainDrivenDesign.csproj +++ b/Shared.DomainDrivenDesign/Shared.DomainDrivenDesign.csproj @@ -5,6 +5,5 @@ - diff --git a/Shared.EventStore.Tests/AggregateRepositoryTests.cs b/Shared.EventStore.Tests/AggregateRepositoryTests.cs index 3666b0c7..3b120cb8 100644 --- a/Shared.EventStore.Tests/AggregateRepositoryTests.cs +++ b/Shared.EventStore.Tests/AggregateRepositoryTests.cs @@ -1,5 +1,7 @@ -using KurrentDB.Client; +using System.Text.Json; +using KurrentDB.Client; using Shared.EventStore.Tests.TestObjects; +using Shared.Serialisation; using SimpleResults; namespace Shared.EventStore.Tests; @@ -17,6 +19,11 @@ namespace Shared.EventStore.Tests; using Xunit; public class AggregateRepositoryTests{ + + public AggregateRepositoryTests() { + StringSerialiser.Initialise(new SystemTextJsonSerializer(new JsonSerializerOptions())); + } + [Fact] public async Task AggregateRepository_GetLatestVersion_AggregateReturned(){ Mock context = new(); diff --git a/Shared.EventStore.Tests/DomainEventFactoryTests.cs b/Shared.EventStore.Tests/DomainEventFactoryTests.cs index 44c2ac3b..d967a597 100644 --- a/Shared.EventStore.Tests/DomainEventFactoryTests.cs +++ b/Shared.EventStore.Tests/DomainEventFactoryTests.cs @@ -1,26 +1,28 @@ -using System; +using KurrentDB.Client; +using Shared.EventStore.Tests.TestObjects; +using Shared.Serialisation; +using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using KurrentDB.Client; -using Shared.EventStore.Tests.TestObjects; namespace Shared.EventStore.Tests; using Aggregate; using DomainDrivenDesign.EventSourcing; -using global::EventStore.Client; -using Newtonsoft.Json; using Shouldly; +using System.Text.Json; using Xunit; public class DomainEventFactoryTests { + public DomainEventFactoryTests() { + StringSerialiser.Initialise(new SystemTextJsonSerializer(new JsonSerializerOptions())); + } + [Fact] public void DomainEventFactory_CreateDomainEvent_StringAndType_DomainEventCreated(){ AggregateNameSetEvent aggregateNameSetEvent = new(TestData.AggregateId, TestData.EventId, "Test"); - String eventData = JsonConvert.SerializeObject(aggregateNameSetEvent); + String eventData = StringSerialiser.Serialise(aggregateNameSetEvent, new SerialiserOptions(SerialiserPropertyFormat.CamelCase, IgnoreNullValues: true, WriteIndented: true)); DomainEventFactory factory = new(); DomainEvent newEvent = factory.CreateDomainEvent(eventData, typeof(AggregateNameSetEvent)); ((AggregateNameSetEvent)newEvent).AggregateName.ShouldBe(aggregateNameSetEvent.AggregateName); @@ -30,7 +32,7 @@ public void DomainEventFactory_CreateDomainEvent_StringAndType_DomainEventCreate public void DomainEventFactory_CreateDomainEvent_StringAndType_InvalidJson_ExceptionThrown() { AggregateNameSetEvent aggregateNameSetEvent = new(TestData.AggregateId, TestData.EventId, "Test"); - String eventData = JsonConvert.SerializeObject(aggregateNameSetEvent); + String eventData = StringSerialiser.Serialise(aggregateNameSetEvent); eventData = eventData.Replace(":", ""); DomainEventFactory factory = new(); Should.Throw(() => factory.CreateDomainEvent(eventData, typeof(AggregateNameSetEvent))); @@ -69,37 +71,4 @@ public void DomainEventFactory_CreateDomainEvents_GuidAndResolvedEventList_Domai DomainEvent[] newEvent = factory.CreateDomainEvents(TestData.AggregateId, resolvedEventList); ((AggregateNameSetEvent)newEvent.Single()).AggregateName.ShouldBe(aggregateNameSetEvent.AggregateName); } -} - -public class EventDataFactoryTests{ - [Fact] - public void EventDataFactory_CreateEventData_EventDataCreated(){ - EventDataFactory factory = new(); - AggregateNameSetEvent aggregateNameSetEvent = new(TestData.AggregateId, TestData.EventId, "Test"); - EventData eventData = factory.CreateEventData(aggregateNameSetEvent); - eventData.EventId.ToGuid().ShouldBe(aggregateNameSetEvent.EventId); - } - - [Fact] - public void EventDataFactory_CreateEventData_NullEvent_ErrorThrown() - { - EventDataFactory factory = new(); - - Should.Throw(() => factory.CreateEventData(null)); - } - - [Fact] - public void EventDataFactory_CreateEventDataList_EventDataCreated() - { - EventDataFactory factory = new(); - List events = new(); - AggregateNameSetEvent aggregateNameSetEvent1 = new(TestData.AggregateId, Guid.NewGuid(), "Test"); - AggregateNameSetEvent aggregateNameSetEvent2 = new(TestData.AggregateId, Guid.NewGuid(), "Test"); - events.Add(aggregateNameSetEvent1); - events.Add(aggregateNameSetEvent2); - - EventData[] eventData = factory.CreateEventDataList(events); - eventData[0].EventId.ToGuid().ShouldBe(aggregateNameSetEvent1.EventId); - eventData[1].EventId.ToGuid().ShouldBe(aggregateNameSetEvent2.EventId); - } } \ No newline at end of file diff --git a/Shared.EventStore.Tests/EventDataFactoryTests.cs b/Shared.EventStore.Tests/EventDataFactoryTests.cs new file mode 100644 index 00000000..3b1c8b69 --- /dev/null +++ b/Shared.EventStore.Tests/EventDataFactoryTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using KurrentDB.Client; +using Shared.DomainDrivenDesign.EventSourcing; +using Shared.EventStore.Aggregate; +using Shared.EventStore.Tests.TestObjects; +using Shared.Serialisation; +using Shouldly; +using Xunit; + +namespace Shared.EventStore.Tests; + +public class EventDataFactoryTests{ + public EventDataFactoryTests() { + StringSerialiser.Initialise(new SystemTextJsonSerializer(new JsonSerializerOptions())); + } + + [Fact] + public void EventDataFactory_CreateEventData_EventDataCreated(){ + EventDataFactory factory = new(); + AggregateNameSetEvent aggregateNameSetEvent = new(TestData.AggregateId, TestData.EventId, "Test"); + EventData eventData = factory.CreateEventData(aggregateNameSetEvent); + eventData.EventId.ToGuid().ShouldBe(aggregateNameSetEvent.EventId); + } + + [Fact] + public void EventDataFactory_CreateEventData_NullEvent_ErrorThrown() + { + EventDataFactory factory = new(); + + Should.Throw(() => factory.CreateEventData(null)); + } + + [Fact] + public void EventDataFactory_CreateEventDataList_EventDataCreated() + { + EventDataFactory factory = new(); + List events = new(); + AggregateNameSetEvent aggregateNameSetEvent1 = new(TestData.AggregateId, Guid.NewGuid(), "Test"); + AggregateNameSetEvent aggregateNameSetEvent2 = new(TestData.AggregateId, Guid.NewGuid(), "Test"); + events.Add(aggregateNameSetEvent1); + events.Add(aggregateNameSetEvent2); + + EventData[] eventData = factory.CreateEventDataList(events); + eventData[0].EventId.ToGuid().ShouldBe(aggregateNameSetEvent1.EventId); + eventData[1].EventId.ToGuid().ShouldBe(aggregateNameSetEvent2.EventId); + } + + [Fact] + public void EventDataFactory_CreateEventData_InterfaceReference_IncludesDerivedProperties() + { + EventDataFactory factory = new(); + IDomainEvent domainEvent = new AggregateNameSetEvent(TestData.AggregateId, Guid.NewGuid(), "Test"); + + EventData eventData = factory.CreateEventData(domainEvent); + string payload = Encoding.Default.GetString(eventData.Data.ToArray()); + + payload.ShouldContain("\"aggregateName\": \"Test\""); + } +} diff --git a/Shared.EventStore.Tests/EventStoreConnectionStringHealthCheckTests.cs b/Shared.EventStore.Tests/EventStoreConnectionStringHealthCheckTests.cs new file mode 100644 index 00000000..e69405ce --- /dev/null +++ b/Shared.EventStore.Tests/EventStoreConnectionStringHealthCheckTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using KurrentDB.Client; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Moq; +using Shared.EventStore.EventStore; +using Shared.EventStore.Tests.TestObjects; +using Shared.Serialisation; +using Shouldly; +using SimpleResults; +using Xunit; + +namespace Shared.EventStore.Tests; + +public class EventStoreConnectionStringHealthCheckTests { + + public EventStoreConnectionStringHealthCheckTests() { + StringSerialiser.Initialise(new SystemTextJsonSerializer(new JsonSerializerOptions())); + } + + [Fact] + public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_EventsReturned_Healthy() { + Mock context = new(); + AggregateNameSetEvent aggregateNameSetEvent = new(TestData.AggregateId, TestData.EventId, "Test"); + + EventRecord r = TestData.CreateEventRecord(aggregateNameSetEvent, "TestAggregate"); + + var resolvedEvent = new ResolvedEvent(r, null,null); + + context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List() { resolvedEvent })); + EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); + result.Status.ShouldBe(HealthStatus.Healthy); + } + + [Fact] + public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_NoEventsReturned_Unhealthy() + { + Mock context = new(); + + context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); + EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); + result.Status.ShouldBe(HealthStatus.Unhealthy); + } + + [Fact] + public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_ReadLastEventsFromAll_Unhealthy() + { + Mock context = new(); + + context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Failure()); + EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); + result.Status.ShouldBe(HealthStatus.Unhealthy); + } + + [Fact] + public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_ExceptionThrown_Unhealthy() + { + Mock context = new(); + + context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ThrowsAsync(new Exception()); + EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); + + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); + result.Status.ShouldBe(HealthStatus.Unhealthy); + } +} \ No newline at end of file diff --git a/Shared.EventStore.Tests/EventStoreHealthCheckBuilderExtensionsTests.cs b/Shared.EventStore.Tests/EventStoreHealthCheckBuilderExtensionsTests.cs index 616ae8a7..65045efb 100644 --- a/Shared.EventStore.Tests/EventStoreHealthCheckBuilderExtensionsTests.cs +++ b/Shared.EventStore.Tests/EventStoreHealthCheckBuilderExtensionsTests.cs @@ -1,12 +1,4 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using KurrentDB.Client; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Moq; -using Shared.EventStore.EventStore; -using Shouldly; -using SimpleResults; +using KurrentDB.Client; namespace Shared.EventStore.Tests; @@ -14,7 +6,6 @@ namespace Shared.EventStore.Tests; using global::EventStore.Client; using Microsoft.Extensions.DependencyInjection; using Pose; -using System; using TestObjects; using Xunit; @@ -25,55 +16,4 @@ public void AddEventStore_HealthCheckAdded(){ IHealthChecksBuilder builder = new TestHealthChecksBuilder(); builder.AddEventStore(new KurrentDBClientSettings(), null, null, null); } -} - -public class EventStoreConnectionStringHealthCheckTests { - [Fact] - public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_EventsReturned_Healthy() { - Mock context = new(); - AggregateNameSetEvent aggregateNameSetEvent = new(TestData.AggregateId, TestData.EventId, "Test"); - - EventRecord r = TestData.CreateEventRecord(aggregateNameSetEvent, "TestAggregate"); - - var resolvedEvent = new ResolvedEvent(r, null,null); - - context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List() { resolvedEvent })); - EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - result.Status.ShouldBe(HealthStatus.Healthy); - } - - [Fact] - public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_NoEventsReturned_Unhealthy() - { - Mock context = new(); - - context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); - EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - result.Status.ShouldBe(HealthStatus.Unhealthy); - } - - [Fact] - public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_ReadLastEventsFromAll_Unhealthy() - { - Mock context = new(); - - context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Failure()); - EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - result.Status.ShouldBe(HealthStatus.Unhealthy); - } - - [Fact] - public async Task EventStoreConnectionStringHealthCheck_CheckHealthAsync_ExceptionThrown_Unhealthy() - { - Mock context = new(); - - context.Setup(c => c.ReadLastEventsFromAll(It.IsAny(), It.IsAny())).ThrowsAsync(new Exception()); - EventStoreConnectionStringHealthCheck healthCheck = new(context.Object); - - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - result.Status.ShouldBe(HealthStatus.Unhealthy); - } } \ No newline at end of file diff --git a/Shared.EventStore.Tests/PersistentSubscriptionTests.cs b/Shared.EventStore.Tests/PersistentSubscriptionTests.cs index 23866d63..897ded74 100644 --- a/Shared.EventStore.Tests/PersistentSubscriptionTests.cs +++ b/Shared.EventStore.Tests/PersistentSubscriptionTests.cs @@ -1,5 +1,7 @@ +using System.Text.Json; using Microsoft.Extensions.Configuration; using Shared.EventStore.Tests.TestObjects; +using Shared.Serialisation; namespace Shared.EventStore.Tests; @@ -24,6 +26,7 @@ public PersistentSubscriptionTests() ConfigurationReader.Initialise(new ConfigurationRoot(new List())); TypeMap.AddType("EstateCreatedEvent"); + StringSerialiser.Initialise(new SystemTextJsonSerializer(new JsonSerializerOptions())); } [Fact] diff --git a/Shared.EventStore.Tests/TestObjects/TestData.cs b/Shared.EventStore.Tests/TestObjects/TestData.cs index 41517c20..b7556e6b 100644 --- a/Shared.EventStore.Tests/TestObjects/TestData.cs +++ b/Shared.EventStore.Tests/TestObjects/TestData.cs @@ -1,17 +1,13 @@ using KurrentDB.Client; using Shared.General; +using Shared.Serialisation; namespace Shared.EventStore.Tests.TestObjects; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using global::EventStore.Client; -using Newtonsoft.Json; using System.Text; using DomainDrivenDesign.EventSourcing; -using NLog.LayoutRenderers.Wrappers; -using SubscriptionWorker; using PersistentSubscriptionInfo = SubscriptionWorker.PersistentSubscriptionInfo; public class TestData @@ -25,7 +21,7 @@ public class TestData public static EventRecord CreateEventRecord(T domainEvent, string streamId, bool addToMap) where T : DomainEvent { - byte[] eventData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(domainEvent)); + byte[] eventData = Encoding.UTF8.GetBytes(StringSerialiser.Serialise(domainEvent, new SerialiserOptions(SerialiserPropertyFormat.CamelCase, IgnoreNullValues: true, WriteIndented: true))); byte[] customEventMetaData = Encoding.UTF8.GetBytes(string.Empty); Dictionary metaData = new(); diff --git a/Shared.EventStore.Tests/TypeMapConvertorTests.cs b/Shared.EventStore.Tests/TypeMapConvertorTests.cs index cdd6b9d1..d649db47 100644 --- a/Shared.EventStore.Tests/TypeMapConvertorTests.cs +++ b/Shared.EventStore.Tests/TypeMapConvertorTests.cs @@ -1,24 +1,30 @@ -using System; +using KurrentDB.Client; +using Shared.EventStore.Aggregate; +using Shared.EventStore.Tests.TestObjects; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using KurrentDB.Client; -using Shared.EventStore.Aggregate; -using Shared.EventStore.Tests.TestObjects; namespace Shared.EventStore.Tests; -using System.Reflection; -using System.Xml.Serialization; using DomainDrivenDesign.EventSourcing; using General; using global::EventStore.Client; +using Shared.Serialisation; using Shouldly; +using System.Reflection; +using System.Text.Json; +using System.Xml.Serialization; using Xunit; public class TypeMapConvertorTests { + public TypeMapConvertorTests() { + StringSerialiser.Initialise(new SystemTextJsonSerializer(new JsonSerializerOptions())); + } + [Fact] public void TypeMapConvertor_Convertor_IDomainEvent_EventDataReturned(){ AggregateNameSetEvent domainEvent = new(TestData.AggregateId, TestData.EventId, TestData.EstateName); diff --git a/Shared.EventStore/Aggregate/AggregateVersion.cs b/Shared.EventStore/Aggregate/AggregateVersion.cs index 043c5485..3f4600eb 100644 --- a/Shared.EventStore/Aggregate/AggregateVersion.cs +++ b/Shared.EventStore/Aggregate/AggregateVersion.cs @@ -2,29 +2,11 @@ using System; using System.Globalization; -using Newtonsoft.Json; -[JsonObject] public struct AggregateVersion : IComparable { - #region Constructors - [Newtonsoft.Json.JsonConstructor] - private AggregateVersion(Int64 value) : this() - { - this.Value = value; - } - - #endregion - - #region Properties - - [JsonProperty(Order = 1)] - public Int64 Value { get; private set; } - - #endregion - - #region Methods + public Int64 Value { get; init; } public Int32 CompareTo(Object obj) { @@ -34,12 +16,12 @@ public Int32 CompareTo(Object obj) public static AggregateVersion CreateFrom(Int64 value) { - return new AggregateVersion(value); + return new AggregateVersion { Value = value }; } public static AggregateVersion CreateNew() { - return new AggregateVersion(0); + return new AggregateVersion { Value = 0 }; } public Boolean Equals(AggregateVersion other) @@ -105,6 +87,4 @@ public static implicit operator AggregateVersion(Int64 value) public static bool operator >=(AggregateVersion left, AggregateVersion right) => left.Value >= right.Value; - - #endregion } \ No newline at end of file diff --git a/Shared.EventStore/Aggregate/DomainEventFactory.cs b/Shared.EventStore/Aggregate/DomainEventFactory.cs index 90a2a60f..e993ec3a 100644 --- a/Shared.EventStore/Aggregate/DomainEventFactory.cs +++ b/Shared.EventStore/Aggregate/DomainEventFactory.cs @@ -1,4 +1,5 @@ using KurrentDB.Client; +using Shared.Serialisation; namespace Shared.EventStore.Aggregate; @@ -7,42 +8,17 @@ namespace Shared.EventStore.Aggregate; using System.Linq; using DomainDrivenDesign.EventSourcing; using General; -using global::EventStore.Client; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Serialisation; -public class DomainEventFactory : IDomainEventFactory -{ - #region Constructors +public class DomainEventFactory : IDomainEventFactory { - /// - /// Initializes a new instance of the class. - /// - public DomainEventFactory() - { - JsonIgnoreAttributeIgnorerContractResolver jsonIgnoreAttributeIgnorerContractResolver = new(); + private SerialiserOptions SerialiserOptions = new SerialiserOptions(SerialiserPropertyFormat.CamelCase, IgnoreNullValues: true, WriteIndented: true); - JsonConvert.DefaultSettings = () => new JsonSerializerSettings - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, - TypeNameHandling = TypeNameHandling.All, - Formatting = Formatting.Indented, - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - ContractResolver = jsonIgnoreAttributeIgnorerContractResolver - }; - } - - #endregion - - #region Methods - - public DomainEvent CreateDomainEvent(Guid aggregateId, ResolvedEvent @event) - { + public DomainEvent CreateDomainEvent(Guid aggregateId, + ResolvedEvent @event) { String json = @event.GetResolvedEventDataAsString(); Type eventType = null; - try{ + try { eventType = TypeMap.GetType(@event.Event.EventType); } catch (Exception) { @@ -52,10 +28,9 @@ public DomainEvent CreateDomainEvent(Guid aggregateId, ResolvedEvent @event) if (eventType == null) throw new ArgumentException($"Failed to find a domain event with type {@event.Event.EventType}"); - DomainEvent domainEvent = (DomainEvent)JsonConvert.DeserializeObject(json, eventType); + DomainEvent domainEvent = StringSerialiser.DeserializeObject(json, eventType, SerialiserOptions); - domainEvent = domainEvent with - { + domainEvent = domainEvent with { AggregateId = aggregateId, AggregateVersion = @event.Event.EventNumber.ToInt64(), EventNumber = @event.Event.EventNumber.ToInt64(), @@ -66,17 +41,15 @@ public DomainEvent CreateDomainEvent(Guid aggregateId, ResolvedEvent @event) return domainEvent; } - - public DomainEvent CreateDomainEvent(String json, Type eventType) - { + + public DomainEvent CreateDomainEvent(String json, + Type eventType) { DomainEvent domainEvent; - try - { - domainEvent = (DomainEvent)JsonConvert.DeserializeObject(json, eventType); + try { + domainEvent = StringSerialiser.DeserializeObject(json, eventType, SerialiserOptions); } - catch(Exception e) - { + catch (Exception e) { ApplicationException ex = new($"Failed to convert json event {json} into a domain event. EventType was {eventType.Name}", e); throw ex; } @@ -84,10 +57,8 @@ public DomainEvent CreateDomainEvent(String json, Type eventType) return domainEvent; } - public DomainEvent[] CreateDomainEvents(Guid aggregateId, IList @event) - { + public DomainEvent[] CreateDomainEvents(Guid aggregateId, + IList @event) { return @event.Select(e => this.CreateDomainEvent(aggregateId, e)).ToArray(); } - - #endregion } \ No newline at end of file diff --git a/Shared.EventStore/Aggregate/EventDataFactory.cs b/Shared.EventStore/Aggregate/EventDataFactory.cs index d27f3b85..fbcdd9d0 100644 --- a/Shared.EventStore/Aggregate/EventDataFactory.cs +++ b/Shared.EventStore/Aggregate/EventDataFactory.cs @@ -1,4 +1,5 @@ using KurrentDB.Client; +using Shared.Serialisation; namespace Shared.EventStore.Aggregate; @@ -7,80 +8,27 @@ namespace Shared.EventStore.Aggregate; using System.Linq; using System.Text; using DomainDrivenDesign.EventSourcing; -using global::EventStore.Client; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -/// -/// -/// -/// public class EventDataFactory : IEventDataFactory { - #region Fields + private SerialiserOptions SerialiserOptions = new SerialiserOptions(SerialiserPropertyFormat.CamelCase, IgnoreNullValues: true, WriteIndented: true); - /// - /// The json options function - /// - private static readonly Func jsonOptionsFunc = () => new JsonSerializerSettings - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, - TypeNameHandling = TypeNameHandling.None, - Formatting = Formatting.None, - ContractResolver = new CamelCasePropertyNamesContractResolver(), - DefaultValueHandling = DefaultValueHandling.Ignore - }; - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - public EventDataFactory() - { - JsonConvert.DefaultSettings = EventDataFactory.jsonOptionsFunc; - } - - #endregion - - #region Methods - - /// - /// Creates the event data. - /// - /// The domain event. - /// - /// EventData. - /// - /// public EventData CreateEventData(IDomainEvent domainEvent) { - this.GuardAgainstNoDomainEvent(domainEvent); - - Byte[] data = Encoding.Default.GetBytes(JsonConvert.SerializeObject(domainEvent)); + this.GuardAgainstNoDomainEvent(domainEvent); + var x = StringSerialiser.Serialise(domainEvent, SerialiserOptions); + Byte[] data = Encoding.Default.GetBytes(StringSerialiser.Serialise(domainEvent, SerialiserOptions)); EventData eventData = new(Uuid.FromGuid(domainEvent.EventId), domainEvent.EventType, data); return eventData; } - /// - /// Creates the event data. - /// - /// The domain events. - /// public EventData[] CreateEventDataList(IList domainEvents) { return domainEvents.Select(this.CreateEventData).ToArray(); } - /// - /// Guards the against no domain event. - /// - /// The event. - /// @event;No domain event provided private void GuardAgainstNoDomainEvent(IDomainEvent @event) { if (@event == null) @@ -89,5 +37,4 @@ private void GuardAgainstNoDomainEvent(IDomainEvent @event) } } - #endregion } \ No newline at end of file diff --git a/Shared.EventStore/Shared.EventStore.csproj b/Shared.EventStore/Shared.EventStore.csproj index 0f99af42..64410b46 100644 --- a/Shared.EventStore/Shared.EventStore.csproj +++ b/Shared.EventStore/Shared.EventStore.csproj @@ -11,7 +11,6 @@ - diff --git a/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs b/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs index 83f8d3bb..18b17d24 100644 --- a/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs +++ b/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs @@ -1,4 +1,5 @@ using KurrentDB.Client; +using Shared.Serialisation; using SimpleResults; namespace Shared.EventStore.SubscriptionWorker; @@ -6,8 +7,6 @@ namespace Shared.EventStore.SubscriptionWorker; using Aggregate; using DomainDrivenDesign.EventSourcing; using EventHandling; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Shared.General; using Shared.Logger.TennantContext; using System; @@ -103,20 +102,11 @@ public static PersistentSubscription Create(IPersistentSubscriptionsClient persi private static TenantIdentifiers GetTenantIdentifiersFromDomainEvent(IDomainEvent domainEvent) { - String domainEventAsString = JsonConvert.SerializeObject(domainEvent); + String domainEventAsString = StringSerialiser.Serialise(domainEvent); try { - JToken rootToken = JToken.Parse(domainEventAsString); - - JToken estateIdIdToken = rootToken.SelectTokens("..estateId").FirstOrDefault() ?? - rootToken.SelectTokens("..EstateId").FirstOrDefault(); - - JToken merchantIdToken = rootToken.SelectTokens("..merchantId").FirstOrDefault() ?? - rootToken.SelectTokens("..MerchantId").FirstOrDefault(); - - Guid.TryParse(estateIdIdToken?.Value(), out Guid estateId); - Guid.TryParse(merchantIdToken?.Value(), out Guid merchantId); + Guid estateId = StringSerialiser.GetValue(domainEventAsString, "estateId"); return estateId == Guid.Empty ? TenantIdentifiers.Default() : new TenantIdentifiers(estateId, Guid.Empty); } diff --git a/Shared.EventStore/SubscriptionWorker/PersistentSubscriptionInfo.cs b/Shared.EventStore/SubscriptionWorker/PersistentSubscriptionInfo.cs index 4bc9f00b..0397e5d6 100644 --- a/Shared.EventStore/SubscriptionWorker/PersistentSubscriptionInfo.cs +++ b/Shared.EventStore/SubscriptionWorker/PersistentSubscriptionInfo.cs @@ -2,7 +2,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using Newtonsoft.Json; [ExcludeFromCodeCoverage] public class PersistentSubscriptionInfo @@ -11,7 +10,6 @@ public class PersistentSubscriptionInfo public String GroupName { get; set; } - [JsonProperty("eventStreamId")] public String StreamName { get; set; } #endregion diff --git a/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs b/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs index 4c082a74..9411c702 100644 --- a/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs +++ b/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs @@ -1,4 +1,5 @@ using KurrentDB.Client; +using Shared.Serialisation; using SimpleResults; namespace Shared.EventStore.SubscriptionWorker; @@ -9,8 +10,7 @@ namespace Shared.EventStore.SubscriptionWorker; using System.Threading; using System.Threading.Tasks; using global::EventStore.Client; - using Newtonsoft.Json; - + [ExcludeFromCodeCoverage] public class SubscriptionRepository : ISubscriptionRepository { private Int32 CacheHits; @@ -56,7 +56,7 @@ public static async Task>> GetSubscripti String responseBody = await responseMessage.Content.ReadAsStringAsync(cancellationToken); if (responseMessage.IsSuccessStatusCode) { - List list = JsonConvert.DeserializeObject>(responseBody); + List list = StringSerialiser.Deserialise>(responseBody); return Result.Success(list); } diff --git a/Shared.EventStoreContext.Tests/EventStoreContextTests.cs b/Shared.EventStoreContext.Tests/EventStoreContextTests.cs index 8c0aa654..15efa8de 100644 --- a/Shared.EventStoreContext.Tests/EventStoreContextTests.cs +++ b/Shared.EventStoreContext.Tests/EventStoreContextTests.cs @@ -1,12 +1,13 @@ -using KurrentDB.Client; -using Newtonsoft.Json; +using System.Text.Json; +using KurrentDB.Client; using NLog; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.Aggregate; using Shared.EventStore.EventStore; -using Shared.EventStore.Tests; +using Shared.General; using Shared.Logger; using Shared.Middleware; +using Shared.Serialisation; using Shouldly; using SimpleResults; @@ -30,6 +31,7 @@ public EventStoreContextTests() logger.Initialise(LogManager.GetLogger("Reqnroll"), "Reqnroll"); this.EventStoreDockerHelper = new() { Logger = logger }; + StringSerialiser.Initialise(new SystemTextJsonSerializer(new JsonSerializerOptions())); } #endregion @@ -123,17 +125,19 @@ public async Task EventStoreContext_InsertEvents_EventsAreWritten(Boolean secure [TestCase(true)] [TestCase(false)] public async Task EventStoreContext_ReadEvents_EventsAreRead(Boolean secureEventStore){ + + TypeMap.AddType("EstateCreatedEvent"); TimeSpan deadline = TimeSpan.FromMinutes(2); await this.EventStoreDockerHelper.StartContainers(secureEventStore, $"EventStoreContext_ReadEvents_EventsAreRead{secureEventStore}"); IEventStoreContext context = this.CreateContext(secureEventStore, deadline); - Guid aggreggateId = Guid.NewGuid(); - String streamName = $"TestStream-{aggreggateId:N}"; + Guid aggregateId = Guid.NewGuid(); + String streamName = $"TestStream-{aggregateId:N}"; - EstateCreatedEvent event1 = new(aggreggateId, "Test Estate 1"); - EstateCreatedEvent event2 = new(aggreggateId, "Test Estate 2"); + EstateCreatedEvent event1 = new(aggregateId, "Test Estate 1"); + EstateCreatedEvent event2 = new(aggregateId, "Test Estate 2"); List domainEvents = new(){ event1, event2 @@ -149,6 +153,17 @@ public async Task EventStoreContext_ReadEvents_EventsAreRead(Boolean secureEvent resolvedEventsResult.IsSuccess.ShouldBeTrue(); resolvedEventsResult.Data.Count.ShouldBe(events.Length); + + IDomainEventFactory domainEventFactory = new DomainEventFactory(); + + foreach (ResolvedEvent resolvedEvent in resolvedEventsResult.Data) { + EstateCreatedEvent domainEvent = + TypeMapConvertor.Convertor(domainEventFactory, aggregateId, resolvedEvent) as EstateCreatedEvent; + domainEvent.EstateId.ShouldBe(aggregateId); + domainEvent.EventType.ShouldBe("EstateCreatedEvent"); + domainEvent.EstateName.ShouldStartWith("Test Estate"); + } + } [Test] @@ -290,7 +305,7 @@ public async Task EventStoreContext_RunTransientQuery_QueryIsRun(Boolean secureE var definition = new{ estates = new List() }; - var result = JsonConvert.DeserializeAnonymousType(queryResult.Data, definition); + var result = StringSerialiser.DeserialiseAnonymousType(queryResult.Data, definition); result.estates.Contains(event1.EstateName).ShouldBeTrue(); result.estates.Contains(event2.EstateName).ShouldBeTrue(); diff --git a/Shared.EventStoreContext.Tests/EventStoreDockerHelper.cs b/Shared.EventStoreContext.Tests/EventStoreDockerHelper.cs index c1a59d42..dd739e6d 100644 --- a/Shared.EventStoreContext.Tests/EventStoreDockerHelper.cs +++ b/Shared.EventStoreContext.Tests/EventStoreDockerHelper.cs @@ -1,20 +1,10 @@ using Ductus.FluentDocker.Builders; -using KurrentDB.Client; - -namespace Shared.EventStore.Tests; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; using Ductus.FluentDocker.Services; -using global::EventStore.Client; -using IntegrationTesting; -using Newtonsoft.Json; +using KurrentDB.Client; +using Shared.IntegrationTesting; using Shared.IntegrationTesting.Ductus; -using Shouldly; + +namespace Shared.EventStoreContext.Tests; public class EventStoreDockerHelper : DockerHelper { diff --git a/Shared.EventStoreContext.Tests/Shared.EventStoreContext.Tests.csproj b/Shared.EventStoreContext.Tests/Shared.EventStoreContext.Tests.csproj index cb408093..b3e12c06 100644 --- a/Shared.EventStoreContext.Tests/Shared.EventStoreContext.Tests.csproj +++ b/Shared.EventStoreContext.Tests/Shared.EventStoreContext.Tests.csproj @@ -4,7 +4,7 @@ net10.0 enable enable - None + Full false true diff --git a/Shared.IntegrationTesting/Ductus/BaseDockerHelper.cs b/Shared.IntegrationTesting/Ductus/BaseDockerHelper.cs index e79bc57e..06fa1a9c 100644 --- a/Shared.IntegrationTesting/Ductus/BaseDockerHelper.cs +++ b/Shared.IntegrationTesting/Ductus/BaseDockerHelper.cs @@ -7,6 +7,7 @@ using Ductus.FluentDocker.Model.Containers; using Ductus.FluentDocker.Services; using Ductus.FluentDocker.Services.Extensions; +using Shared.Serialisation; using SimpleResults; namespace Shared.IntegrationTesting.Ductus; @@ -15,7 +16,6 @@ namespace Shared.IntegrationTesting.Ductus; using HealthChecks; using Logger; using Microsoft.Data.SqlClient; -using Newtonsoft.Json; using Shared.IntegrationTesting; using Shouldly; using System; @@ -783,7 +783,7 @@ await Retry.For(async () => { SimpleResults.Result healthCheckResult = await this.HealthCheckClient.PerformHealthCheck(containerDetails.Item1, "127.0.0.1", containerDetails.Item2, CancellationToken.None); if (healthCheckResult.IsSuccess) { - HealthChecks.HealthCheckResult result = JsonConvert.DeserializeObject(healthCheckResult.Data); + HealthChecks.HealthCheckResult result = StringSerialiser.Deserialise(healthCheckResult.Data); this.Trace($"health check complete for {containerType} result is [{healthCheckResult.Data}]"); diff --git a/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs b/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs index 59dbb947..faee249c 100644 --- a/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs +++ b/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs @@ -5,23 +5,19 @@ using DotNet.Testcontainers.Networks; using SimpleResults; using System.Net.Http.Headers; +using Shared.Serialisation; namespace Shared.IntegrationTesting.TestContainers; using Docker.DotNet; using EventStore.Client; -using global::Ductus.FluentDocker.Model.Networks; using HealthChecks; using Logger; using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Newtonsoft.Json; using Shouldly; using System; using System.Collections.Generic; using System.Data; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -29,7 +25,6 @@ namespace Shared.IntegrationTesting.TestContainers; using System.Net.Security; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -856,11 +851,11 @@ await Retry.For(async () => { SimpleResults.Result healthCheckResult = await this.HealthCheckClient.PerformHealthCheck(containerDetails.Item1, "127.0.0.1", containerDetails.Item2, CancellationToken.None); if (healthCheckResult.IsSuccess) { - HealthChecks.HealthCheckResult result = JsonConvert.DeserializeObject(healthCheckResult.Data); + HealthChecks.HealthCheckResult result = StringSerialiser.Deserialise(healthCheckResult.Data); this.Trace($"health check complete for {containerType} result is [{healthCheckResult.Data}]"); - result.Status.ShouldBe(HealthCheckStatus.Healthy.ToString(), $"Service Type: {containerType} Details {healthCheckResult.Data}"); + result.Status.ShouldBe(nameof(HealthCheckStatus.Healthy), $"Service Type: {containerType} Details {healthCheckResult.Data}"); this.Trace($"health check complete for {containerType}"); } else { diff --git a/Shared.IntegrationTesting/TestContainers/DockerHelper.cs b/Shared.IntegrationTesting/TestContainers/DockerHelper.cs index 4e53290d..22cab61d 100644 --- a/Shared.IntegrationTesting/TestContainers/DockerHelper.cs +++ b/Shared.IntegrationTesting/TestContainers/DockerHelper.cs @@ -1,21 +1,15 @@ -using System.Net.Http; -using System.Text; -using System.Threading; +using System.Threading; using DotNet.Testcontainers.Containers; using DotNet.Testcontainers.Networks; using SimpleResults; namespace Shared.IntegrationTesting.TestContainers; -using Newtonsoft.Json; -using Shouldly; using System; using System.Collections.Generic; -using System.Diagnostics.Metrics; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text.Json.Serialization; using System.Threading.Tasks; public abstract class DockerHelper : BaseDockerHelper diff --git a/Shared.Tests/ClientProxyBaseTests.cs b/Shared.Tests/ClientProxyBaseTests.cs index 696c31d6..428fe56c 100644 --- a/Shared.Tests/ClientProxyBaseTests.cs +++ b/Shared.Tests/ClientProxyBaseTests.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using ClientProxyBase; +using Shared.Serialisation; using SimpleResults; namespace Shared.Tests; @@ -16,6 +17,15 @@ namespace Shared.Tests; using Xunit; public partial class SharedTests { + + String Serialise(Object arg) { + return StringSerialiser.Serialise(arg); + } + + Object Deserialise(String arg, Type type) { + return StringSerialiser.DeserializeObject(arg, type); + } + [Theory] [InlineData(HttpStatusCode.OK)] [InlineData(HttpStatusCode.Created)] @@ -32,7 +42,8 @@ public async Task ClientProxyBase_HandleResponseX_SuccessStatus(HttpStatusCode s String responseContent = $"Content - {statusCode}"; HttpResponseMessage response = new(statusCode); response.Content = new StringContent(responseContent); - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); + Result result = await proxybase.Test_HandleResponseX(response, CancellationToken.None); result.IsSuccess.ShouldBeTrue(); result.Data.ShouldBe(responseContent); @@ -54,7 +65,7 @@ public async Task ClientProxyBase_HandleResponse_SuccessStatus(HttpStatusCode st String responseContent = $"Content - {statusCode}"; HttpResponseMessage response = new(statusCode); response.Content = new StringContent(responseContent); - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); Should.NotThrow(async () => await proxybase.Test_HandleResponse(response, CancellationToken.None)); } @@ -220,14 +231,14 @@ public async Task ClientProxyBase_HandleResponse_5xx_ErrorStatus(HttpStatusCode private async Task TestMethod_HandleResponseX(HttpStatusCode statusCode, ResultStatus resultStatus) { - var proxybase = new TestClient(new HttpClient()); + var proxybase = new TestClient(new HttpClient(), Serialise, Deserialise); var result = await proxybase.Test_HandleResponseX(new HttpResponseMessage(statusCode), CancellationToken.None); result.Status.ShouldBe(resultStatus); } private async Task TestMethod_HandleResponse(HttpStatusCode statusCode, Type expectedException) { - var proxybase = new TestClient(new HttpClient()); + var proxybase = new TestClient(new HttpClient(), Serialise, Deserialise); var exception = Should.Throw(async () => await proxybase.Test_HandleResponse(new HttpResponseMessage(statusCode), CancellationToken.None)); exception.ShouldBeOfType(expectedException); } @@ -236,7 +247,7 @@ private async Task TestMethod_HandleResponse(HttpStatusCode statusCode, public void HandleResponseContent_NullContent_ReturnsDefaultList() { // Arrange string content = null; - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); // Act var result = proxybase.Test_HandleResponseContent>(content); @@ -251,7 +262,7 @@ public void HandleResponseContent_NullContent_ReturnsDefaultList() { public void HandleResponseContent_NullContent_ReturnsDefaultObject() { // Arrange string content = null; - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); // Act var result = proxybase.Test_HandleResponseContent(content); @@ -265,7 +276,7 @@ public void HandleResponseContent_NullContent_ReturnsDefaultObject() { public void HandleResponseContent_EmptyContent_ReturnsDefaultList() { // Arrange string content = string.Empty; - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); // Act var result = proxybase.Test_HandleResponseContent>(content); @@ -280,7 +291,7 @@ public void HandleResponseContent_EmptyContent_ReturnsDefaultList() { public void HandleResponseContent_EmptyContent_ReturnsDefaultObject() { // Arrange string content = string.Empty; - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); // Act var result = proxybase.Test_HandleResponseContent(content); @@ -294,7 +305,7 @@ public void HandleResponseContent_EmptyContent_ReturnsDefaultObject() { public void HandleResponseContent_ValidJsonContent_ReturnsDeserializedObject() { // Arrange string json = "{ \"Data\": { \"name\": \"test\", \"description\": \"test description\" } }"; - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); // Act var result = proxybase.Test_HandleResponseContent(json); @@ -310,7 +321,7 @@ public void HandleResponseContent_ValidJsonContent_ReturnsDeserializedObject() { public void HandleResponseContent_ValidJsonContent_ReturnsDeserializedList() { // Arrange string json = "{ \"Data\": [{ \"name\": \"test1\", \"description\": \"test description 1\" }, { \"name\": \"test2\", \"description\": \"test description 2\" }] }"; - TestClient proxybase = new(new HttpClient()); + TestClient proxybase = new(new HttpClient(), Serialise, Deserialise); // Act var result = proxybase.Test_HandleResponseContent>(json); @@ -327,7 +338,7 @@ public void HandleResponseContent_ValidJsonContent_ReturnsDeserializedList() { } public class TestClient : ClientProxyBase.ClientProxyBase{ - public TestClient(HttpClient httpClient) : base(httpClient){ + public TestClient(HttpClient httpClient, Func Serialise, Func Deserialise) : base(httpClient, Serialise, Deserialise){ } public async Task Test_HandleResponse(HttpResponseMessage responseMessage, CancellationToken cancellationToken) { @@ -348,4 +359,4 @@ public class ApiResourceDetails { public string name { get; set; } public string description { get; set; } -} \ No newline at end of file +} diff --git a/Shared.Tests/ConfigurationReaderTests.cs b/Shared.Tests/ConfigurationReaderTests.cs index b200be7a..f0e71f47 100644 --- a/Shared.Tests/ConfigurationReaderTests.cs +++ b/Shared.Tests/ConfigurationReaderTests.cs @@ -19,6 +19,7 @@ public partial class SharedTests public SharedTests(ITestOutputHelper testOutputHelper){ this.TestOutputHelper = testOutputHelper; + TestHelpers.InitialiseStringSerialiser(); } [Fact] diff --git a/Shared.Tests/ConfigurationRootExtensionsTests.cs b/Shared.Tests/ConfigurationRootExtensionsTests.cs index d6093a37..bb0a4802 100644 --- a/Shared.Tests/ConfigurationRootExtensionsTests.cs +++ b/Shared.Tests/ConfigurationRootExtensionsTests.cs @@ -1,29 +1,17 @@ namespace Shared.Tests; using System; -using System.Collections.Generic; using System.Linq; -using Castle.Components.DictionaryAdapter; using Extensions; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Hosting.Internal; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Shouldly; using Xunit; -using static Azure.Core.HttpHeader; /// /// /// public partial class SharedTests { - #region Properties - - - #endregion - #region Methods /// diff --git a/Shared.Tests/HttpMethodTests.cs b/Shared.Tests/HttpMethodTests.cs index 1b7a8942..97c0c306 100644 --- a/Shared.Tests/HttpMethodTests.cs +++ b/Shared.Tests/HttpMethodTests.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using RichardSzalay.MockHttp; +using RichardSzalay.MockHttp; using Shared.Results; using Shouldly; using SimpleResults; @@ -12,12 +11,14 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; +using Shared.Serialisation; using Xunit; namespace Shared.Tests { public class HttpService : ClientProxyBase.ClientProxyBase { - public HttpService(HttpClient httpClient) : base(httpClient) { + public HttpService(HttpClient httpClient, Func Serialise, Func Deserialise) : base(httpClient,Serialise, Deserialise) { } public async Task> SendHttpGetRequest(String uri, @@ -174,11 +175,22 @@ public class HttpServiceTests private readonly MockHttpMessageHandler _mockHttp; private readonly HttpService _service; + String Serialise(Object arg) + { + return StringSerialiser.Serialise(arg); + } + + Object Deserialise(String arg, Type type) + { + return StringSerialiser.DeserializeObject(arg, type); + } + public HttpServiceTests() { _mockHttp = new MockHttpMessageHandler(); var client = _mockHttp.ToHttpClient(); - _service = new HttpService(client); + _service = new HttpService(client, Serialise, Deserialise); + TestHelpers.InitialiseStringSerialiser(); } [Fact] @@ -186,7 +198,7 @@ public async Task SendHttpGetRequest_SuccessfulResponse_ShouldReturnTypedObject( { var payload = new SampleResponse { Message = "Hello", Value = 42 }; _mockHttp.When(HttpMethod.Get, "https://api/success") - .Respond("application/json", JsonConvert.SerializeObject(payload)); + .Respond("application/json", StringSerialiser.Serialise(payload)); var result = await _service.SendHttpGetRequest( "https://api/success", "token", CancellationToken.None); diff --git a/Shared.Tests/MiddlewareTests.cs b/Shared.Tests/MiddlewareTests.cs index d1e1b429..73fb68dd 100644 --- a/Shared.Tests/MiddlewareTests.cs +++ b/Shared.Tests/MiddlewareTests.cs @@ -53,6 +53,7 @@ public void MiddlewareHelpersTests_LogMessage_IsHealthCheckLog(String url, Boole public async Task ExceptionHandlerMiddleware_ArgumentNullExceptionThrown_BadRequestHttpStatusCodeReturned() { TestHelpers.InitialiseLogger(); + TestHelpers.InitialiseStringSerialiser(); ExceptionHandlerMiddleware middleware = new((innerHttpContext) => throw TestHelpers.CreateException()); @@ -129,7 +130,7 @@ public async Task ExceptionHandlerMiddleware_FormatExceptionThrown_BadRequestHtt public async Task ExceptionHandlerMiddleware_NotSupportedExceptionThrown_BadRequestHttpStatusCodeReturned() { TestHelpers.InitialiseLogger(); - + TestHelpers.InitialiseStringSerialiser(); ExceptionHandlerMiddleware middleware = new((innerHttpContext) => throw TestHelpers.CreateException()); @@ -148,7 +149,7 @@ public async Task ExceptionHandlerMiddleware_NotSupportedExceptionThrown_BadRequ public async Task ExceptionHandlerMiddleware_NotFoundExceptionThrown_NotFoundHttpStatusCodeReturned() { TestHelpers.InitialiseLogger(); - + TestHelpers.InitialiseStringSerialiser(); ExceptionHandlerMiddleware middleware = new((innerHttpContext) => throw TestHelpers.CreateException()); @@ -167,7 +168,7 @@ public async Task ExceptionHandlerMiddleware_NotFoundExceptionThrown_NotFoundHtt public async Task ExceptionHandlerMiddleware_NotImplementedExceptionThrown_NotImplementedHttpStatusCodeReturned() { TestHelpers.InitialiseLogger(); - + TestHelpers.InitialiseStringSerialiser(); ExceptionHandlerMiddleware middleware = new((innerHttpContext) => throw TestHelpers.CreateException()); @@ -205,7 +206,7 @@ public async Task ExceptionHandlerMiddleware_OtherExceptionThrown_InternalServer public async Task ExceptionHandlerMiddleware_NoExceptionThrown_OKResponseReturned() { TestHelpers.InitialiseLogger(); - + TestHelpers.InitialiseStringSerialiser(); ExceptionHandlerMiddleware middleware = new((innerHttpContext) => Task.CompletedTask); DefaultHttpContext context = TestHelpers.CreateHttpContext(); diff --git a/Shared.Tests/StringExtensionsTests.cs b/Shared.Tests/StringExtensionsTests.cs index e346da2a..6bf0e08d 100644 --- a/Shared.Tests/StringExtensionsTests.cs +++ b/Shared.Tests/StringExtensionsTests.cs @@ -15,7 +15,7 @@ public partial class SharedTests [InlineData("{\r\n\"Property1\": \"Value1\",\r\n\"Property2\": \"Value2\"\r\n}", true)] [InlineData("{\r\n\"Property1\": \"Value1\"\r\n\"Property2\": \"Value2\"\r\n}", false)] [InlineData("{\r\n\"Property1\": \r\n}", false)] - [InlineData("{\r\n\"Property1\": \"Value1\",\r\n\"Property3\": \"Value2\"\r\n}", false)] + [InlineData("{\r\n\"Property1\": \"Value1\",\r\n\"Property3\": \"Value2\"\r\n}", true)] public void StringExtensions_TryParseJson_ResultExpected(String json, Boolean expectedResult) { Boolean isValidObject = json.TryParseJson(out TestModel model); diff --git a/Shared.Tests/StringSerialiserTests.cs b/Shared.Tests/StringSerialiserTests.cs index 7d81d3ae..b7db0b53 100644 --- a/Shared.Tests/StringSerialiserTests.cs +++ b/Shared.Tests/StringSerialiserTests.cs @@ -1,3 +1,5 @@ +using System; + namespace Shared.Tests; using System.Text.Json; @@ -10,9 +12,9 @@ public partial class SharedTests [Fact] public void StringSerialiser_Initialise_SetsIsInitialised() { - var fake = new FakeSerialiser(); + var serializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); - StringSerialiser.Initialise(fake); + StringSerialiser.Initialise(serializer); StringSerialiser.IsInitialised.ShouldBeTrue(); } @@ -20,69 +22,61 @@ public void StringSerialiser_Initialise_SetsIsInitialised() [Fact] public void StringSerialiser_Serialise_UsesProvidedSerializer() { - var fake = new FakeSerialiser(); - StringSerialiser.Initialise(fake); + var serializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); + + StringSerialiser.Initialise(serializer); var person = new Person { Name = "Alice", Age = 25 }; var json = StringSerialiser.Serialise(person); - fake.LastObj.ShouldBe(person); - json.ShouldBe(fake.LastJson); + json.ShouldContain("\"Name\":\"Alice\""); + json.ShouldContain("\"Age\":25"); + } + + [Fact] + public void StringSerialiser_Serialise_InterfaceReference_UsesRuntimeType() + { + var serializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); + + StringSerialiser.Initialise(serializer); + + ITestPerson person = new DetailedPerson { Name = "Alice", Age = 25 }; + + var json = StringSerialiser.Serialise(person); + + json.ShouldContain("\"Name\":\"Alice\""); + json.ShouldContain("\"Age\":25"); } [Fact] public void StringSerialiser_Deserialize_ReturnsObject() { - var fake = new FakeSerialiser(); - StringSerialiser.Initialise(fake); + var serializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); + + StringSerialiser.Initialise(serializer); var expected = new Person { Name = "Dan", Age = 40 }; var json = JsonSerializer.Serialize(expected); - var result = StringSerialiser.Deserialize(json); + var result = StringSerialiser.Deserialise(json); result.Name.ShouldBe(expected.Name); result.Age.ShouldBe(expected.Age); } - - [Fact] - public void StringSerialiser_Initialise_ReplacesPreviousSerializer() + + private class Person { - var first = new FakeSerialiser(); - var second = new FakeSerialiser(); - - StringSerialiser.Initialise(first); - var p1 = new Person { Name = "X", Age = 1 }; - StringSerialiser.Serialise(p1); - first.LastObj.ShouldBe(p1); - - StringSerialiser.Initialise(second); - var p2 = new Person { Name = "Y", Age = 2 }; - StringSerialiser.Serialise(p2); - second.LastObj.ShouldBe(p2); + public string Name { get; set; } + public int Age { get; set; } } - private class FakeSerialiser : IStringSerialiser + private interface ITestPerson { - public object? LastObj { get; private set; } - public string? LastJson { get; private set; } - - public string Serialize(T obj) - { - LastObj = obj!; - LastJson = JsonSerializer.Serialize(obj); - return LastJson!; - } - - public T Deserialize(string json) - { - LastJson = json; - return JsonSerializer.Deserialize(json)!; - } + string Name { get; set; } } - private class Person + private class DetailedPerson : ITestPerson { public string Name { get; set; } public int Age { get; set; } diff --git a/Shared.Tests/TestHelpers.cs b/Shared.Tests/TestHelpers.cs index b9b805d7..9519338e 100644 --- a/Shared.Tests/TestHelpers.cs +++ b/Shared.Tests/TestHelpers.cs @@ -1,4 +1,7 @@ -namespace Shared.Tests; +using System.Text.Json; +using Shared.Serialisation; + +namespace Shared.Tests; using System; using System.Collections.Generic; @@ -6,7 +9,6 @@ using Logger; using Microsoft.AspNetCore.Http; using Middleware; -using Newtonsoft.Json; public static class TestHelpers{ public static DefaultHttpContext CreateHttpContext() @@ -28,8 +30,11 @@ public static ErrorResponse GetErrorResponse(DefaultHttpContext context) context.Response.Body.Seek(0, SeekOrigin.Begin); StreamReader reader = new(context.Response.Body); String streamText = reader.ReadToEnd(); - ErrorResponse responseData = JsonConvert.DeserializeObject(streamText); - return responseData; + + return streamText switch { + null or "" => null, + _ => StringSerialiser.Deserialise(streamText) + }; } public static readonly String ExceptionMessage = "Test Exception Message"; @@ -44,6 +49,16 @@ public static TestLogger InitialiseLogger(){ return logger; } + public static void InitialiseStringSerialiser() + { + if (StringSerialiser.IsInitialised) + { + return; + } + var serializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); + StringSerialiser.Initialise(serializer); + } + public static IReadOnlyDictionary DefaultAppSettings { get; } = new Dictionary { ["AppSettings:Test"] = "", diff --git a/Shared/Extensions/StringExtensions.cs b/Shared/Extensions/StringExtensions.cs index 8e2b6c44..0c006ebf 100644 --- a/Shared/Extensions/StringExtensions.cs +++ b/Shared/Extensions/StringExtensions.cs @@ -1,41 +1,22 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Text; +using System.Text.Json; +using Shared.Serialisation; namespace Shared.Extensions; public static class StringExtensions { - #region public static Boolean TryParseJson(this String obj, out T result) - /// - /// Tries the parse json. - /// - /// - /// The object. - /// The result. - /// - public static Boolean TryParseJson(this String obj, out T result) - { - try - { - // Validate missing fields of object - JsonSerializerSettings settings = new(); - settings.MissingMemberHandling = MissingMemberHandling.Error; - - result = JsonConvert.DeserializeObject(obj, settings); + public static bool TryParseJson(this string obj, + out T result) { + try { + result = StringSerialiser.Deserialise(obj); return true; } - catch (JsonReaderException) - { - result = default(T); - return false; - } - catch (JsonSerializationException) - { - result = default(T); + catch (JsonException) { + result = default; return false; } } - #endregion } \ No newline at end of file diff --git a/Shared/General/ConfigurationReader.cs b/Shared/General/ConfigurationReader.cs index ae2b78c8..ea0f1432 100644 --- a/Shared/General/ConfigurationReader.cs +++ b/Shared/General/ConfigurationReader.cs @@ -1,70 +1,31 @@ -namespace Shared.General; +using Shared.Serialisation; + +namespace Shared.General; using System; using System.Collections.Generic; using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; public static class ConfigurationReader { - #region Fields - - /// - /// The configuration root - /// private static IConfigurationRoot ConfigurationRoot; - #endregion - - #region Properties - - /// - /// Gets a value indicating whether this instance is initialised. - /// - /// - /// true if this instance is initialised; otherwise, false. - /// public static Boolean IsInitialised { get; private set; } - - #endregion - - #region Methods - - /// - /// Gets the base server URI. - /// - /// Name of the service. - /// + public static Uri GetBaseServerUri(String serviceName) { var uriString = ConfigurationReader.ConfigurationRoot.GetSection("AppSettings")[serviceName]; return new Uri(uriString); } - /// - /// Gets the connection string. - /// - /// Name of the key. - /// public static String GetConnectionString(String keyName) { return ConfigurationReader.GetValueFromSection("ConnectionStrings", keyName); } - /// - /// Gets the value. - /// - /// Name of the key. - /// public static String GetValue(String keyName) { return ConfigurationReader.GetValueFromSection("AppSettings", keyName); } - /// - /// Gets the value. - /// - /// Name of the section. - /// Name of the key. - /// public static String GetValue(String sectionName, String keyName) { return ConfigurationReader.GetValueFromSection(sectionName, keyName); @@ -75,14 +36,13 @@ public static T GetValueFromSection(String sectionName, return ConfigurationReader.GetTypedValueFromSection(sectionName, keyName); } - public static T GetValueOrDefault(String sectionName, String keyName, T defaultValue) - { - try - { + public static T GetValueOrDefault(String sectionName, + String keyName, + T defaultValue) { + try { var value = ConfigurationReader.GetValue(sectionName, keyName); - if (String.IsNullOrEmpty(value)) - { + if (String.IsNullOrEmpty(value)) { return defaultValue; } @@ -92,39 +52,31 @@ public static T GetValueOrDefault(String sectionName, String keyName, T defau return (T)Convert.ChangeType(value, typeof(T)); } - catch (KeyNotFoundException) - { + catch (KeyNotFoundException) { return defaultValue; } } - /// - /// Initialises the specified configuration root. - /// - /// The configuration root. - /// configurationRoot public static void Initialise(IConfigurationRoot configurationRoot) { ConfigurationReader.ConfigurationRoot = configurationRoot ?? throw new ArgumentNullException(nameof(configurationRoot)); ConfigurationReader.IsInitialised = true; } - private static String GetValueFromSection(String sectionName, String keyName) - { - if (!ConfigurationReader.IsInitialised) - { + private static String GetValueFromSection(String sectionName, + String keyName) { + if (!ConfigurationReader.IsInitialised) { throw new InvalidOperationException("Configuration Reader has not been initialised"); } IConfigurationSection section = null; - try{ + try { section = ConfigurationReader.ConfigurationRoot.GetRequiredSection(sectionName); } - catch(InvalidOperationException){ + catch (InvalidOperationException) { throw new KeyNotFoundException($"Section [{sectionName}] not found."); } - if (section[keyName] == null) - { + if (section[keyName] == null) { throw new KeyNotFoundException($"No configuration value was found for key [{sectionName}:{keyName}]"); } @@ -133,14 +85,14 @@ private static String GetValueFromSection(String sectionName, String keyName) private static T GetTypedValueFromSection(String sectionName, String keyName) { - if (!ConfigurationReader.IsInitialised) { + if (!IsInitialised) { throw new InvalidOperationException("Configuration Reader has not been initialised"); } - IConfigurationSection section = null; + IConfigurationSection section; try { - section = ConfigurationReader.ConfigurationRoot.GetRequiredSection(sectionName); + section = ConfigurationRoot.GetRequiredSection(sectionName); } catch (InvalidOperationException) { @@ -153,12 +105,10 @@ private static T GetTypedValueFromSection(String sectionName, throw new KeyNotFoundException($"No configuration value was found for key [{sectionName}:{keyName}]"); } - T returnValue = default(T); + T returnValue = default; - returnValue = JsonConvert.DeserializeAnonymousType(value, returnValue); + returnValue = StringSerialiser.DeserialiseAnonymousType(value, returnValue); return returnValue; } - - #endregion } \ No newline at end of file diff --git a/Shared/HealthChecks/HealthCheckClient.cs b/Shared/HealthChecks/HealthCheckClient.cs index 4673448c..42e497ed 100644 --- a/Shared/HealthChecks/HealthCheckClient.cs +++ b/Shared/HealthChecks/HealthCheckClient.cs @@ -1,29 +1,20 @@ -using System.Collections.Generic; -using System.Net; +using System.Net; using SimpleResults; - -namespace Shared.HealthChecks; - using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; + +namespace Shared.HealthChecks; public class HealthCheckClient : IHealthCheckClient { private readonly HttpClient HttpClient; - #region Constructors - public HealthCheckClient(HttpClient httpClient) { this.HttpClient = httpClient; } - #endregion - - #region Methods - public async Task> PerformHealthCheck(String scheme, String uri, Int32 port, @@ -64,6 +55,4 @@ private String BuildRequestUri(String scheme, Int32 port) { return $"{scheme}://{uri}:{port}/health"; } - - #endregion } \ No newline at end of file diff --git a/Shared/Middleware/CorrelationIdMiddleware.cs b/Shared/Middleware/CorrelationIdMiddleware.cs index 356b2ac1..04ab7ce3 100644 --- a/Shared/Middleware/CorrelationIdMiddleware.cs +++ b/Shared/Middleware/CorrelationIdMiddleware.cs @@ -1,16 +1,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; -using Microsoft.VisualBasic; -using Newtonsoft.Json.Linq; using Shared.General; using Shared.Logger.TennantContext; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Security.Claims; -using System.Text; using System.Threading.Tasks; using ClaimsPrincipal = System.Security.Claims.ClaimsPrincipal; @@ -18,23 +13,15 @@ namespace Shared.Middleware; public class TenantMiddleware { - #region Fields - private readonly RequestDelegate Next; - #endregion - - #region Constructors - public TenantMiddleware(RequestDelegate next) { this.Next = next; } public static readonly String KeyNameCorrelationId = "correlationId"; - - #endregion - + public async Task InvokeAsync(HttpContext context, TenantContext tenantContext) { Stopwatch watch = Stopwatch.StartNew(); diff --git a/Shared/Middleware/ExceptionHandlerMiddleware.cs b/Shared/Middleware/ExceptionHandlerMiddleware.cs index 43a057a9..87bb7f27 100644 --- a/Shared/Middleware/ExceptionHandlerMiddleware.cs +++ b/Shared/Middleware/ExceptionHandlerMiddleware.cs @@ -1,76 +1,33 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -using Newtonsoft.Json; using Shared.Exceptions; +using Shared.Serialisation; namespace Shared.Middleware; -public class ExceptionHandlerMiddleware -{ - #region Fields - - /// - /// The next method - /// +public class ExceptionHandlerMiddleware { private readonly RequestDelegate next; - #endregion - - #region Constructors - /// - /// Initializes a new instance of the class. - /// - /// The next. - public ExceptionHandlerMiddleware(RequestDelegate next) - { + public ExceptionHandlerMiddleware(RequestDelegate next) { this.next = next; } - #endregion - #region Public Methods - - #region public async Task Invoke(HttpContext context) - /// - /// Invokes the specified context. - /// - /// The context. - /// - public async Task Invoke(HttpContext context) - { - try - { + public async Task Invoke(HttpContext context) { + try { await next(context); } - catch (Exception ex) - { + catch (Exception ex) { await HandleExceptionAsync(context, ex); } } - #endregion - - #endregion - #region Private Methods - - #region private async Task HandleExceptionAsync(HttpContext context, Exception exception) - /// - /// Handles the exception asynchronous. - /// - /// The context. - /// The exception. - /// - private async Task HandleExceptionAsync(HttpContext context, Exception exception) - { - Exception newException = - new( - $"An unhandled exception has occurred while executing the request. Url: {context.Request.GetDisplayUrl()}", - exception); + private async Task HandleExceptionAsync(HttpContext context, + Exception exception) { + Exception newException = new($"An unhandled exception has occurred while executing the request. Url: {context.Request.GetDisplayUrl()}", exception); Logger.Logger.LogError(newException); // Set some defaults @@ -78,32 +35,22 @@ private async Task HandleExceptionAsync(HttpContext context, Exception exception HttpStatusCode statusCode = HttpStatusCode.InternalServerError; String message = "Unexpected error"; - if (exception is ArgumentException || - exception is InvalidOperationException || - exception is InvalidDataException || - exception is FormatException || - exception is NotSupportedException) - { + if (exception is ArgumentException || exception is InvalidOperationException || exception is InvalidDataException || exception is FormatException || exception is NotSupportedException) { statusCode = HttpStatusCode.BadRequest; message = exception.Message; } - else if (exception is NotFoundException) - { + else if (exception is NotFoundException) { statusCode = HttpStatusCode.NotFound; message = exception.Message; } - else if (exception is NotImplementedException) - { + else if (exception is NotImplementedException) { statusCode = HttpStatusCode.NotImplemented; message = exception.Message; } response.ContentType = context.Request.ContentType; - response.StatusCode = (Int32) statusCode; + response.StatusCode = (Int32)statusCode; - await response.WriteAsync(JsonConvert.SerializeObject(new ErrorResponse(message))); + await response.WriteAsync(StringSerialiser.Serialise(new ErrorResponse(message))); } - #endregion - - #endregion } \ No newline at end of file diff --git a/Shared/Serialisation/JsonIgnoreAttributeIgnorerContractResolver.cs b/Shared/Serialisation/JsonIgnoreAttributeIgnorerContractResolver.cs deleted file mode 100644 index 206f7df9..00000000 --- a/Shared/Serialisation/JsonIgnoreAttributeIgnorerContractResolver.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Shared.Serialisation; - -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -[ExcludeFromCodeCoverage] -public class JsonIgnoreAttributeIgnorerContractResolver : DefaultContractResolver -{ - #region Methods - - /// - /// Creates a for the given . - /// - /// The member to create a for. - /// The member's parent . - /// - /// A created for the given . - /// - protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(MemberInfo member, - MemberSerialization memberSerialization) - { - JsonProperty property = base.CreateProperty(member, memberSerialization); - property.Ignored = false; // Here is the magic - return property; - } - - #endregion -} \ No newline at end of file diff --git a/Shared/Serialisation/StringSerialiser.cs b/Shared/Serialisation/StringSerialiser.cs index a5c08b36..891993ba 100644 --- a/Shared/Serialisation/StringSerialiser.cs +++ b/Shared/Serialisation/StringSerialiser.cs @@ -8,9 +8,24 @@ namespace Shared.Serialisation; +public enum SerialiserPropertyFormat +{ + CamelCase, + SnakeCase, +} +public record SerialiserOptions(SerialiserPropertyFormat PropertyFormat, Boolean IgnoreNullValues = true, Boolean WriteIndented = false); + + public interface IStringSerialiser { - string Serialize(T obj); - T Deserialize(string json); + string Serialize(T obj, SerialiserOptions serialiserOptions= null); + T Deserialize(string json, SerialiserOptions serialiserOptions = null); + + T DeserializeAnonymousType(string json, + T anonymousTypeObject, SerialiserOptions serialiserOptions = null); + + T DeserializeObject(string json, Type type, SerialiserOptions serialiserOptions = null); + + T? GetValue(string json, string propertyName); } public class SystemTextJsonSerializer : IStringSerialiser @@ -21,12 +36,107 @@ public SystemTextJsonSerializer(JsonSerializerOptions options) { Options = options; } - public string Serialize(T obj) { - return JsonSerializer.Serialize(obj, Options); + private JsonSerializerOptions BuildSerialiserOptions(SerialiserOptions serialiserOptions) + { + if (serialiserOptions == null) { + return Options; + } + + var options = new JsonSerializerOptions(Options); + if (serialiserOptions.IgnoreNullValues) + { + options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull; + } + if (serialiserOptions.WriteIndented) + { + options.WriteIndented = true; + } + options.PropertyNamingPolicy = serialiserOptions.PropertyFormat switch + { + SerialiserPropertyFormat.CamelCase => JsonNamingPolicy.CamelCase, + SerialiserPropertyFormat.SnakeCase => JsonNamingPolicy.SnakeCaseLower, + _ => options.PropertyNamingPolicy + }; + return options; + } + + public string Serialize(T obj, SerialiserOptions serialiserOptions = null) { + + var options = BuildSerialiserOptions(serialiserOptions); + return obj is null + ? JsonSerializer.Serialize(obj, options) + : JsonSerializer.Serialize(obj, obj.GetType(), options); + } + + public T Deserialize(string json, SerialiserOptions serialiserOptions = null) { + var options = BuildSerialiserOptions(serialiserOptions); + return JsonSerializer.Deserialize(json, options)!; + } + + public T DeserializeAnonymousType(String json, + T anonymousTypeObject, SerialiserOptions serialiserOptions = null) { + var options = BuildSerialiserOptions(serialiserOptions); + return JsonSerializer.Deserialize(json, options)!; + } + + public T DeserializeObject(String json, + Type type, SerialiserOptions serialiserOptions = null) { + var options = BuildSerialiserOptions(serialiserOptions); + return (T)JsonSerializer.Deserialize(json, type, options)!; + } + + public T GetValue(String json, + String propertyName) { + using var doc = JsonDocument.Parse(json); + var _root = doc.RootElement.Clone(); // important to avoid disposed doc issues + + if (TryFindProperty(_root, propertyName, out var value)) + { + try + { + return value.Deserialize(); + } + catch + { + return default; + } + } + + return default; } - public T Deserialize(string json) { - return JsonSerializer.Deserialize(json, Options)!; + private static bool TryFindProperty( + JsonElement element, + string propertyName, + out JsonElement found) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var prop in element.EnumerateObject()) + { + if (string.Equals(prop.Name, propertyName, StringComparison.OrdinalIgnoreCase)) + { + found = prop.Value; + return true; + } + + if (TryFindProperty(prop.Value, propertyName, out found)) + return true; + } + break; + + case JsonValueKind.Array: + foreach (var item in element.EnumerateArray()) + { + if (TryFindProperty(item, propertyName, out found)) + return true; + } + break; + } + + found = default; + return false; } } @@ -39,11 +149,28 @@ public static void Initialise(IStringSerialiser serialiser) { IsInitialised = true; } - public static string Serialise(T obj) { - return Serializer.Serialize(obj); + public static string Serialise(T obj, SerialiserOptions serialiserOptions = null) { + if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + return Serializer.Serialize(obj, serialiserOptions); } - public static T Deserialize(string json) { - return Serializer.Deserialize(json); + public static T Deserialise(string json, SerialiserOptions serialiserOptions = null) { + if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + return Serializer.Deserialize(json, serialiserOptions); } -} \ No newline at end of file + + public static T DeserialiseAnonymousType(String json, T anonymousTypeObject, SerialiserOptions serialiserOptions = null) { + if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + return Serializer.DeserializeAnonymousType(json, anonymousTypeObject, serialiserOptions); + } + + public static T DeserializeObject(String json, Type type, SerialiserOptions serialiserOptions = null) { + if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + return Serializer.DeserializeObject(json, type, serialiserOptions); + } + + public static T GetValue(String json, String propertyName, SerialiserOptions serialiserOptions = null) { + if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + return Serializer.GetValue(json, propertyName); + } +} diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index 3247ca39..95ed1d8d 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -13,7 +13,6 @@ - From 09318cb9a4bd375242f15aa65da1157abe2f17ff Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 1 May 2026 10:07:47 +0100 Subject: [PATCH 2/5] fix codacy issues --- Shared.EventStore/Aggregate/EventDataFactory.cs | 2 +- Shared/Serialisation/StringSerialiser.cs | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Shared.EventStore/Aggregate/EventDataFactory.cs b/Shared.EventStore/Aggregate/EventDataFactory.cs index fbcdd9d0..e8357f93 100644 --- a/Shared.EventStore/Aggregate/EventDataFactory.cs +++ b/Shared.EventStore/Aggregate/EventDataFactory.cs @@ -16,7 +16,7 @@ public class EventDataFactory : IEventDataFactory public EventData CreateEventData(IDomainEvent domainEvent) { this.GuardAgainstNoDomainEvent(domainEvent); - var x = StringSerialiser.Serialise(domainEvent, SerialiserOptions); + Byte[] data = Encoding.Default.GetBytes(StringSerialiser.Serialise(domainEvent, SerialiserOptions)); EventData eventData = new(Uuid.FromGuid(domainEvent.EventId), domainEvent.EventType, data); diff --git a/Shared/Serialisation/StringSerialiser.cs b/Shared/Serialisation/StringSerialiser.cs index 891993ba..e6b5d893 100644 --- a/Shared/Serialisation/StringSerialiser.cs +++ b/Shared/Serialisation/StringSerialiser.cs @@ -5,6 +5,7 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; namespace Shared.Serialisation; @@ -133,6 +134,10 @@ private static bool TryFindProperty( return true; } break; + default: + found = default; + return false; + break; } found = default; @@ -141,6 +146,7 @@ private static bool TryFindProperty( } public static class StringSerialiser{ + private const String NotInitialisedErrorMessage = "StringSerialiser is not initialised."; public static Boolean IsInitialised { get; set; } private static IStringSerialiser Serializer; @@ -150,27 +156,27 @@ public static void Initialise(IStringSerialiser serialiser) { } public static string Serialise(T obj, SerialiserOptions serialiserOptions = null) { - if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + if (!IsInitialised) throw new InvalidOperationException(NotInitialisedErrorMessage); return Serializer.Serialize(obj, serialiserOptions); } public static T Deserialise(string json, SerialiserOptions serialiserOptions = null) { - if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + if (!IsInitialised) throw new InvalidOperationException(NotInitialisedErrorMessage); return Serializer.Deserialize(json, serialiserOptions); } public static T DeserialiseAnonymousType(String json, T anonymousTypeObject, SerialiserOptions serialiserOptions = null) { - if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + if (!IsInitialised) throw new InvalidOperationException(NotInitialisedErrorMessage); return Serializer.DeserializeAnonymousType(json, anonymousTypeObject, serialiserOptions); } public static T DeserializeObject(String json, Type type, SerialiserOptions serialiserOptions = null) { - if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + if (!IsInitialised) throw new InvalidOperationException(NotInitialisedErrorMessage); return Serializer.DeserializeObject(json, type, serialiserOptions); } public static T GetValue(String json, String propertyName, SerialiserOptions serialiserOptions = null) { - if (!IsInitialised) throw new InvalidOperationException("StringSerialiser is not initialised."); + if (!IsInitialised) throw new InvalidOperationException(NotInitialisedErrorMessage); return Serializer.GetValue(json, propertyName); } } From 59f70bd607098cbb13b2b32bc425ab5fab1e6711 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 1 May 2026 10:21:19 +0100 Subject: [PATCH 3/5] fix serialiser not initialised --- Shared.IntegrationTesting/TestContainers/DockerHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Shared.IntegrationTesting/TestContainers/DockerHelper.cs b/Shared.IntegrationTesting/TestContainers/DockerHelper.cs index 22cab61d..1e539db4 100644 --- a/Shared.IntegrationTesting/TestContainers/DockerHelper.cs +++ b/Shared.IntegrationTesting/TestContainers/DockerHelper.cs @@ -1,6 +1,7 @@ using System.Threading; using DotNet.Testcontainers.Containers; using DotNet.Testcontainers.Networks; +using Shared.Serialisation; using SimpleResults; namespace Shared.IntegrationTesting.TestContainers; @@ -15,6 +16,7 @@ namespace Shared.IntegrationTesting.TestContainers; public abstract class DockerHelper : BaseDockerHelper { protected DockerHelper(Boolean skipHealthChecks=false) :base(skipHealthChecks){ + StringSerialiser.Initialise(new SystemTextJsonSerializer(new System.Text.Json.JsonSerializerOptions())); } protected virtual void SetHostTraceFolder(String scenarioName) { From 4c553c1034d8800894bf48e72a7c5950bec4dcba Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 1 May 2026 11:25:00 +0100 Subject: [PATCH 4/5] :| --- Shared/HealthChecks/HealthCheckResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shared/HealthChecks/HealthCheckResult.cs b/Shared/HealthChecks/HealthCheckResult.cs index 06c64c51..db8f2a2d 100644 --- a/Shared/HealthChecks/HealthCheckResult.cs +++ b/Shared/HealthChecks/HealthCheckResult.cs @@ -8,6 +8,6 @@ public class HealthCheckResult { #region Properties - public String Status { get; set; } + public String status { get; set; } #endregion } \ No newline at end of file From a7a4095ae58aadb028850c310fb843ceceec1484 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 1 May 2026 11:28:01 +0100 Subject: [PATCH 5/5] .. --- Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs | 2 +- Shared/HealthChecks/HealthCheckResult.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs b/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs index faee249c..a637d487 100644 --- a/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs +++ b/Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs @@ -851,7 +851,7 @@ await Retry.For(async () => { SimpleResults.Result healthCheckResult = await this.HealthCheckClient.PerformHealthCheck(containerDetails.Item1, "127.0.0.1", containerDetails.Item2, CancellationToken.None); if (healthCheckResult.IsSuccess) { - HealthChecks.HealthCheckResult result = StringSerialiser.Deserialise(healthCheckResult.Data); + HealthChecks.HealthCheckResult result = StringSerialiser.Deserialise(healthCheckResult.Data, new SerialiserOptions(SerialiserPropertyFormat.CamelCase)); this.Trace($"health check complete for {containerType} result is [{healthCheckResult.Data}]"); diff --git a/Shared/HealthChecks/HealthCheckResult.cs b/Shared/HealthChecks/HealthCheckResult.cs index db8f2a2d..06c64c51 100644 --- a/Shared/HealthChecks/HealthCheckResult.cs +++ b/Shared/HealthChecks/HealthCheckResult.cs @@ -8,6 +8,6 @@ public class HealthCheckResult { #region Properties - public String status { get; set; } + public String Status { get; set; } #endregion } \ No newline at end of file