ND.FW.DataValidation
1.0.1
dotnet add package ND.FW.DataValidation --version 1.0.1
NuGet\Install-Package ND.FW.DataValidation -Version 1.0.1
<PackageReference Include="ND.FW.DataValidation" Version="1.0.1" />
<PackageVersion Include="ND.FW.DataValidation" Version="1.0.1" />
<PackageReference Include="ND.FW.DataValidation" />
paket add ND.FW.DataValidation --version 1.0.1
#r "nuget: ND.FW.DataValidation, 1.0.1"
#:package ND.FW.DataValidation@1.0.1
#addin nuget:?package=ND.FW.DataValidation&version=1.0.1
#tool nuget:?package=ND.FW.DataValidation&version=1.0.1
ND.FW.DataValidation
A configurable data-validation and metadata-derivation nuget, built on top of
ND.FW.RulesEngine. Everything
is driven by the same Rule JSON shape — no C# changes needed to add a validation
rule, switch rule providers, or add a metadata-derivation stage.
Install
dotnet add package ND.FW.DataValidation
The two halves
| Half | Entry point | Input | Output |
|---|---|---|---|
| Validation | IDataValidationService.ValidateAsync |
one record (IDictionary<string,object?>) |
pass/fail per rule |
| Metadata derivation | IMetadataDerivationService.DeriveAsync |
many AttributeCandidates (multi-page, multi-source) |
one canonical record + evidence + conflicts |
Both read rules through the same IRuleRepository and the same Rule JSON shape —
a rule's RuleExpressionType decides which half consumes it. Validation types
(Required, Regex, NumberRange, DateValidation, Conditional, Lookup,
Triangulation, FuzzyMatch, Enrichment, Standardization, Normalization) map
to an ND.FW.RulesEngine workflow exactly as before. Metadata-derivation types
(AttributeMapping, CandidateGrouping, MultiPageConsolidation,
ConfidenceAggregation, CanonicalSelection, MultiValueConsolidation,
ConflictDetection, CrossFieldValidation) drive the consolidation pipeline
instead. You can mix both kinds of rules in the same rule set returned by a
provider — each service simply filters for the types it understands.
Quick start
builder.Services.AddDataValidation(builder.Configuration, options =>
{
// register any product-specific operators alongside the built-in base set
options.CustomOperatorTypes.Add(typeof(MyProduct.Operators));
});
// appsettings.json
{
"DataValidation": {
"RuleRepository": {
"UseDatabaseProvider": true,
"UseLocalJsonProvider": true,
"Mode": "Additive"
},
"Database": {
"Schema": "dbo",
"RuleDetailsSpName": "sp_Get_Rule_Details",
"RuleJsonColumnName": "Rule_Definition_JSON",
"ParameterMap": {
"FileTaxonomyId": "@p_File_Taxonomy_Id",
"DocTaxonomyId": "@p_Doc_Taxonomy_Id",
"FileDocTaxonomyId": "@p_File_Doc_Taxonomy_Id",
"FieldId": "@p_File_Taxonomy_X_Doc_Taxonomy_X_Field_Id",
"RuleEventStage": "@p_Rule_Event_Stage"
}
},
"LocalJson": {
"RootDirectory": "RuleSets",
"FileNameTemplate": "{DocTaxonomyId}.json"
}
}
}
Validation
var criteria = RuleQueryCriteria.FromKeys(
("FileTaxonomyId", 12),
("DocTaxonomyId", 4),
("FieldId", 101));
var input = new Dictionary<string, object?> { ["phone"] = "+919876543210", ["pan"] = "ABCDE1234F" };
var result = await dataValidationService.ValidateAsync(criteria, input);
foreach (var r in result.RuleResults)
Console.WriteLine($"{r.RuleName}: {(r.IsSuccess ? "PASS" : $"FAIL - {r.ErrorMessage}")}");
Metadata derivation
var candidates = new List<AttributeCandidate>
{
new() { AttributeName = "PT_NAME_1", Value = "John A Smith", Page = 1, Position = 0, ValueStrength = 0.81, Source = "ocr" },
new() { AttributeName = "patient full name", Value = "John Smith", Page = 3, Position = 2, ValueStrength = 0.93, Source = "model-x" },
new() { AttributeName = "PhoneNumber", Value = "555-0100", Page = 1, ValueStrength = 0.7 },
new() { AttributeName = "PhoneNumber", Value = "555-0199", Page = 2, ValueStrength = 0.6 },
};
var result = await metadataDerivationService.DeriveAsync(criteria, candidates);
var patientName = result.Attributes["PatientName"]; // canonical value + evidence + confidence
foreach (var conflict in result.Conflicts)
Console.WriteLine($"Conflict on {conflict.AttributeName}: {conflict.ConflictingValues.Count} candidate values");
Rule providers — multiple, at the same time
IRuleRepository has two built-in implementations:
DatabaseRuleRepository— calls a stored procedure. SP name, schema, and the mapping fromRuleQueryCriteria.Keysto SP parameter names are all configuration (DatabaseRuleProviderOptions), not hardcoded — so this provider works for any product's own key shape, not just taxonomy IDs.LocalJsonRuleRepository— reads rule JSON from local files (or inline configuration). Accepts a single Rule object, a Rule array, or a{ "Rules": [...] }wrapper per file.
When both are enabled, CompositeRuleRepository merges their results:
Additive(default) — rules from every enabled provider are combined, de-duplicated byRuleName(earliest-registered provider wins on a collision).Fallback— providers are tried in order; the first one that returns any rules wins and the rest are skipped.
Add a third provider (e.g. a remote config service) by implementing
IRuleRepository and registering it — CompositeRuleRepository accepts any
IEnumerable<IRuleRepository>.
Metadata-derivation pipeline
Stages run in a fixed logical order, but only the stages whose
RuleExpressionType is present in the resolved rule set actually run — a rule
set with just CandidateGrouping + CanonicalSelection rules skips multi-page
consolidation and cross-field validation entirely.
AttributeMapping → CandidateGrouping → MultiPageConsolidation →
ConfidenceAggregation → CanonicalSelection → MultiValueConsolidation →
ConflictDetection → CrossFieldValidation
| Stage | RuleExpressionType | What it does |
|---|---|---|
| Attribute name mapping | AttributeMapping |
Renames raw extracted names onto one canonical name, via AttributeNameMap. |
| Candidate grouping | CandidateGrouping |
Groups candidates by (mapped) attribute name, optionally narrowed by GroupByKeys. |
| Multi-page consolidation | MultiPageConsolidation |
Orders each group by page/position per PageOrderStrategy. |
| Confidence aggregation | ConfidenceAggregation |
Scores each candidate from key/position/value-strength via ConfidenceWeights. |
| Canonical value selection | CanonicalSelection |
Picks the winning value per attribute (or ranks candidates for multi-value attributes), via ConflictResolutionStrategy. |
| Multi-value consolidation | MultiValueConsolidation |
For attributes with IsMultiValue: true, keeps a de-duplicated, ranked list capped at MaxValues. |
| Conflict detection | ConflictDetection |
Flags attributes whose top candidates differ by less than ConflictThreshold. |
| Cross-field validation | CrossFieldValidation |
Runs an ND.FW.RulesEngine expression (Condition) against the canonical record — reuses the same Operators.* as ordinary validation. |
Note:
ToCanonicalDictionary()exposes multi-value attributes (IsMultiValue: true) as aList<object?>, not a scalar. ACrossFieldValidationexpression referencing such an attribute must treat it as a list (e.g.((List<object?>)input1["PhoneNumber"]).Count > 0) rather than comparing it directly —ND.FW.RulesEnginepasses the value through unchanged and does not coerce it to a string or number.
Every canonical attribute retains full evidence (EvidenceRef: value, page,
position, source location, source, and computed weight) for every candidate that
contributed to it, whether it won or lost — satisfying audit/traceability needs
without any extra configuration.
Extending with a custom stage
Metadata-derivation stages are just another named plugin point, exactly like
ND.FW.RulesEngine's INdContextPlugin:
public sealed class MyCustomStage : IMetadataDerivationStage
{
public string RuleExpressionType => "MyCustomStageType";
public Task ExecuteAsync(Rule rule, MetadataDerivationContext context, CancellationToken ct) { ... }
}
services.AddDataValidation(configuration, options =>
{
options.MetadataDerivationStageTypes.Add(typeof(MyCustomStage));
});
Rule JSON compatibility
Existing rule rows/files are not restructured. Every new capability is added by:
- New
RuleExpressionTypestring values (data, not schema). - A small number of new, optional fields on
RuleExpression—AttributeNameMap,GroupByKeys,ConfidenceWeights,ConflictResolutionStrategy,ConflictThreshold,IsMultiValue,MaxValues,PageOrderStrategy,CanonicalFields. All nullable; absent in existing rows and simply unused by validation rule types. - Reuse of one existing field,
Condition, as the raw cross-field expression string forCrossFieldValidationrules (this field already existed onRuleExpressionand was previously unused by any mapped type).
No existing field was renamed, retyped, or removed.
Packaging note
ND.FW.DataValidation references ND.FW.RDBM / Microsoft.Data.SqlClient directly
so DatabaseRuleRepository is available out of the box, matching how DVS already
depends on ND.FW.RDBM. If a consumer only ever wants the local-JSON provider (no
database dependency at all — e.g. an edge/offline scenario), the cleaner long-term
shape is to split this into ND.FW.DataValidation.Core (abstractions, local JSON,
metadata derivation) + ND.FW.DataValidation.SqlServer (the database provider) so
the SQL dependency is opt-in per package reference rather than always-transitive.
That split is a bigger packaging change than this version takes on; call it out if
a consumer without SQL Server ever needs this nuget.
License
Internal use.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- ND.FW.RDBM (>= 0.0.25)
- ND.FW.RulesEngine (>= 1.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.