![]() |
VOOZH | about |
dotnet add package Davasorus.Utility.DotNet.Config --version 2026.2.3.4
NuGet\Install-Package Davasorus.Utility.DotNet.Config -Version 2026.2.3.4
<PackageReference Include="Davasorus.Utility.DotNet.Config" Version="2026.2.3.4" />
<PackageVersion Include="Davasorus.Utility.DotNet.Config" Version="2026.2.3.4" />Directory.Packages.props
<PackageReference Include="Davasorus.Utility.DotNet.Config" />Project file
paket add Davasorus.Utility.DotNet.Config --version 2026.2.3.4
#r "nuget: Davasorus.Utility.DotNet.Config, 2026.2.3.4"
#:package Davasorus.Utility.DotNet.Config@2026.2.3.4
#addin nuget:?package=Davasorus.Utility.DotNet.Config&version=2026.2.3.4Install as a Cake Addin
#tool nuget:?package=Davasorus.Utility.DotNet.Config&version=2026.2.3.4Install as a Cake Tool
Davasorus.Utility.DotNet.Config provides utilities for loading, creating, converting, and updating configuration files (JSON, XML, and YAML) in .NET 8+ applications. It is designed for use with dependency injection (DI) and follows a strict service/client separation: end users should only interact with the Service interfaces; the Service classes handle all client interactions, error handling, and logging.
Use the fluent configuration API to register services easily:
using Davasorus.Utility.DotNet.Config.Configuration;
// Register only JSON services (Load, Create, Convert, Update)
services.AddConfigServices(config => config.AddJsonServices());
// Register only XML services (Load, Create, Convert, Update)
services.AddConfigServices(config => config.AddXmlServices());
// Register only YAML services (Load, Create, Convert, Update)
services.AddConfigServices(config => config.AddYamlServices());
// Register all services (JSON + XML + YAML)
services.AddConfigServices(config => config.AddAllServices());
// Or chain them explicitly
services.AddConfigServices(config => config
.AddJsonServices()
.AddXmlServices()
.AddYamlServices());
You can also register services individually if you need fine-grained control:
// JSON services
services.AddJsonConfigServices();
// XML services
services.AddXmlConfigServices();
// YAML services
services.AddYamlConfigServices();
// All services
services.AddAllConfigServices();
If you prefer manual registration:
// JSON
services.AddScoped<ILoadJSONService, LoadJSONService>();
services.AddTransient<ILoadJSONClient, LoadJSONClient>();
services.AddScoped<ICreateJSONService, CreateJSONService>();
services.AddTransient<ICreateJSONClient, CreateJSONClient>();
services.AddScoped<IConvertToJSONService, ConvertToJSONService>();
services.AddTransient<IConvertToJSONClient, ConvertToJSONClient>();
services.AddScoped<IUpdateJSONService, UpdateJSONService>();
services.AddTransient<IUpdateJSONClient, UpdateJSONClient>();
// XML
services.AddScoped<ILoadXmlService, LoadXmlService>();
services.AddTransient<ILoadXMLClient, LoadXMLClient>();
services.AddScoped<ICreateXMLService, CreateXMLService>();
services.AddTransient<ICreateXMLClient, CreateXMLClient>();
services.AddScoped<IConvertToXMLService, ConvertToXMLService>();
services.AddTransient<IConvertToXMLClient, ConvertToXMLClient>();
services.AddScoped<IUpdateXMLService, UpdateXMLService>();
services.AddTransient<IUpdateXMLClient, UpdateXMLClient>();
// YAML
services.AddScoped<ILoadYAMLService, LoadYAMLService>();
services.AddTransient<ILoadYAMLClient, LoadYAMLClient>();
services.AddScoped<ICreateYAMLService, CreateYAMLService>();
services.AddTransient<ICreateYAMLClient, CreateYAMLClient>();
services.AddScoped<IConvertToYAMLService, ConvertToYAMLService>();
services.AddTransient<IConvertToYAMLClient, ConvertToYAMLClient>();
services.AddScoped<IUpdateYAMLService, UpdateYAMLService>();
services.AddTransient<IUpdateYAMLClient, UpdateYAMLClient>();
Note: Only use the Service interfaces (
ILoadJSONService,ICreateJSONService,ILoadXmlService,ILoadYAMLService, etc.) in your application code. The Service classes manage all communication with their respective Client classes internally.
public class MyConfigLoader
{
private readonly ILoadJSONService _loadJsonService;
public MyConfigLoader(ILoadJSONService loadJsonService)
{
_loadJsonService = loadJsonService;
}
public async Task<MyConfigModel?> LoadConfigAsync(string path, string fileName)
{
return await _loadJsonService.LoadDataFromJsonFile<MyConfigModel>(path, fileName);
}
}
public class MyConfigCreator
{
private readonly ICreateJSONService _createJsonService;
public MyConfigCreator(ICreateJSONService createJsonService)
{
_createJsonService = createJsonService;
}
public async Task CreateConfigAsync(string path, string fileName, MyConfigModel config)
{
await _createJsonService.CreateJsonFileAsync(path, fileName, config);
}
}
public class MyConverter
{
private readonly IConvertToJSONService _convertToJsonService;
public MyConverter(IConvertToJSONService convertToJsonService)
{
_convertToJsonService = convertToJsonService;
}
public async Task<string?> ConvertFileAsync(string filePath)
{
return await _convertToJsonService.ConvertFileToJsonAsync(filePath);
}
public async Task<string?> ConvertTextAsync(string text)
{
return await _convertToJsonService.ConvertTextToJsonInMemoryAsync(text);
}
}
public class MyConfigUpdater
{
private readonly IUpdateJSONService _updateJsonService;
public MyConfigUpdater(IUpdateJSONService updateJsonService)
{
_updateJsonService = updateJsonService;
}
// Merge new data into existing JSON file
public async Task UpdateConfigAsync(string path, string fileName, MyConfigModel newData)
{
await _updateJsonService.UpdateJsonFileAsync(path, fileName, newData);
}
// Overwrite JSON file with new data
public async Task OverwriteConfigAsync(string path, string fileName, MyConfigModel newData)
{
await _updateJsonService.OverwriteJsonFileAsync(path, fileName, newData);
}
// Add new data to JSON file (adds new keys only)
public async Task AddToConfigAsync(string path, string fileName, MyConfigModel newData)
{
await _updateJsonService.AddToJSONFileAsync(path, fileName, newData);
}
}
public class MyXmlLoader
{
private readonly ILoadXmlService _loadXmlService;
public MyXmlLoader(ILoadXmlService loadXmlService)
{
_loadXmlService = loadXmlService;
}
// Load and deserialize an XML file
public async Task<MyConfigModel?> LoadConfigAsync(string path, string fileName)
{
return await _loadXmlService.LoadDataFromXmlFile<MyConfigModel>(path, fileName);
}
// Load descendants matching an attribute and value
public async Task<List<XElement>?> LoadDescendantsAsync(string path, string parent, string searchAttribute, string searchValue)
{
return await _loadXmlService.LoadDescendants(path, parent, searchAttribute, searchValue);
}
// Load attributes matching an attribute name
public async Task<List<XAttribute>?> LoadElementsAsync(string path, string parent, string searchAttribute)
{
return await _loadXmlService.LoadElements(path, parent, searchAttribute);
}
}
// Create XML file
await _createXmlService.CreateXmlFileAsync(path, fileName, config);
// Convert file to XML
string? xmlPath = await _convertToXmlService.ConvertFileToXmlAsync(filePath);
// Convert text to XML in memory
string? xml = await _convertToXmlService.ConvertTextToXmlInMemoryAsync(text);
// Update (merge), overwrite, or add to XML file
await _updateXmlService.UpdateXmlFileAsync(path, fileName, data);
await _updateXmlService.OverwriteXmlFileAsync(path, fileName, data);
await _updateXmlService.AddToXmlFileAsync(path, fileName, data);
public class MyYamlLoader
{
private readonly ILoadYAMLService _loadYamlService;
public MyYamlLoader(ILoadYAMLService loadYamlService)
{
_loadYamlService = loadYamlService;
}
public async Task<MyConfigModel?> LoadConfigAsync(string path, string fileName)
{
return await _loadYamlService.LoadDataFromYamlFile<MyConfigModel>(path, fileName);
}
}
// Create YAML file
await _createYamlService.CreateYamlFileAsync(path, fileName, config);
// Convert file to YAML
string? yamlPath = await _convertToYamlService.ConvertFileToYamlAsync(filePath);
// Convert text to YAML in memory
string? yaml = await _convertToYamlService.ConvertTextToYamlInMemoryAsync(text);
// Update (merge), overwrite, or add to YAML file
await _updateYamlService.UpdateYamlFileAsync(path, fileName, data);
await _updateYamlService.OverwriteYamlFileAsync(path, fileName, data);
await _updateYamlService.AddToYAMLFileAsync(path, fileName, data);
MIT License
In addition to the typed Service-style API (ILoadJsonService<T> etc.), the package ships YAML and XML IConfigurationProvider implementations for Microsoft.Extensions.Configuration. Register them on an IConfigurationBuilder and bind to IOptions<T> / IOptionsMonitor<T> per the standard framework pattern.
using Davasorus.Utility.DotNet.Config.Configuration.Providers;
using Microsoft.Extensions.Configuration;
var config = new ConfigurationBuilder()
.AddYamlFile("appsettings.yaml", optional: false, reloadOnChange: true)
.AddXmlFile("legacy.xml", optional: true)
.Build();
Both extensions accept absolute or relative paths. reloadOnChange: true is handled by Microsoft's FileConfigurationProvider base class — when the file changes, the provider re-invokes Load(Stream), fires IConfiguration.GetReloadToken(), and IOptionsMonitor<T> listeners receive the new values.
Both providers project parsed content through JsonNode (via the package's existing YamlJsonBridge / XmlJsonBridge) and then flatten the tree per Microsoft.Extensions.Configuration.Json conventions:
Db:Host, Db:Port.Servers:0, Servers:1.null leaves are preserved as keys with null values.One deliberate divergence from Microsoft conventions: boolean scalars are emitted as "True" / "False" (matching bool.ToString()) rather than the JSON literal forms "true" / "false". IConfiguration.GetValue<bool>(...) accepts both casings on the read path, so this only affects raw inspection of the flattened map (e.g., logging or CLI dumps), where capitalized values are friendlier for human eyes.
The XML provider uses the same conventions as the package's XmlJsonBridge. The XML root element's local name becomes the top-level key, so <Root><Host>x</Host></Root> flattens to Root:Host = "x" (not just Host = "x"). This matches the rest of the package's XML handling and gives JSON-compatible binding behavior.
Consumers using the typed ILoadYamlService<T> / ILoadXmlService<T> / ILoadJsonService<T> API (rather than IConfiguration) can subscribe to file-change events via IConfigFileWatcher:
using Davasorus.Utility.DotNet.Config;
using var watcher = ConfigFileWatcherFactory.Create("config.yaml");
watcher.Changed += async (_, e) =>
{
var fresh = await loadYamlService.LoadDataFromYamlFileAsync<MyConfig>(
e.FilePath,
CancellationToken.None);
// hand-off to caller's reload logic
};
The watcher is consumer-owned: dispose it (e.g., via using) when no longer needed. Failing to dispose leaks the underlying FileSystemWatcher native handle. The watcher is constructed via the static ConfigFileWatcherFactory.Create(filePath) rather than DI registration so the lifetime contract is explicit — DI containers don't always dispose Singletons cleanly on shutdown, and the right lifetime for a watcher is almost always tied to a specific consumer-side component rather than application-global.
IConfiguration providerMicrosoft.Extensions.Configuration.Json is canonical for JSON. This package does not ship an AddJsonFile extension; use Microsoft's. The package's value-add for JSON is on the write path — merge semantics, atomic writes, schema-friendly serialization — exposed via ILoadJsonService<T> / IUpdateJsonService<T> / ICreateJsonService<T>, all of which are unaffected.
Schema validation is JSON-only.
WithSchema<T>(JsonSchema)registers a JSON Schema (Json.Schema.JsonSchemafrom JsonSchema.Net). Validation runs only on JSON Load operations. XML, YAML, INI, and TOML Load services do not validate registered schemas; format conversion through JSON intermediate (e.g.ConvertToJsonfrom a YAML source) does validate against any registered schema for the target type.
When a JSON Schema is registered for a type, every Load / Create / Update operation that targets that type runs the data through the schema before binding (read) or writing (write). Validation failure throws ConfigSchemaException; on the write path, the throw happens before AtomicConfigWriter is invoked, so failed validation has zero filesystem side effects.
using Davasorus.Utility.DotNet.Config.Configuration;
using Json.Schema;
var schema = JsonSchema.FromFile("my-config.schema.json");
services.AddConfigServices(config => config
.AddYamlServices()
.WithSchema<MyConfig>(schema));
WithSchema<T>(JsonSchema) is type-keyed: every Load / Create / Update operation that targets MyConfig validates against the registered schema. Multiple types are supported by chaining WithSchema<T1> / WithSchema<T2> / etc.
When a schema is registered for T, the Service routes the data through a JsonNode intermediate, validates, then binds to T. When no schema is registered, the existing fast stream-first path runs unchanged — non-validating consumers see no performance regression. The Group C tripwire (LoadJsonPathFacadeDoesNotMaterialiseStringContent) continues to enforce stream-first behavior on the no-schema path.
When a schema is registered for T and the write operation is Create / Update / Add / Overwrite, the Service serializes the data to a JsonNode (and for Update, merges with the existing file's content via RFC 7396), validates the result, and only then hands the bytes to AtomicConfigWriter. Validation failure throws ConfigSchemaException before any filesystem work — the directory listing is unchanged.
For ad-hoc cases (validating in-memory data, multi-error aggregation, tooling scenarios), inject IConfigSchemaValidator and call its Validate method:
public class MyTool(IConfigSchemaValidator validator)
{
public void Check(JsonNode data, JsonSchema schema)
{
var result = validator.Validate(data, schema);
if (!result.IsValid)
{
foreach (var failure in result.Failures)
{
Console.WriteLine($"{failure.InstancePath}: {failure.Message}");
}
}
}
}
The standalone validator returns a SchemaValidationResult rather than throwing — useful for aggregating many errors.
XmlJsonBridge projects XML through a single-key root object: <Person><Name>Alice</Name></Person> becomes {"Person": {"Name": "Alice"}}. Schemas for XML must account for this wrapping. For the Person example, the schema would be:
{
"type": "object",
"properties": {
"Person": {
"type": "object",
"properties": { "Name": { "type": "string" } },
"required": ["Name"]
}
},
"required": ["Person"]
}
YamlJsonBridge serializes YAML using CamelCaseNamingConvention. The Service's write-path projection serializes patch data with a matching JsonNamingPolicy.CamelCase policy so the merge sees the same keys for the same logical fields. Schemas for YAML should use the camelCase form (name rather than Name) — this matches what YamlJsonBridge.YamlToJsonNode produces from a typical YAML file.
ConvertTo*Service for JSON / XML / YAML): the Convert API is type-erased (the methods take/return string or Stream, no T parameter), so the type-keyed registration model has nothing to look up. Convert validation is future-work if a typed-Convert API surfaces.T: nothing to key by.IConfiguration providers (AddYamlFile / AddXmlFile from D-A): validation is not currently wired. Future-work; the providers already produce a JsonNode so the engine reuse is free, but the registration shape needs more thinking since IConfiguration doesn't have a T.XmlJsonBridge to JSON and applying JSON Schema. XSD is future-work if a real consumer surfaces.Schema validation is built on JsonSchema.Net, which uses reflection. The schema-related public surface is attributed [RequiresUnreferencedCode] / [RequiresDynamicCode]. Consumers building with <PublishAot>true</PublishAot> who use WithSchema<T> get analyzer warnings (intentional). Consumers who never touch schema validation see no AOT analyzer warnings — the non-validating Load / Write paths stay reflection-free.
Group B refactors the package's architecture without changing the package's high-level capabilities. Three behaviour changes affect consumers:
IConfigErrorReporter; SQS is auto-detectedISqsService is no longer a constructor parameter on any Service or Client.
All cross-cutting error reporting goes through the new IConfigErrorReporter
abstraction. Consumers who want to swap in a different reporting sink
(Application Insights, file logger, no-op for tests, etc.) register their
own implementation as IConfigErrorReporter in DI; the package picks it up.
The default registration is automatic and depends on what's already in your DI container:
ISqsService is registered before AddJsonConfigServices /
AddXmlConfigServices / AddYamlConfigServices / AddAllConfigServices
/ AddConfigServices(...) runs, the package registers
SqsConfigErrorReporter as the default — preserves the pre-Group-B
Trello-via-SQS behaviour.NullConfigErrorReporter (no-op) as
the default.In both cases the registration uses TryAddSingleton, so a host-registered
reporter still wins.
Hosts that already had ISqsService registered before Group B keep getting
SQS reporting without changing any DI wiring code. Hosts that want SQS
reporting but are NOT going through one of the Add*ConfigServices
extensions can call services.AddConfigSqsErrorReporter() directly.
CancellationToken cancellationToken = defaultSource-compatible at call-sites that omit the parameter; binary-incompatible (recompile against the new assembly).
(string fullPath)The most visible breaking change. Each Client's path-based method (Load,
Create, Update, Convert × JSON / XML / YAML) now takes a single
string fullPath argument instead of (string filePath, string fileName).
The two-argument shape stays on Service interfaces. With Group A's
PathSafety.ResolveSafePath validating in the Service layer, the Client
receives a pre-validated, pre-combined full path.
Code that calls Service interfaces (the recommended pattern) is unaffected. Code that calls Client interfaces directly must update:
// Before
client.LoadDataFromJsonFile(filePath, fileName);
// After
client.LoadDataFromJsonFileAsync(Path.Combine(filePath, fileName), cancellationToken);
Each Client also gains parallel Stream overloads on Load / Create / Convert (Update remains path-only). Net-additive surface — no source-compat impact on the Stream side.
Group A introduced YamlJsonBridge and routed YAML Convert and YAML
Update through it, but YAML Load and YAML Create still used
YamlDotNet.Serialization.IDeserializer / ISerializer directly with
the camelCase naming convention. Group B finishes the unification — all
four YAML operations now go through YamlJsonBridge + JsonSerializer
with JsonSafety.DefaultReadOptions / DefaultWriteOptions.
Two consumer-visible effects:
Name: … → C# Name property) now succeed.Name: Test instead of
name: Test. Round-tripping through YAML Load works because of the
case-insensitive deserialize, but external tooling that reads the
YAML directly (linters, yq, other deserializers) sees the new
casing. This change brings YAML Create in line with what YAML Convert
and YAML Update already produce after Group A.The behaviour is now consistent across the four YAML operations and matches how the JSON stack handles property names.
Group C lands two correctness/observability changes and a logging refactor. None of them change public types, DI surface, or method signatures.
Create / Update / Add / Overwrite / Convert path facades for JSON / XML / YAML route through a temp + fsync + rename helper. The window where a crash could leave a config file partially written is gone — observers always see either the old file or the new file, never partial.
External processes (antivirus, sync clients, file watchers) may briefly
see *.{guid}.tmp files in the same directory as the target while a write
is in progress. The Guid suffix prevents collisions across concurrent
writers; the temp file is deleted on success or on cleanup if the rename
fails.
The synchronous fsync adds ~1ms to each write — invisible for typical
config writes (rare, small).
If an Update / Create / Overwrite / Convert produces bytes that match
what's already on disk, the write is skipped entirely. LastWriteTime
does not change; downstream filesystem watchers do not fire.
This is the new default. Consumers who need to force a LastWriteTime
bump (e.g. as a heartbeat for a process-of-record that polls mtime) should
call File.SetLastWriteTimeUtc(path, DateTime.UtcNow) directly — that's
the right tool for the job.
When a skip happens, the package emits an EventId 1201
(Config write skipped: content unchanged for '...') at Debug level.
Operators investigating "I edited the config but nothing reloaded" can
filter on that EventId.
The atomic temp + rename pattern replaces the file (and its ACL) on every
real write. Inherited-from-directory ACLs work as before; explicit per-file
ACLs are cleared. If a real consumer needs ACL preservation, file an issue
— the helper has a clean place to add a preserveAcl knob.
| Range | Domain |
|---|---|
| 1000-1099 | Group A — correctness events |
| 1100-1199 | Group B — operation events |
| 1200-1299 | Group C — performance / write events |
| 1300+ | Group D — feature events (reserved) |
Documented in the ConfigLog class XML comment. Operators get filterable
events; Group D's analyzer pack (CA1848) will lock in the convention going
forward.
Group F renames the package's C# namespace from Tyler.Utility.DotNet.Config.*
to Davasorus.Utility.DotNet.Config.* and converts all all-caps acronym
folders, types, and files (JSON/XML/YAML) to PascalCase
(Json/Xml/Yaml). No behaviour changes.
using directivesEvery consumer reference of the form
using Tyler.Utility.DotNet.Config.<...>;
becomes
using Davasorus.Utility.DotNet.Config.<...>;
Mechanical find/replace across consumer code. The package-id (NuGet
install) was already Davasorus.Utility.DotNet.Config; this aligns the
C# namespace with the package brand.
The all-caps JSON, XML, and YAML segments in type and method names
are now PascalCase: Json, Xml, Yaml. Examples:
| Before | After |
|---|---|
ILoadJSONService |
ILoadJsonService |
LoadJSONClient |
LoadJsonClient |
IUpdateXMLClient |
IUpdateXmlClient |
ConvertToYAMLService |
ConvertToYamlService |
XML types that were already PascalCase (ILoadXmlService, LoadXmlService)
are unchanged. Method names that were already correct
(UpdateJsonFileAsync, OverwriteYamlFileAsync) are unchanged.
Mechanical find/replace; the C# compiler surfaces every breakage with a clear "could not find type" error pointing at the call site.
Stepping into the package via SourceLink now lands in Json/Load/Client/
instead of JSON/Load/Client/. Cosmetic — no semantic effect. Stack
traces from production errors show the new paths.
If consumer code uses Type.GetType or Assembly.GetType with literal
strings like "Tyler.Utility.DotNet.Config.JSON.Load.Service.LoadJSONService",
those return null after the rename. Update the strings to the new
namespace and casing. (Most consumers don't do this; [JsonDerivedType]
or other discriminator-based polymorphism uses consumer-controlled
strings, not full type names.)
Group D-A adds IConfiguration providers for YAML and XML and an IConfigFileWatcher for typed-Service-API consumers. All additions are net-new types; no existing API changes. There is, however, one consumer-facing wrinkle worth flagging.
AddXmlFile ambiguity with Microsoft.Extensions.Configuration.XmlMicrosoft.Extensions.Configuration.Xml.XmlConfigurationExtensions ships with the .NET runtime and exposes its own AddXmlFile(...) extension. Critically, that static class lives in the Microsoft.Extensions.Configuration namespace (NOT Microsoft.Extensions.Configuration.Xml), and it is transitively pulled in via Microsoft.Extensions.Configuration.FileExtensions — which this package now depends on.
Result: as soon as a consumer file imports both Microsoft.Extensions.Configuration AND Davasorus.Utility.DotNet.Config.Configuration.Providers, calls to builder.AddXmlFile(...) become ambiguous and the C# compiler reports CS0104.
Disambiguation options — pick whichever fits your codebase style:
// Option A: type alias
using XmlConfigurationExtensions = Davasorus.Utility.DotNet.Config.Configuration.Providers.XmlConfigurationExtensions;
XmlConfigurationExtensions.AddXmlFile(builder, "config.xml");
// Option B: fully-qualified static call
Davasorus.Utility.DotNet.Config.Configuration.Providers.XmlConfigurationExtensions.AddXmlFile(builder, "config.xml");
// Option C: drop one of the conflicting using directives in this file
// (e.g., remove `using Microsoft.Extensions.Configuration;` if you don't need
// IConfigurationBuilder etc. by short name in this particular file)
AddYamlFile does not have this problem — Microsoft does not ship a YAML IConfiguration provider.
Microsoft.Extensions.Configuration.XmlThe two AddXmlFile implementations have distinct binding conventions:
XmlJsonBridge for JSON-compatible binding (attributes prefixed @, mixed text under #text, same-named siblings become arrays, root element's local name becomes the top-level key). XXE protection is inherited from the package's hardened reader settings.Choose one or the other for a given consumer; mixing them in the same IConfigurationBuilder is technically possible but not recommended.
IConfigFileWatcher is the package's only new IDisposableThe architectural-invariants tripwire NoServiceOrClientImplementsIDisposable continues to enforce that no *Service or *Client class implements IDisposable. PhysicalConfigFileWatcher is intentionally outside the tripwire's scope (different name pattern); it owns a native FileSystemWatcher handle and IDisposable on it is honest, not cargo-culted.
EventIds 1300–1310 are reserved for D-A telemetry. The current implementation does not allocate any of these — providers and the watcher do not log at the package level — but the reservation stands so future incremental additions stay in the documented range.
Group D-B adds opt-in schema validation for JSON / YAML / XML Loads, Creates, and Updates. All additions are net-new types; no existing API changes for consumers who don't register a schema.
ISchemaValidationGateEvery format Service constructor (LoadJsonService, UpdateYamlService, CreateXmlService, etc.) gains a new public-interface parameter. DI consumers see no change — DI resolves the new parameter automatically. Direct-construction consumers (rare; not the supported registration model) get a compile error and must add the parameter, which is registered as Scoped<ISchemaValidationGate> by AddConfigServices and the Add*ConfigServices extensions.
The ISchemaValidationGate interface itself is public (the concrete SchemaValidationGate is internal sealed). The interface had to be public for the public Service constructor parameter to compile (CS0051 inconsistent accessibility). The implementation lives behind it; consumers should not new up SchemaValidationGate directly — DI provides it.
ConfigSchemaException joins ConfigExceptionFilters.PolicyAndValidationConsumers catching base Exception see no change. Consumers with custom catch handlers may want to add ConfigSchemaException to their handler matrices. The exception's Result property carries the structured failure list (SchemaValidationResult.Failures — list of SchemaValidationFailure(InstancePath, SchemaPath, Message)).
SchemaValidationFailedSchema-validation failures log a Warning at EventId 1310 before throwing. The Group B OperationFailed (1101) entry continues to fire as well, since the throw propagates up through ConfigOperation.RunAsync. EventIds 1311–1399 are reserved for future D-B telemetry expansions.
JsonSchema.Net is a new transitive dependencyPure System.Text.Json-based, single transitive package, MIT-licensed. Consumers who never touch schema validation pay only the binary download cost (a few hundred KB). Consumers using WithSchema<T> or any of the schema-related public types build against JsonSchema.Net's public API directly.
Json.Schema.SchemaRegistry ships in JsonSchema.Net and collides at compile time with this package's internal SchemaRegistry. Internal collision is invisible to consumers, but tests / tooling that import both Davasorus.Utility.DotNet.Config.Internal and Json.Schema need a using alias to disambiguate.[RequiresUnreferencedCode] / [RequiresDynamicCode] are not valid on interface declarations (CS0592) — the attributes live on the methods that actually trigger reflection.The IConvertTo*Service API is type-erased (no T parameter on Convert methods). Type-keyed schema registration has nothing to look up. Convert validation is future-work if a typed-Convert API surfaces. Consumers needing validation on Convert output can pipe the Convert result through a separate IConfigSchemaValidator.Validate call.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on Davasorus.Utility.DotNet.Config:
| Package | Downloads |
|---|---|
|
SA.OpenSearchTool.Business
Package Description |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2026.2.3.4 | 88 | 6/13/2026 |
| 2026.2.3.3 | 95 | 6/3/2026 |
| 2026.2.3.2 | 93 | 6/2/2026 |
| 2026.2.3.1 | 97 | 6/1/2026 |
| 2026.2.2.18 | 96 | 5/31/2026 |
| 2026.2.2.17 | 97 | 5/26/2026 |
| 2026.2.2.15 | 96 | 5/23/2026 |
| 2026.2.2.14 | 96 | 5/17/2026 |
| 2026.2.2.13 | 91 | 5/15/2026 |
| 2026.2.2.12 | 112 | 5/10/2026 |
| 2026.2.2.11 | 105 | 5/6/2026 |
| 2026.2.2.10 | 100 | 5/5/2026 |
| 2026.2.2.9 | 98 | 5/5/2026 |
| 2026.2.2.8 | 105 | 5/4/2026 |
| 2026.2.2.7 | 90 | 5/4/2026 |
| 2026.2.2.6 | 102 | 5/3/2026 |
| 2026.2.2.5 | 97 | 5/2/2026 |
| 2026.2.2.4 | 110 | 5/2/2026 |
| 2026.2.2.3 | 98 | 5/2/2026 |
| 2026.2.2.2 | 96 | 5/1/2026 |