Skip to content

Modernize dependency injection and JSON serialization - #368

Draft
niemyjski wants to merge 13 commits into
mainfrom
feature/modern-di-stj
Draft

niemyjski wants to merge 13 commits into
mainfrom
feature/modern-di-stj

Conversation

@niemyjski

@niemyjski niemyjski commented Jul 13, 2026 •

Copy link
Copy Markdown
Member

Replace the vendored Newtonsoft.Json serializer and TinyIoC container with System.Text.Json and Microsoft DI, preserving the client setup APIs and collector payload format while adding source-generated serialization and NativeAOT support.

Compatibility: recommend a 7.0 release. Custom payloads adopt STJ contracts; NativeAOT payloads need generated metadata. Hosting/logging configuration overloads own isolated clients, modern targets use runtime stack traces, and STJ 10/Microsoft DI become dependencies.

Merge blocker: the additional hardening under review is not yet published. Its keyed DI activation still fails [ServiceKey] injection. Resolve the activation design and rerun the complete matrix before marking this PR ready.

Validation: published head d94a17b passed Linux, macOS, Windows, and CLA. The September 21 review run passed 427 core and 15 MessagePack tests, with three keyed-DI failures and 18 existing skips; a subsequent cycle fix reduced the focused DI run to 51 passed / one failed. These review results do not replace CI on the final commit.

Verification and implementation details

Behavior and compatibility

  • Preserve snake_case wire names, storage round trips, raw extended data, exclusions, depth limits, cycles, public fields, and the legacy ignore attribute.
  • Support STJ contracts, custom converters, source-generated metadata, and scalar event data under NativeAOT.
  • Keep each client's resolver isolated. Application root-provider registrations are not imported automatically; explicitly supplied clients remain caller-owned.
  • Add modern core assets alongside .NET Standard and .NET Framework support. Validate the actual NuGet package through compatibility-asset and NativeAOT consumers.
  • Previous API compatibility checks passed, but behavioral and dependency changes still warrant a major version.

Current review gate

The remaining failure is Resolve_WithKeyedImplementation_InjectsServiceKey: rewriting a keyed implementation descriptor as an ActivatorUtilities factory loses native Microsoft DI key binding. Preserve canonical container activation instead of adding another constructor-selection implementation. Reconcile that boundary with captured-provider disposal behavior before approval.

The review also corrected false cycle detection between different service keys and updated the build-only SourceLink package from 10.0.300 to patched 10.0.303 after NU1902 exposed GHSA-23fw-v26w-5fgq. These follow-up changes require publication and fresh CI.

The latest core test coverage report is 74.35% line / 51.36% branch overall; its core product assembly is 76.73% / 52.28%. The run had failures, so these figures are not release approval. No changed C# file exceeds 1,000 lines.

Reproduce the principal gate:

dotnet test Exceptionless.Net.NonWindows.slnx -c Release --collect:"XPlat Code Coverage"
dotnet test test/Exceptionless.Tests/Exceptionless.Tests.csproj -c Release -f net10.0 --filter FullyQualifiedName~DependencyTests

Before merge, require a passing final suite, refreshed coverage, Windows .NET Framework runtime/package tests, package and hosting NativeAOT executables for net8.0/net10.0, API compatibility, and dependency audit on the published revision.

Historical probes established that the original client did not work under NativeAOT: TinyIoC activation and reflection-based Newtonsoft serialization were independent blockers. Replacing dependencies alone was insufficient; generated metadata and trimming-safe activation were also necessary.

niemyjski and others added 12 commits July 12, 2026 13:31
- Remove vendored Newtonsoft.Json (entire src/Exceptionless/Newtonsoft.Json/ directory)
- Remove update-json.ps1 script
- Add System.Text.Json 10.0.0 NuGet package reference
- Rewrite DefaultJsonSerializer using System.Text.Json with:
  - SnakeCaseNamingPolicy matching legacy Newtonsoft behavior (e.g. OSName -> o_s_name)
  - Per-type snake_case applied only to Exceptionless.Models namespace
  - DataDictionaryConverter for storing complex values as JSON strings
  - SettingsDictionaryConverter for ObservableDictionary-based type
  - ObjectToInferredTypesConverter for proper type inference
  - PostDataConverter to convert object/array PostData to indented strings
  - Custom WriteValue with depth limiting and property exclusion support
- Update all model classes to remove [JsonObject] attributes
- Add [JsonPropertyName] for EnvironmentInfo OS properties
- Update DefaultSubmissionClient to use JsonDocument instead of JObject
- All 300 tests pass

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sion bug

- Replace custom SnakeCaseNamingPolicy with built-in JsonNamingPolicy.SnakeCaseLower
  (aligns with server approach in exceptionless/Exceptionless#2135)
- Delete unused SnakeCaseNamingPolicy.cs
- Fix DataDictionaryConverter.Write: use WriteRawValue for JSON strings that
  were previously objects (fixes double-escaping on storage roundtrip)
- Fix exclusion logic: add TypeInfoResolver = new DefaultJsonTypeInfoResolver()
  so GetTypeInfo() works and WriteValue can filter properties by name
- Update all test assertions to expect snake_case property names

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WriteValue for IDictionary entries wrote the property name before checking
whether the value could actually be serialized at the current depth. When a
complex value exceeded maxDepth, WriteValue returned without writing anything,
leaving the JSON writer in an invalid state. The error was silently swallowed
by continueOnSerializationError, causing a fallback to full serialization
(effectively ignoring the depth limit entirely).

Fix: check depth before writing the property name. Skip complex dictionary
entries that would exceed maxDepth, consistent with the object property path.

Added regression test that fails before the fix (depth limit violated) and
passes after.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- preserve literal JSON-looking strings in DataDictionary as strings
- restore raw JSON emission only for values produced from structured data
- preserve raw JSON markers through MessagePack storage roundtrips
- coerce primitive SettingsDictionary JSON values to strings like main
- keep dictionary depth-limit regression coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant