BeneficialStrategies.Iso20022.FluentValidation
0.6.2-alpha
dotnet add package BeneficialStrategies.Iso20022.FluentValidation --version 0.6.2-alpha
NuGet\Install-Package BeneficialStrategies.Iso20022.FluentValidation -Version 0.6.2-alpha
<PackageReference Include="BeneficialStrategies.Iso20022.FluentValidation" Version="0.6.2-alpha" />
<PackageVersion Include="BeneficialStrategies.Iso20022.FluentValidation" Version="0.6.2-alpha" />
<PackageReference Include="BeneficialStrategies.Iso20022.FluentValidation" />
paket add BeneficialStrategies.Iso20022.FluentValidation --version 0.6.2-alpha
#r "nuget: BeneficialStrategies.Iso20022.FluentValidation, 0.6.2-alpha"
#:package BeneficialStrategies.Iso20022.FluentValidation@0.6.2-alpha
#addin nuget:?package=BeneficialStrategies.Iso20022.FluentValidation&version=0.6.2-alpha&prerelease
#tool nuget:?package=BeneficialStrategies.Iso20022.FluentValidation&version=0.6.2-alpha&prerelease
Beneficial Strategies ISO20022 FluentValidation Library
This project contains FluentValidation validators for the message domain model published in BeneficialStrategies.Iso20022 — 1,296 validators covering top-level messages, message components, choice types, and external code sets, generated from and cross-checked against the ISO 20022 specification.
Welcome!
BeneficialStrategies.Iso20022 gives you a strongly-typed, compiler-enforced rendering of ISO 20022 messages in memory — but the C# type system can only express so much. Some ISO 20022 rules are cross-field ("a case identification may appear in at most one of three possible locations") or depend on runtime data the compiler can't see. This package is the follow-on project that covers that ground: validators that check field-level constraints and cross-field business rules the record types themselves cannot enforce.
Coverage today:
391 validators — full ISO 20022 spec-compliance coverage (field-level constraints and cross-field rules), including 43 top-level messages validated completely, top to bottom, with zero exceptions anywhere in their reachable graph — the entire
pain(Payments Initiation) business area is 100% complete (12/12 addressable message families; two additional legacy IDs,PaymentCancellationRequestV01andPaymentStatusReportV02, are permanently out of scope — see the note below the table), and the entirepacs(Payments Clearing and Settlement) business area is 100% complete (9/9 addressable message families:MultilateralSettlementRequestV02,FinancialInstitutionDirectDebitV06,FIToFIPaymentStatusRequestV07,FIToFIPaymentReversalV14,FIToFIPaymentStatusReportV16,FIToFICustomerCreditTransferV14,FinancialInstitutionCreditTransferV13,FIToFICustomerDirectDebitV12,PaymentReturnV15). This list only grows, so it's kept here as a table, sorted by ISO ID:ISO ID Message C# type Description camt.012.001.08 Delete Limit DeleteLimitV08Sent by a member to the transaction administrator to request the deletion of one, several, or all limits set by the member and managed by the transaction administrator. camt.016.001.04 Get Currency Exchange Rate GetCurrencyExchangeRateV04Sent by a member to the transaction administrator to request static data related to currency exchange details, by source and/or target currency. camt.020.001.04 Get General Business Information GetGeneralBusinessInformationV04Sent by a member to the transaction administrator to request the full content of a broadcast-type business information message previously sent. camt.024.001.08 Modify Standing Order ModifyStandingOrderV08Sent by a member to the transaction administrator to request a change in the features of a permanent funds-transfer order between two of its accounts. camt.030.001.06 Notification Of Case Assignment NotificationOfCaseAssignmentV06Sent by a case assignee to a case creator/case assigner to inform them of further action undertaken on the case (reassignment, or working it directly). camt.031.001.07 Reject Investigation RejectInvestigationV07Sent by a case assignee to a case creator or case assigner to reject a case given to it. camt.032.001.05 Cancel Case Assignment CancelCaseAssignmentV05Sent by a case creator or case assigner to a case assignee to request the cancellation of a case. camt.033.001.07 Request For Duplicate RequestForDuplicateV07Sent by the case assignee to the case creator or case assigner to request a copy of the original payment instruction considered in the case. camt.034.001.07 Duplicate DuplicateV07Sent in response to a RequestForDuplicate message, to exchange a duplicate payment instruction. camt.035.001.06 Proprietary Format Investigation ProprietaryFormatInvestigationV06Used, by bilateral agreement, as an envelope for a non-standard message managing an exception or investigation outside the scope of any other formatted message. camt.036.001.06 Debit Authorisation Response DebitAuthorisationResponseV06Sent by an account owner to its account servicing institution to approve or reject a debit authorisation request. camt.038.001.05 Case Status Report Request CaseStatusReportRequestV05Sent by a case creator or case assigner to a case assignee to request the status of a case. camt.039.001.06 Case Status Report CaseStatusReportV06Sent by a case assignee to a case creator or case assigner to report on the status of a case, in reply to a CaseStatusReportRequest message. camt.048.001.07 Modify Reservation ModifyReservationV07Used to request modifications in the details of one particular reservation set by the member and managed by the transaction administrator. camt.049.001.07 Delete Reservation DeleteReservationV07Used to request the deletion of one particular reservation by the member and managed by the transaction administrator. camt.050.001.07 Liquidity Credit Transfer LiquidityCreditTransferV07Sent by a member to the transaction administrator to request a transfer of funds between two accounts belonging to the same member or group of accounts. camt.051.001.07 Liquidity Debit Transfer LiquidityDebitTransferV07Sent by a member to the transaction administrator to request a transfer of funds between two accounts belonging to the same member or group of accounts. camt.063.001.02 Pay In Event Acknowledgement PayInEventAcknowledgementV02Sent by a participant of a central system to the central system to confirm a PayInSchedule or a PayInCall has been received. camt.071.001.05 Delete Standing Order DeleteStandingOrderV05Sent by the system member to delete one or more standing orders within the static data held by the system transaction administrator. camt.101.001.02 Create Limit CreateLimitV02Sent by a member to the transaction administrator to create one or several limits set by the member and managed by the transaction administrator. camt.102.001.03 Create Standing Order CreateStandingOrderV03Sent by a member to the transaction administrator to create a permanent order for the transfer of funds between two of its accounts. camt.103.001.03 Create Reservation CreateReservationV03Used to request the creation of one particular reservation by the member and managed by the transaction administrator. pacs.002.001.16 FI To FI Payment Status Report FIToFIPaymentStatusReportV16Sent by an instructed agent to the previous party in the payment chain, to inform them about the positive or negative status of an instruction, or to report on a pending instruction. pacs.003.001.12 FI To FI Customer Direct Debit FIToFICustomerDirectDebitV12Sent by the creditor agent to the debtor agent, directly or through other agents and/or a payment clearing and settlement system, to collect funds from a debtor account for a creditor. pacs.004.001.15 Payment Return PaymentReturnV15Sent by an agent to the previous agent in the payment chain to undo a payment previously settled. pacs.007.001.14 FI To FI Payment Reversal FIToFIPaymentReversalV14Sent by an agent to the next party in the payment chain, to reverse a payment previously executed and settled. pacs.008.001.14 FI To FI Customer Credit Transfer FIToFICustomerCreditTransferV14Sent by the debtor agent to the creditor agent, directly or through other agents and/or a payment clearing and settlement system, to move funds from a debtor account to a creditor. pacs.009.001.13 Financial Institution Credit Transfer FinancialInstitutionCreditTransferV13Sent by a debtor financial institution to a creditor financial institution, directly or through other agents and/or a payment clearing and settlement system, to move funds from a debtor account to a creditor, where both debtor and creditor are financial institutions. pacs.010.001.06 Financial Institution Direct Debit FinancialInstitutionDirectDebitV06Sent by an exchange or clearing house, or a financial institution, directly or through another agent, to the DebtorAgent, to move funds from one or more debtor(s) account(s) to one or more creditor(s), where both debtor and creditor are financial institutions. pacs.028.001.07 FI To FI Payment Status Request FIToFIPaymentStatusRequestV07Sent by the debtor agent to the creditor agent, directly or through other agents and/or a payment clearing and settlement system, to request a FIToFIPaymentStatusReport message containing information on the status of a previously sent instruction. pacs.029.001.02 Multilateral Settlement Request MultilateralSettlementRequestV02Sent from an instructing agent to a market infrastructure, to settle obligations between their participants using accounts held in a settlement service. pain.001.001.13 Customer Credit Transfer Initiation CustomerCreditTransferInitiationV13Sent by the initiating party to the forwarding agent or debtor agent, to request movement of funds from the debtor account to a creditor. pain.002.001.15 Customer Payment Status Report CustomerPaymentStatusReportV15Sent by an instructed agent to the previous party in the payment chain, to inform them about the positive or negative status of an instruction, or to report on a pending instruction. pain.007.001.13 Customer Payment Reversal CustomerPaymentReversalV13Sent by the initiating party to the next party in the payment chain, to reverse a payment previously executed. pain.008.001.12 Customer Direct Debit Initiation CustomerDirectDebitInitiationV12Sent by the initiating party to the forwarding agent or creditor agent, to request single or bulk collection(s) of funds from one or various debtor's account(s) for a creditor. pain.009.001.08 Mandate Initiation Request MandateInitiationRequestV08Sent by the initiator of a mandate request (debtor or creditor) to their agent, to set up the instruction that allows the debtor agent to accept debit instructions from the creditor agent. pain.010.001.08 Mandate Amendment Request MandateAmendmentRequestV08Sent by the initiator of the request (debtor or creditor) to their agent and/or counterparty to request the amendment of specific information in an existing mandate. pain.011.001.08 Mandate Cancellation Request MandateCancellationRequestV08Sent by the initiator of the request (debtor or creditor) to their agent to request the cancellation of an existing mandate. pain.012.001.08 Mandate Acceptance Report MandateAcceptanceReportV08Sent from the agent of the receiver of a mandate request to the agent of the initiator, to confirm the acceptance or rejection of that request. pain.013.001.12 Creditor Payment Activation Request CreditorPaymentActivationRequestV12Sent by the Creditor sending party to the Debtor receiving party, directly or through agents, to request movement of funds from the debtor account to a creditor. pain.014.001.12 Creditor Payment Activation Request Status Report CreditorPaymentActivationRequestStatusReportV12Sent by a party to the next party in the creditor payment activation request chain, to inform them about the positive or negative status of a creditor payment activation request. pain.017.001.04 Mandate Copy Request MandateCopyRequestV04Sent by the initiator of the request (debtor or creditor) to their agent to request a copy of an existing mandate. pain.018.001.04 Mandate Suspension Request MandateSuspensionRequestV04Sent by the initiator of the request (debtor, debtor agent, creditor, or creditor agent) to its agent to request the suspension of an existing mandate. (
FIToFIPaymentCancellationRequestV10, camt.056.001.10, has a validator too, but mechanical verification found gaps in its dependency graph — not yet complete enough to list above. See the FluentValidation project's ownCLAUDE.mdfor tracking.)(Two
painmessage IDs are intentionally absent above, not merely unbuilt:PaymentCancellationRequestV01(pain.006.001.01) andPaymentStatusReportV02(pain.002.001.02) are both superseded — per ISO 20022's ownnextVersionspointer, each was renamed decades ago into theCustomerXxxnaming convention.PaymentCancellationRequestV01's lineage continued asCustomerPaymentCancellationRequestV01→ ... →CustomerPaymentCancellationRequestV11, which now lives in thecamtbusiness area (camt.055.001.11) — permanently out ofpainscope.PaymentStatusReportV02's lineage continued asCustomerPaymentStatusReportV03→ ... →CustomerPaymentStatusReportV15, which stays inpainand is tracked under its current name instead. Neither of the two old IDs will ever be built under those names.)905 validators — abbreviated coverage. These currently only enforce a known minimum-collection-size gap (some
required-looking collections were generated with no lower bound, so an empty collection compiles but violates the spec); they have not yet been reviewed for the remaining field-level constraints and cross-field rules a full validator would cover.
Every validator — full or abbreviated — carries an XML doc <remarks> block naming exactly which spec constraints it currently checks, so you always know what you're getting from a specific type, not just its category.
Known limitations:
- No composite message-level validator yet for most business areas — validators exist per component (and per full message for the messages in the table above); you compose them yourself for now via
SetValidator()where needed. - Abbreviated validators (see above) are not yet a complete spec-compliance check.
For more information about the project, see the repository.
This is provided free of charge under a very non-restrictive license as a good-faith contribution to the community. This library is a working proof of concept built using Claude Code and the Beneficial Strategies ISO20022 MCP server.
If you have questions or concerns about the implementation, please send developer comments or questions to support@beneficialstrategies.com.
Trying out the library
mkdir test
cd test
dotnet new console
dotnet add package BeneficialStrategies.Iso20022 --version 0.6.1-alpha
dotnet add package BeneficialStrategies.Iso20022.FluentValidation --version 0.6.1-alpha
Open your Program.cs and paste the following. This validates camt.056.001.10 (FIToFIPaymentCancellationRequest) — one of the full-spec-compliance validators — first against a well-formed message, then against one that violates a cross-field rule no C# type could express on its own:
using BeneficialStrategies.Iso20022.camt;
using BeneficialStrategies.Iso20022.Components;
using BeneficialStrategies.Iso20022.Choices.Party40Choice;
using BeneficialStrategies.Iso20022.Validation.camt;
var validator = new FIToFIPaymentCancellationRequestV10Validator();
// A well-formed cancellation request.
var request = new FIToFIPaymentCancellationRequestV10
{
Assignment = new CaseAssignment5
{
Identification = "CASE-2026-0001",
Assigner = new Agent { FinancialInstitutionIdentification = new() { BICFI = "AAAAGB2L" } },
Assignee = new Agent { FinancialInstitutionIdentification = new() { BICFI = "BBBBUS33" } },
CreationDateTime = new DateTime(2026, 8, 17, 9, 0, 0),
},
Underlying = new UnderlyingTransaction28
{
TransactionInformation = [new PaymentTransaction137()],
},
};
var result = validator.Validate(request);
Console.WriteLine($"IsValid={result.IsValid}"); // True
// The same message, but with a case identification present at both the message level
// and the transaction level — a spec rule (MessageOrGroupCaseRule) that no property type
// alone can enforce, since both locations are individually optional.
var invalidRequest = request with
{
Case = new Case5
{
Identification = "CASE-2026-0001",
Creator = new Agent { FinancialInstitutionIdentification = new() { BICFI = "AAAAGB2L" } },
},
Underlying = new UnderlyingTransaction28
{
TransactionInformation =
[
new PaymentTransaction137
{
Case = new Case5
{
Identification = "CASE-2026-0001",
Creator = new Agent { FinancialInstitutionIdentification = new() { BICFI = "AAAAGB2L" } },
},
},
],
},
};
var invalidResult = validator.Validate(invalidRequest);
Console.WriteLine($"IsValid={invalidResult.IsValid}"); // False
foreach (var error in invalidResult.Errors)
{
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
}
// MessageOrGroupCaseRule: Case identification must appear in at most one location: message-level
// Case, Underlying.OriginalGroupInformationAndCancellation.Case, or
// Underlying.TransactionInformation.Case (MessageOrGroupCaseRule / MessageOrTransactionCaseRule).
Registering validators with dependency injection
Constructing one validator with new is fine for a quick check, but every validator in this
package composes its children via constructor injection — the two-constructor pattern you'll see
throughout the source (a DI constructor taking IValidator<T> for each child, and a parameterless
convenience constructor for exactly the kind of one-off use above). AddIso20022Validators()
registers all of them at once with a standard IServiceCollection:
using BeneficialStrategies.Iso20022.camt;
using BeneficialStrategies.Iso20022.Validation;
using BeneficialStrategies.Iso20022.Validation.camt;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddIso20022Validators();
using var provider = services.BuildServiceProvider();
var validator = provider.GetRequiredService<IValidator<CancelCaseAssignmentV05>>();
Registering every validator in the assembly is the simplest option, but not always the leanest — three narrower overloads are available when you know your scope up front:
// Only validators for a specific ISO 20022 business area (plus whatever shared component/choice
// validators those messages actually depend on — never fewer than that, so nothing breaks).
services.AddIso20022Validators(businessAreas: ["camt", "pain"]);
// The exact transitive closure one specific message needs — nothing more, nothing approximated.
// Computed via reflection over the validators' own constructors, not a namespace guess.
services.AddIso20022Validators(rootTypes: [typeof(CancelCaseAssignmentV05)]);
// A raw predicate over each concrete validator type, for anything the two above don't cover.
services.AddIso20022Validators(filter: validatorType => validatorType.Namespace!.EndsWith(".camt"));
rootTypes is the tightest of the three — it registers precisely what the given message(s) need,
computed by walking each validator's own dependency-injection constructor. It's the one to reach
for if you're only ever validating a known, fixed set of message types and want to avoid
registering the other 1,000+ validators in the assembly you'll never resolve.
Validating raw XML/JSON payloads (e.g. off a message queue)
The examples above validate an already-deserialized message record. Often what you actually have is raw XML or JSON text — off a queue, from an HTTP body — and you want deserialization failures to show up as ordinary validation errors too, not as a separate exception-handling path. Two building blocks cover this, depending on whether you know the message type up front:
IValidator<XmlEnvelope<TMessage>>/IValidator<JsonEnvelope<TMessage>>— when the type is known at the call site (e.g. a queue bound to one message type). Registered viaAddIso20022PayloadValidators(); behaves as literally just anotherIValidator<T>.IIso20022PayloadValidationDispatcher— when the payload could be any one of several message types, resolved from the payload itself. Registered viaAddIso20022PayloadValidationDispatcher().
Either way, a parse failure becomes a ValidationFailure in the result (ErrorCode values like
"XmlParseError", "JsonParseError", "UnknownMessageType"), never a thrown exception.
Example 1 — known type, JSON payload
A queue consumer bound to exactly one message type validates each message body as it arrives:
using BeneficialStrategies.Iso20022.pacs;
using BeneficialStrategies.Iso20022.Serialization;
using BeneficialStrategies.Iso20022.Validation;
using BeneficialStrategies.Iso20022.Validation.Payloads;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
public sealed class CreditTransferQueueHandler
{
private readonly IValidator<JsonEnvelope<FIToFICustomerCreditTransferV14>> _validator;
public CreditTransferQueueHandler(IValidator<JsonEnvelope<FIToFICustomerCreditTransferV14>> validator) =>
_validator = validator;
public void Handle(string messageBody)
{
var result = _validator.Validate(new JsonEnvelope<FIToFICustomerCreditTransferV14>(messageBody));
if (!result.IsValid)
{
foreach (var error in result.Errors)
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
return; // dead-letter, nack, etc. — whatever your queue client calls for. Covers
// malformed JSON (ErrorCode "JsonParseError") exactly like a business-rule
// violation — same list, same shape, no separate catch block needed.
}
// `result` proves the body is valid but, like any IValidator<T> result, doesn't hand the
// deserialized instance back — re-deserializing once, now that you know it's valid, is
// cheap and keeps "is this valid" and "give me the object" as separate concerns.
var message = Iso20022JsonSerializer.Deserialize<FIToFICustomerCreditTransferV14>(messageBody);
var firstTransfer = message.CreditTransferTransactionInformation[0];
Console.WriteLine($"Processing transfer {firstTransfer.PaymentIdentification.EndToEndIdentification}");
}
}
var services = new ServiceCollection();
services.AddIso20022Validators(rootTypes: [typeof(FIToFICustomerCreditTransferV14)]);
services.AddIso20022PayloadValidators();
services.AddSingleton<CreditTransferQueueHandler>();
using var provider = services.BuildServiceProvider();
var handler = provider.GetRequiredService<CreditTransferQueueHandler>();
// handler.Handle(messageBodyFromYourQueueClient);
Example 2 — type unknown up front, XML payload, one of several candidates
A queue that carries any of several related message types — here, three events in a payment's
lifecycle — resolved from each payload's own root document namespace, then dispatched by a
switch on the resolved type:
using BeneficialStrategies.Iso20022.pacs;
using BeneficialStrategies.Iso20022.Validation;
using BeneficialStrategies.Iso20022.Validation.Payloads;
using Microsoft.Extensions.DependencyInjection;
public sealed class PaymentLifecycleQueueHandler
{
private readonly IIso20022PayloadValidationDispatcher _dispatcher;
public PaymentLifecycleQueueHandler(IIso20022PayloadValidationDispatcher dispatcher) =>
_dispatcher = dispatcher;
public void Handle(string xmlBody)
{
var result = _dispatcher.ValidateXml(xmlBody);
if (!result.IsValid)
{
// Covers malformed XML, an unrecognized document namespace, and ordinary business-rule
// failures alike — all as ValidationFailure entries, none of them a thrown exception.
foreach (var error in result.ValidationResult.Errors)
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
return;
}
// result.Message is the deserialized instance, typed as `object` since the concrete type
// wasn't known until the payload was inspected — pattern-match it to each type you expect.
switch (result.Message)
{
case FIToFIPaymentStatusReportV16 statusReport:
Console.WriteLine(
$"Status report: {statusReport.TransactionInformationAndStatus.Count} transaction(s)"
);
break;
case FIToFIPaymentReversalV14 reversal:
Console.WriteLine($"Reversal request: {reversal.TransactionInformation.Count} transaction(s)");
break;
case PaymentReturnV15 paymentReturn:
Console.WriteLine($"Payment return: {paymentReturn.TransactionInformation.Count} transaction(s)");
break;
default:
// Only reachable if a validator for a fourth type got registered without a
// matching case being added here — rootTypes below scopes DI to exactly the
// three types this handler knows about, so nothing else should ever validate.
throw new InvalidOperationException($"Unhandled message type: {result.MessageType}");
}
}
}
var services = new ServiceCollection();
services.AddIso20022Validators(
rootTypes:
[
typeof(FIToFIPaymentStatusReportV16),
typeof(FIToFIPaymentReversalV14),
typeof(PaymentReturnV15),
]
);
services.AddIso20022PayloadValidationDispatcher();
services.AddSingleton<PaymentLifecycleQueueHandler>();
using var provider = services.BuildServiceProvider();
var handler = provider.GetRequiredService<PaymentLifecycleQueueHandler>();
// handler.Handle(xmlBodyFromYourQueueClient);
External code set validation
Some ISO 20022 fields — country codes, currency codes, and ISO 20022's own externally-maintained
code lists — are only checked for format by the type itself (CountryCode's constructor
enforces "two uppercase letters," for example, not "is an actual assigned country"). The
acceptable values for these live in a registry maintained outside this library — sometimes an
external standard like ISO 3166, sometimes your own reference-data table — and this package gives
you a pluggable way to check against it: IExternalCodeRegistry<TCode>, one per code set type,
defaulting to an in-memory implementation you populate.
AddIso20022Validators() already registers the in-memory default for every external code type in
one line — you don't need to do anything for validation to run, but by default an unpopulated code
set is permissive (nothing to check against, so nothing gets rejected on that basis). To
actually restrict CountryCode to a specific set of countries, construct a registry, populate it,
and register the instance:
using BeneficialStrategies.Iso20022.Codesets;
using BeneficialStrategies.Iso20022.Validation;
var countries = new InMemoryExternalCodeRegistry<CountryCode>();
countries.Add("US");
countries.Add("CA");
countries.Add("KP"); // added first, then...
countries.Remove("KP"); // ...reconsidered and taken back out.
services.AddIso20022Validators();
services.AddSingleton<IExternalCodeRegistry<CountryCode>>(countries);
using var provider = services.BuildServiceProvider();
var validator = provider.GetRequiredService<IValidator<CountryCode>>();
Console.WriteLine(validator.Validate((CountryCode)"US").IsValid); // True
Console.WriteLine(validator.Validate((CountryCode)"KP").IsValid); // False — removed above
Console.WriteLine(validator.Validate((CountryCode)"FR").IsValid); // False — never added
Some external code types (the ones ISO 20022's own registry snapshot had known values for) start
pre-populated automatically — Add/Remove still work the same way, adjusting the seeded set
rather than starting from nothing.
If populating a fixed list isn't enough — you want a rule of your own layered on top, or values
that come from a database instead of an in-memory set — subclass InMemoryExternalCodeRegistry<TCode>
and override IsAcceptable. It's declared virtual specifically for this:
public class EmbargoedCountryRegistry : InMemoryExternalCodeRegistry<CountryCode>
{
private static readonly HashSet<string> Embargoed = ["KP", "IR", "SY"];
public override bool IsAcceptable(CountryCode value) =>
base.IsAcceptable(value) && !Embargoed.Contains(value.Value);
}
services.AddIso20022Validators();
services.AddSingleton<IExternalCodeRegistry<CountryCode>, EmbargoedCountryRegistry>();
using var provider = services.BuildServiceProvider();
var validator = provider.GetRequiredService<IValidator<CountryCode>>();
Console.WriteLine(validator.Validate((CountryCode)"US").IsValid); // True — base is permissive
Console.WriteLine(validator.Validate((CountryCode)"KP").IsValid); // False — rejected by the override
Register your override after AddIso20022Validators() — the last registration for a given
service type wins, so this replaces the in-memory default for CountryCode only; every other
external code type keeps using it.
| 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 is compatible. 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. |
-
net10.0
- BeneficialStrategies.Iso20022 (>= 0.6.2-alpha)
- FluentValidation (>= 11.12.0)
- FluentValidation.DependencyInjectionExtensions (>= 11.12.0)
-
net8.0
- BeneficialStrategies.Iso20022 (>= 0.6.2-alpha)
- FluentValidation (>= 11.12.0)
- FluentValidation.DependencyInjectionExtensions (>= 11.12.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.6.2-alpha | 42 | 8/31/2026 |
0.6.2-alpha
First NuGet release of this companion package, published by the BeneficialStrategies.com organization via NuGet Trusted Publishing, in lockstep with BeneficialStrategies.Iso20022 0.6.2-alpha (see that package's release notes for the full history of the underlying message model).
FluentValidation validators for the ISO 20022 message domain model, generated from the ISO 20022 specification.
Full spec-compliance coverage (field-level constraints and cross-field rules) for CAMT-reachable components.
Abbreviated coverage (currently-known minimum-collection-size gaps only) for the remaining components, pending full review.