diff --git a/ZB.MOM.WW.Configuration/src/ZB.MOM.WW.Configuration/OptionsValidatorBase.cs b/ZB.MOM.WW.Configuration/src/ZB.MOM.WW.Configuration/OptionsValidatorBase.cs
new file mode 100644
index 0000000..c2dc7eb
--- /dev/null
+++ b/ZB.MOM.WW.Configuration/src/ZB.MOM.WW.Configuration/OptionsValidatorBase.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.Options;
+
+namespace ZB.MOM.WW.Configuration;
+
+///
+/// Base class for implementations that removes the
+/// failure-accumulation plumbing. Override and
+/// use the supplied ; the base aggregates ALL failures and returns
+/// only when none were recorded.
+///
+/// The options type being validated.
+public abstract class OptionsValidatorBase : IValidateOptions
+ where TOptions : class
+{
+ ///
+ public ValidateOptionsResult Validate(string? name, TOptions options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+ var builder = new ValidationBuilder();
+ Validate(builder, options);
+ return builder.IsValid
+ ? ValidateOptionsResult.Success
+ : ValidateOptionsResult.Fail(builder.Failures);
+ }
+
+ /// Records validation failures for on .
+ /// The accumulator to record failures on.
+ /// The options instance to validate.
+ protected abstract void Validate(ValidationBuilder builder, TOptions options);
+}
diff --git a/ZB.MOM.WW.Configuration/tests/ZB.MOM.WW.Configuration.Tests/OptionsValidatorBaseTests.cs b/ZB.MOM.WW.Configuration/tests/ZB.MOM.WW.Configuration.Tests/OptionsValidatorBaseTests.cs
new file mode 100644
index 0000000..1def4df
--- /dev/null
+++ b/ZB.MOM.WW.Configuration/tests/ZB.MOM.WW.Configuration.Tests/OptionsValidatorBaseTests.cs
@@ -0,0 +1,37 @@
+using Microsoft.Extensions.Options;
+using ZB.MOM.WW.Configuration;
+
+namespace ZB.MOM.WW.Configuration.Tests;
+
+public sealed class OptionsValidatorBaseTests
+{
+ private sealed class SampleOptions
+ {
+ public int Port { get; set; }
+ public string? Name { get; set; }
+ }
+
+ private sealed class SampleValidator : OptionsValidatorBase
+ {
+ protected override void Validate(ValidationBuilder v, SampleOptions o)
+ {
+ v.Port(o.Port, "Sample:Port");
+ v.Required(o.Name, "Sample:Name");
+ }
+ }
+
+ [Fact]
+ public void Success_when_clean()
+ {
+ var r = new SampleValidator().Validate(null, new SampleOptions { Port = 8080, Name = "ok" });
+ Assert.True(r.Succeeded);
+ }
+
+ [Fact]
+ public void Fails_and_reports_all_failures()
+ {
+ var r = new SampleValidator().Validate(null, new SampleOptions { Port = 0, Name = "" });
+ Assert.True(r.Failed);
+ Assert.Equal(2, r.Failures!.Count());
+ }
+}